Files
supabase/packages/ui-patterns/CommandMenu/api/hooks/pagesHooks.ts
Charis 1b89fa0b5c feat: command menu v2 ui components (#27761)
Co-authored-by: Francesco Sansalvadore <f.sansalvadore@gmail.com>
2024-07-16 14:14:03 -04:00

97 lines
2.7 KiB
TypeScript

'use client'
import { isEqual } from 'lodash'
import { useEffect, useMemo, useRef } from 'react'
import { useSnapshot } from 'valtio'
import { useCommandContext } from '../../internal/Context'
import { type PageDefinition, isComponentPage } from '../../internal/state/pagesState'
import { useSetQuery } from './queryHooks'
const useCurrentPage = () => {
const { pagesState } = useCommandContext()
const { commandPages, pageStack } = useSnapshot(pagesState)
const topOfStack = pageStack.at(-1)
const result = useMemo(
() =>
topOfStack && commandPages[topOfStack]
? { ...commandPages[topOfStack], name: topOfStack }
: undefined,
[commandPages, topOfStack]
)
return result
}
const usePageComponent = () => {
const _currentPage = useCurrentPage()
const currentPage = _currentPage as PageDefinition
if (!currentPage || !isComponentPage(currentPage)) return undefined
return currentPage.component
}
const useSetPage = () => {
const { pagesState } = useCommandContext()
const setQuery = useSetQuery()
return (name: string, preserveQuery: boolean = false) => {
pagesState.appendPageStack(name)
if (!preserveQuery) setQuery('')
}
}
const usePopPage = () => {
const { pagesState } = useCommandContext()
const { popPageStack } = useSnapshot(pagesState)
return popPageStack
}
const EMPTY_ARRAY = [] as any[]
const useRegisterPage = (
name: string,
definition: PageDefinition,
{ deps = EMPTY_ARRAY, enabled = true }: { deps?: any[]; enabled?: boolean } = {}
) => {
const { pagesState } = useCommandContext()
const { registerNewPage } = useSnapshot(pagesState)
const prevDeps = useRef(deps)
const prevEnabled = useRef(enabled)
const unsubscribe = useRef<() => void>()
/**
* useEffect handles the registration on first render, since React runs the
* first render twice in development. (Otherwise the first render would leave
* a dangling subscription.)
*
* It also handles final cleanup, since useMemo can't do this.
*
* useMemo handles the registration on subsequent renders, to ensure it
* happens synchronously.
*/
useMemo(() => {
if (!isEqual(prevDeps.current, deps) || prevEnabled.current !== enabled) {
unsubscribe.current?.()
unsubscribe.current = enabled ? registerNewPage(name, definition) : undefined
prevDeps.current = deps
prevEnabled.current = enabled
}
}, [registerNewPage, name, definition, deps, enabled])
useEffect(() => {
unsubscribe.current = enabled ? registerNewPage(name, definition) : undefined
return () => unsubscribe.current?.()
}, [])
}
export { useCurrentPage, useRegisterPage, useSetPage, usePopPage, usePageComponent }