mirror of
https://github.com/wavetermdev/docusaurus-og.git
synced 2026-08-05 13:46:35 -07:00
feat: implement image lightbox
This commit is contained in:
@@ -82,6 +82,7 @@ export default function logosPreset(
|
||||
})
|
||||
|
||||
plugins.push('docusaurus-plugin-sass')
|
||||
themes.push('@docusaurus/theme-mermaid')
|
||||
|
||||
if (options.theme?.name !== ThemeNames.DocusaurusDefault)
|
||||
plugins.push(
|
||||
|
||||
@@ -24,6 +24,8 @@ import StatusSvg from '../../static/icons/status.svg'
|
||||
import TelegramSvg from '../../static/icons/telegram.svg'
|
||||
import TwitterSvg from '../../static/icons/twitter.svg'
|
||||
import EditSvg from '../../static/icons/edit.svg'
|
||||
import FullscreenSvg from '../../static/icons/fullscreen.svg'
|
||||
import FullscreenExitSvg from '../../static/icons/fullscreen-exit.svg'
|
||||
|
||||
type TIconProps = {
|
||||
size?: 's' | 'm' | 'l'
|
||||
@@ -178,3 +180,15 @@ export const IconEdit = (props: TIconProps): JSX.Element => (
|
||||
<EditSvg />
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export const IconFullscreen = (props: TIconProps): JSX.Element => (
|
||||
<Icon {...props}>
|
||||
<FullscreenSvg />
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export const IconFullscreenExit = (props: TIconProps): JSX.Element => (
|
||||
<Icon {...props}>
|
||||
<FullscreenExitSvg />
|
||||
</Icon>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import { IconButton } from '@acid-info/lsd-react'
|
||||
import NavbarLogo from '@theme/Navbar/Logo'
|
||||
import clsx from 'clsx'
|
||||
import React, {
|
||||
CSSProperties,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useWindowScroll } from 'react-use'
|
||||
import { IconFullscreen, IconFullscreenExit } from '../../components/Icon/Icon'
|
||||
import { Portal } from '../../components/Portal/Portal'
|
||||
import { useHydrated } from '../../lib/useHydrated'
|
||||
import styles from './styles.module.scss'
|
||||
|
||||
export const LightBoxProvider: React.FC<React.PropsWithChildren<{}>> = ({
|
||||
children,
|
||||
}) => {
|
||||
const hydrated = useHydrated()
|
||||
const scroll = useWindowScroll()
|
||||
const [active, setActive] = useState<HTMLElement | null>(null)
|
||||
const [activeStyle, setActiveStyle] = useState<CSSProperties>({
|
||||
opacity: '0.5',
|
||||
})
|
||||
|
||||
const style: React.CSSProperties = useMemo(() => {
|
||||
return {
|
||||
opacity: 1,
|
||||
transform: 'scale(1) translate(0px, 0px)',
|
||||
transition: '0.3s',
|
||||
}
|
||||
}, [active])
|
||||
|
||||
const display = (element: HTMLElement) => {
|
||||
setActive(element)
|
||||
|
||||
const vw = document.body.clientWidth
|
||||
const vh = document.body.clientHeight
|
||||
|
||||
const maxWidth = window.innerWidth > 768 ? vw * 0.9375 : vw - 32
|
||||
const maxHeight = vh - 128
|
||||
|
||||
const rect = element.getBoundingClientRect()
|
||||
|
||||
const scale = Math.min(maxHeight / rect.height, maxWidth / rect.width)
|
||||
|
||||
const center = [rect.left + rect.width / 2, rect.top + rect.height / 2]
|
||||
const windowCenter = [vw / 2, vh / 2]
|
||||
|
||||
const translate = windowCenter.map((w, i) => (w - center[i]!) / scale)
|
||||
|
||||
setActiveStyle({
|
||||
zIndex: 202,
|
||||
transform: `scale(${scale}) translate(${translate[0]}px, ${translate[1]}px)`,
|
||||
position: 'relative',
|
||||
})
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
setActive(null)
|
||||
}
|
||||
|
||||
const toggle = (element: HTMLElement) => {
|
||||
const current = active
|
||||
|
||||
close()
|
||||
current !== element && display(element)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (active && window.innerWidth > 768) close()
|
||||
}, [scroll])
|
||||
|
||||
return (
|
||||
<LightBoxContext.Provider
|
||||
value={{ active, style, activeStyle, display, close, toggle }}
|
||||
>
|
||||
{children}
|
||||
{hydrated && (
|
||||
<Portal containerId="lsd-presentation">
|
||||
<div className={clsx(styles.nav, active && styles.visible)}>
|
||||
<nav>
|
||||
<NavbarLogo />
|
||||
<IconButton size="medium" onClick={close}>
|
||||
<IconFullscreenExit />
|
||||
</IconButton>
|
||||
</nav>
|
||||
</div>
|
||||
</Portal>
|
||||
)}
|
||||
</LightBoxContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export type LightBoxContextType = {
|
||||
active: HTMLElement | null
|
||||
display: (element: HTMLElement) => void
|
||||
close: () => void
|
||||
toggle: (element: HTMLElement) => void
|
||||
style: React.CSSProperties
|
||||
activeStyle: React.CSSProperties
|
||||
}
|
||||
|
||||
export const LightBoxContext = React.createContext<LightBoxContextType>({
|
||||
style: {},
|
||||
activeStyle: {},
|
||||
active: null,
|
||||
close: null as any,
|
||||
toggle: null as any,
|
||||
display: null as any,
|
||||
})
|
||||
|
||||
export const useLightBox = () => {
|
||||
const ctx = useContext(LightBoxContext)
|
||||
|
||||
const getStyle = (element: HTMLElement) => ({
|
||||
...ctx.style,
|
||||
...(element === ctx.active ? ctx.activeStyle : {}),
|
||||
})
|
||||
|
||||
return {
|
||||
getStyle,
|
||||
style: ctx.style,
|
||||
activeStyle: ctx.activeStyle,
|
||||
active: ctx.active,
|
||||
isActive: !!ctx.active,
|
||||
close: ctx.close,
|
||||
toggle: ctx.toggle,
|
||||
display: ctx.display,
|
||||
isActiveElement: (el: HTMLElement) => ctx.active === el,
|
||||
}
|
||||
}
|
||||
|
||||
export const LightBoxWrapper: React.FC<React.PropsWithChildren<{}>> = ({
|
||||
children,
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const { getStyle, display, isActiveElement } = useLightBox()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
styles.wrapper,
|
||||
isActiveElement(ref.current!) && styles.active,
|
||||
)}
|
||||
ref={ref}
|
||||
style={ref.current ? getStyle(ref.current) : {}}
|
||||
>
|
||||
{children}
|
||||
<IconButton
|
||||
className={styles.fullscreenButton}
|
||||
size="medium"
|
||||
onClick={() => ref.current && display(ref.current)}
|
||||
>
|
||||
<IconFullscreen />
|
||||
</IconButton>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './LightBox'
|
||||
@@ -0,0 +1,69 @@
|
||||
@use '../../css/utils';
|
||||
|
||||
.nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgb(var(--lsd-surface-primary));
|
||||
z-index: 201;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.nav > nav {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: var(--ifm-navbar-height);
|
||||
max-width: var(--container-max-width);
|
||||
margin: 0 auto;
|
||||
padding: var(--ifm-navbar-padding-vertical)
|
||||
var(--ifm-navbar-padding-horizontal);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
position: relative;
|
||||
|
||||
.fullscreenButton {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:not(.active) {
|
||||
.fullscreenButton {
|
||||
display: block;
|
||||
transition: 0.3s;
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
background: rgb(var(--lsd-surface-primary));
|
||||
}
|
||||
|
||||
@include utils.responsive('lg', 'up') {
|
||||
.fullscreenButton {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: 0.3s;
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
background: rgb(var(--lsd-surface-primary));
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.fullscreenButton {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.66675 11.0832V9.33317H2.91675V8.1665H5.83341V11.0832H4.66675ZM8.16675 11.0832V8.1665H11.0834V9.33317H9.33342V11.0832H8.16675ZM2.91675 5.83317V4.6665H4.66675V2.9165H5.83341V5.83317H2.91675ZM8.16675 5.83317V2.9165H9.33342V4.6665H11.0834V5.83317H8.16675Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 383 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.75 2.91667V5.25H2.91667V2.91667H5.25V1.75H2.91667C2.275 1.75 1.75 2.275 1.75 2.91667ZM2.91667 8.75H1.75V11.0833C1.75 11.725 2.275 12.25 2.91667 12.25H5.25V11.0833H2.91667V8.75ZM11.0833 11.0833H8.75V12.25H11.0833C11.725 12.25 12.25 11.725 12.25 11.0833V8.75H11.0833V11.0833ZM11.0833 1.75H8.75V2.91667H11.0833V5.25H12.25V2.91667C12.25 2.275 11.725 1.75 11.0833 1.75Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 496 B |
@@ -0,0 +1,15 @@
|
||||
import Img from '@docusaurus/theme-classic/lib/theme/MDXComponents/Img'
|
||||
import type { WrapperProps } from '@docusaurus/types'
|
||||
import type ImgType from '@theme/MDXComponents/Img'
|
||||
import React from 'react'
|
||||
import { LightBoxWrapper } from '../../../containers/LightBox/LightBox'
|
||||
|
||||
type Props = WrapperProps<typeof ImgType>
|
||||
|
||||
export default function ImgWrapper(props: Props): JSX.Element {
|
||||
return (
|
||||
<LightBoxWrapper>
|
||||
<Img {...props} />
|
||||
</LightBoxWrapper>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// @ts-ignore
|
||||
import Mermaid from '@docusaurus/theme-mermaid/lib/theme/Mermaid'
|
||||
import type { WrapperProps } from '@docusaurus/types'
|
||||
import type MermaidType from '@theme/Mermaid'
|
||||
import React from 'react'
|
||||
import { LightBoxWrapper } from '../../containers/LightBox/LightBox'
|
||||
|
||||
type Props = WrapperProps<typeof MermaidType>
|
||||
|
||||
export default function MermaidWrapper(props: Props): JSX.Element {
|
||||
return (
|
||||
<LightBoxWrapper>
|
||||
<Mermaid {...props} />
|
||||
</LightBoxWrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ColorModeProvider } from '@docusaurus/theme-common/internal'
|
||||
import React from 'react'
|
||||
import { LightBoxProvider } from '../containers/LightBox/LightBox'
|
||||
import { ThemeProvider } from '../containers/ThemeProvider'
|
||||
import { useDocThemeOptions } from '../lib/useThemeOptions'
|
||||
import styles from './style.module.css'
|
||||
@@ -11,9 +12,11 @@ export default function Root({ children }) {
|
||||
return (
|
||||
<ColorModeProvider>
|
||||
<ThemeProvider>
|
||||
<div className={styles.root} data-hidden-doc-sidebar={hideDocSidebar}>
|
||||
{children}
|
||||
</div>
|
||||
<LightBoxProvider>
|
||||
<div className={styles.root} data-hidden-doc-sidebar={hideDocSidebar}>
|
||||
{children}
|
||||
</div>
|
||||
</LightBoxProvider>
|
||||
</ThemeProvider>
|
||||
</ColorModeProvider>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user