2025-11-28 06:31:36 +00:00
|
|
|
import { IconButton } from '@/components/ui/button'
|
|
|
|
|
import { cn } from '@/lib/utils'
|
|
|
|
|
import { createContext, useContext, useEffect, useMemo, useCallback, useState, useRef } from 'react'
|
2025-11-13 08:38:25 +00:00
|
|
|
|
|
|
|
|
// 管理所有抽屉的层级顺序
|
|
|
|
|
export const DrawerLayerContext = createContext<{
|
2025-11-28 06:31:36 +00:00
|
|
|
openOrder: string[]
|
|
|
|
|
registerDrawer: (id: string) => void
|
|
|
|
|
unregisterDrawer: (id: string) => void
|
|
|
|
|
bringToFront: (id: string) => void
|
|
|
|
|
getZIndex: (id: string) => number
|
2025-11-13 08:38:25 +00:00
|
|
|
}>({
|
|
|
|
|
openOrder: [],
|
|
|
|
|
registerDrawer: () => {},
|
|
|
|
|
unregisterDrawer: () => {},
|
|
|
|
|
bringToFront: () => {},
|
|
|
|
|
getZIndex: () => 0,
|
2025-11-28 06:31:36 +00:00
|
|
|
})
|
2025-11-13 08:38:25 +00:00
|
|
|
|
|
|
|
|
export const DrawerLayerProvider = ({
|
|
|
|
|
children,
|
|
|
|
|
baseZIndex = 10,
|
|
|
|
|
}: {
|
2025-11-28 06:31:36 +00:00
|
|
|
children: React.ReactNode
|
|
|
|
|
baseZIndex?: number
|
2025-11-13 08:38:25 +00:00
|
|
|
}) => {
|
2025-11-28 06:31:36 +00:00
|
|
|
const [openOrder, setOpenOrder] = useState<string[]>([])
|
2025-11-13 08:38:25 +00:00
|
|
|
|
|
|
|
|
const registerDrawer = useCallback((id: string) => {
|
2025-11-28 06:31:36 +00:00
|
|
|
setOpenOrder((prev) => (prev.includes(id) ? prev : [...prev, id]))
|
|
|
|
|
}, [])
|
2025-11-13 08:38:25 +00:00
|
|
|
|
|
|
|
|
const unregisterDrawer = useCallback((id: string) => {
|
2025-11-28 06:31:36 +00:00
|
|
|
setOpenOrder((prev) => prev.filter((item) => item !== id))
|
|
|
|
|
}, [])
|
2025-11-13 08:38:25 +00:00
|
|
|
|
|
|
|
|
const bringToFront = useCallback((id: string) => {
|
|
|
|
|
setOpenOrder((prev) => {
|
|
|
|
|
// 如果该抽屉已经在最前面,则不需要更新
|
|
|
|
|
if (prev.length > 0 && prev[prev.length - 1] === id) {
|
2025-11-28 06:31:36 +00:00
|
|
|
return prev
|
2025-11-13 08:38:25 +00:00
|
|
|
}
|
2025-11-28 06:31:36 +00:00
|
|
|
const filtered = prev.filter((item) => item !== id)
|
|
|
|
|
return [...filtered, id]
|
|
|
|
|
})
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
const getZIndex = useCallback(
|
|
|
|
|
(id: string) => {
|
|
|
|
|
const index = openOrder.indexOf(id)
|
|
|
|
|
if (index === -1) return baseZIndex
|
|
|
|
|
return baseZIndex + index
|
|
|
|
|
},
|
|
|
|
|
[openOrder, baseZIndex]
|
|
|
|
|
)
|
2025-11-13 08:38:25 +00:00
|
|
|
|
|
|
|
|
const value = useMemo(
|
|
|
|
|
() => ({ openOrder, registerDrawer, unregisterDrawer, bringToFront, getZIndex }),
|
|
|
|
|
[openOrder, registerDrawer, unregisterDrawer, bringToFront, getZIndex]
|
2025-11-28 06:31:36 +00:00
|
|
|
)
|
2025-11-13 08:38:25 +00:00
|
|
|
|
2025-11-28 06:31:36 +00:00
|
|
|
return <DrawerLayerContext.Provider value={value}>{children}</DrawerLayerContext.Provider>
|
|
|
|
|
}
|
2025-11-13 08:38:25 +00:00
|
|
|
|
|
|
|
|
const InlineDrawerContext = createContext<{
|
2025-11-28 06:31:36 +00:00
|
|
|
id: string
|
|
|
|
|
open: boolean
|
|
|
|
|
onOpenChange: (open: boolean) => void
|
2025-11-13 08:38:25 +00:00
|
|
|
}>({
|
2025-11-28 06:31:36 +00:00
|
|
|
id: '',
|
2025-11-13 08:38:25 +00:00
|
|
|
open: false,
|
|
|
|
|
onOpenChange: () => {},
|
2025-11-28 06:31:36 +00:00
|
|
|
})
|
2025-11-13 08:38:25 +00:00
|
|
|
|
|
|
|
|
export const InlineDrawer = ({
|
|
|
|
|
id,
|
|
|
|
|
open,
|
|
|
|
|
onOpenChange,
|
|
|
|
|
timestamp,
|
|
|
|
|
children,
|
|
|
|
|
}: {
|
2025-11-28 06:31:36 +00:00
|
|
|
id: string
|
|
|
|
|
open: boolean
|
|
|
|
|
onOpenChange: (open: boolean) => void
|
|
|
|
|
timestamp?: number
|
|
|
|
|
children: React.ReactNode
|
2025-11-13 08:38:25 +00:00
|
|
|
}) => {
|
2025-11-28 06:31:36 +00:00
|
|
|
const { registerDrawer, unregisterDrawer, bringToFront } = useContext(DrawerLayerContext)
|
2025-11-13 08:38:25 +00:00
|
|
|
|
|
|
|
|
// 当抽屉打开时注册并置顶;当关闭或卸载时移除
|
|
|
|
|
// 监听 timestamp 变化,确保每次重新打开时都会置顶
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (open) {
|
2025-11-28 06:31:36 +00:00
|
|
|
registerDrawer(id)
|
|
|
|
|
bringToFront(id)
|
2025-11-13 08:38:25 +00:00
|
|
|
}
|
2025-11-28 06:31:36 +00:00
|
|
|
}, [open, timestamp, id, registerDrawer, bringToFront])
|
2025-11-13 08:38:25 +00:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
return () => {
|
2025-11-28 06:31:36 +00:00
|
|
|
unregisterDrawer(id)
|
|
|
|
|
}
|
|
|
|
|
}, [id, unregisterDrawer, open])
|
2025-11-13 08:38:25 +00:00
|
|
|
|
|
|
|
|
// 当抽屉关闭时不渲染任何内容
|
|
|
|
|
if (!open) {
|
2025-11-28 06:31:36 +00:00
|
|
|
return null
|
2025-11-13 08:38:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<InlineDrawerContext.Provider value={{ id, open, onOpenChange }}>
|
|
|
|
|
{children}
|
|
|
|
|
</InlineDrawerContext.Provider>
|
2025-11-28 06:31:36 +00:00
|
|
|
)
|
|
|
|
|
}
|
2025-11-13 08:38:25 +00:00
|
|
|
|
|
|
|
|
export const InlineDrawerContent = ({
|
|
|
|
|
children,
|
|
|
|
|
className,
|
|
|
|
|
}: {
|
2025-11-28 06:31:36 +00:00
|
|
|
children: React.ReactNode
|
|
|
|
|
className?: string
|
2025-11-13 08:38:25 +00:00
|
|
|
}) => {
|
2025-11-28 06:31:36 +00:00
|
|
|
const { id } = useContext(InlineDrawerContext)
|
|
|
|
|
const { getZIndex, bringToFront } = useContext(DrawerLayerContext)
|
|
|
|
|
const zIndex = getZIndex(id)
|
2025-11-13 08:38:25 +00:00
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
2025-11-28 06:31:36 +00:00
|
|
|
'bg-background-default border-outline-normal absolute inset-0 flex w-[400px] flex-col border-l border-solid',
|
2025-11-13 08:38:25 +00:00
|
|
|
className
|
|
|
|
|
)}
|
|
|
|
|
style={{ zIndex }}
|
|
|
|
|
onPointerDownCapture={() => bringToFront(id)}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</div>
|
2025-11-28 06:31:36 +00:00
|
|
|
)
|
|
|
|
|
}
|
2025-11-13 08:38:25 +00:00
|
|
|
|
2025-11-28 06:31:36 +00:00
|
|
|
export const InlineDrawerHeader = ({ children }: { children: React.ReactNode }) => {
|
|
|
|
|
const { onOpenChange } = useContext(InlineDrawerContext)
|
2025-11-13 08:38:25 +00:00
|
|
|
return (
|
|
|
|
|
<div className="flex items-center gap-2 p-6">
|
2025-11-28 06:31:36 +00:00
|
|
|
<IconButton
|
|
|
|
|
iconfont="icon-arrow-right"
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="small"
|
|
|
|
|
onClick={() => onOpenChange(false)}
|
|
|
|
|
/>
|
|
|
|
|
<div className="txt-title-m min-w-0 flex-1">{children}</div>
|
2025-11-13 08:38:25 +00:00
|
|
|
</div>
|
2025-11-28 06:31:36 +00:00
|
|
|
)
|
2025-11-13 08:38:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const InlineDrawerDescription = ({
|
|
|
|
|
children,
|
|
|
|
|
className,
|
|
|
|
|
}: {
|
2025-11-28 06:31:36 +00:00
|
|
|
children: React.ReactNode
|
|
|
|
|
className?: string
|
2025-11-13 08:38:25 +00:00
|
|
|
}) => {
|
2025-11-28 06:31:36 +00:00
|
|
|
return <div className={cn('flex-1 overflow-y-auto px-6', className)}>{children}</div>
|
|
|
|
|
}
|
2025-11-13 08:38:25 +00:00
|
|
|
|
|
|
|
|
export const InlineDrawerFooter = ({
|
|
|
|
|
children,
|
|
|
|
|
className,
|
|
|
|
|
}: {
|
2025-11-28 06:31:36 +00:00
|
|
|
children: React.ReactNode
|
|
|
|
|
className?: string
|
2025-11-13 08:38:25 +00:00
|
|
|
}) => {
|
2025-11-28 06:31:36 +00:00
|
|
|
return <div className={cn('flex items-center justify-end gap-4 p-6', className)}>{children}</div>
|
|
|
|
|
}
|