| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- import { ReactNode, useEffect } from 'react'
- import { proxy, snapshot, useSnapshot } from 'valtio'
- import useLatest from '@/hooks/misc/useLatest'
- type SidebarHandlers = {
- onOpen?: () => void
- onClose?: () => void
- }
- type ManagedSidebar = SidebarHandlers & {
- id: string
- component: () => ReactNode | undefined
- }
- type SidebarManagerData = {
- sidebars: Partial<Record<string, ManagedSidebar>>
- activeSidebar: ManagedSidebar | undefined
- pendingSidebarOpen: string | undefined
- }
- type SidebarManagerState = SidebarManagerData & {
- registerSidebar: (
- id: string,
- component: () => ReactNode | undefined,
- handlers?: SidebarHandlers
- ) => void
- unregisterSidebar: (id: string) => void
- openSidebar: (id: string) => void
- toggleSidebar: (id: string) => void
- closeSidebar: (id: string) => void
- isSidebarOpen: (id: string) => boolean
- closeActive: () => void
- clearActiveSidebar: () => void
- }
- const INITIAL_SIDEBAR_MANAGER_DATA: SidebarManagerData = {
- sidebars: {},
- activeSidebar: undefined,
- pendingSidebarOpen: undefined,
- }
- const createSidebarManagerState = () => {
- const state: SidebarManagerState = proxy({
- ...INITIAL_SIDEBAR_MANAGER_DATA,
- registerSidebar(
- id: string,
- component: () => ReactNode | undefined,
- handlers: SidebarHandlers = {}
- ) {
- state.sidebars[id] = {
- id,
- component,
- ...handlers,
- }
- // If this sidebar was pending to be opened, open it now
- if (state.pendingSidebarOpen === id) {
- state.pendingSidebarOpen = undefined
- state.openSidebar(id)
- }
- },
- unregisterSidebar(id: string) {
- const panel = state.sidebars[id]
- if (!panel) return
- delete state.sidebars[id]
- // Clear pending open if this sidebar was pending
- if (state.pendingSidebarOpen === id) {
- state.pendingSidebarOpen = undefined
- }
- if (state.activeSidebar?.id === id) {
- state.activeSidebar = undefined
- panel.onClose?.()
- }
- },
- openSidebar(id: string) {
- const panel = state.sidebars[id]
- if (!panel) {
- // Queue the request - sidebar will be opened when it gets registered
- state.pendingSidebarOpen = id
- return
- }
- // Clear any pending request since we're opening a sidebar now
- state.pendingSidebarOpen = undefined
- if (state.activeSidebar?.id === id) {
- return
- }
- if (state.activeSidebar && state.activeSidebar.id !== id) {
- const previousPanel = state.activeSidebar
- previousPanel?.onClose?.()
- }
- state.activeSidebar = panel
- panel.onOpen?.()
- },
- toggleSidebar(id: string) {
- const panel = state.sidebars[id]
- if (!panel) {
- console.warn(`Sidebar "${id}" is not registered. Register it before toggling.`)
- return
- }
- if (state.activeSidebar?.id === id) {
- panel.onClose?.()
- state.activeSidebar = undefined
- return
- }
- if (state.activeSidebar && state.activeSidebar.id !== id) {
- const previousPanel = state.activeSidebar
- previousPanel?.onClose?.()
- }
- state.activeSidebar = panel
- panel.onOpen?.()
- },
- closeSidebar(id: string) {
- if (state.activeSidebar?.id !== id) return
- const panel = state.sidebars[id]
- if (!panel) return
- panel.onClose?.()
- state.activeSidebar = undefined
- return
- },
- isSidebarOpen(id: string) {
- return state.activeSidebar?.id === id
- },
- closeActive() {
- if (!state.activeSidebar) return
- state.activeSidebar?.onClose?.()
- state.activeSidebar = undefined
- },
- clearActiveSidebar() {
- state.activeSidebar = undefined
- },
- })
- return state
- }
- export const sidebarManagerState = createSidebarManagerState()
- export const getSidebarManagerSnapshot = () => snapshot(sidebarManagerState)
- export const useSidebarManagerSnapshot = (options?: Parameters<typeof useSnapshot>[1]) =>
- useSnapshot(sidebarManagerState, options)
- export const useRegisterSidebar = (
- id: string,
- component: () => ReactNode,
- handlers: SidebarHandlers = {},
- enabled?: boolean
- ) => {
- const componentRef = useLatest(component)
- const handlersRef = useLatest(handlers)
- useEffect(() => {
- if (enabled) {
- sidebarManagerState.registerSidebar(id, () => componentRef.current(), handlersRef.current)
- }
- return () => {
- sidebarManagerState.unregisterSidebar(id)
- }
- }, [id, enabled])
- }
|