feat(blocks): add reusable application shell blocks

- add appearance state, theme presets, preference controls, and preview components

- add responsive primary, nested, mobile, and flyout navigation with route-aware breadcrumbs

- add application layout, sidebar state, header actions, user menu, query refresh, and scroll utilities

- add configurable chat and notification surfaces backed by consumer-provided data and queries

- expose package entry points and cover theme, state, and command behavior with tests
This commit is contained in:
Maofeng
2026-07-29 21:22:47 +08:00
parent 7b9270ef67
commit 011e7d8115
51 changed files with 5253 additions and 0 deletions
@@ -0,0 +1,70 @@
import * as React from "react"
import { getOverflowAncestors, isOverflowElement } from "@floating-ui/utils/dom"
import { addEventListener } from "@base-ui/utils/addEventListener"
import { mergeCleanups } from "@base-ui/utils/mergeCleanups"
import { ownerWindow } from "@base-ui/utils/owner"
type ScrollContainer = HTMLElement | React.RefObject<HTMLElement | null>
type ScrollTarget = Element | VisualViewport | Window
function getScrollTargets(element: HTMLElement): ScrollTarget[] {
const targets: ScrollTarget[] = []
if (isOverflowElement(element)) {
targets.push(element)
}
targets.push(...getOverflowAncestors(element, [], false))
if (targets.length === 0) {
targets.push(ownerWindow(element))
}
return Array.from(new Set(targets))
}
export function useOnScroll(listener: VoidFunction): void
export function useOnScroll(
container: ScrollContainer,
listener: VoidFunction
): void
export function useOnScroll(
containerOrListener: ScrollContainer | VoidFunction,
listener?: VoidFunction
) {
const container =
typeof containerOrListener === "function" ? null : containerOrListener
const scrollListener =
typeof containerOrListener === "function" ? containerOrListener : listener
if (!scrollListener) {
throw new Error(
"useOnScroll requires a listener when a scroll container is provided."
)
}
const onScroll = React.useEffectEvent(scrollListener)
React.useEffect(() => {
const targets =
container === null
? [window]
: "current" in container
? container.current && getScrollTargets(container.current)
: getScrollTargets(container)
if (!targets) {
return
}
const handleScroll = () => {
onScroll()
}
return mergeCleanups(
...targets.map((target) =>
addEventListener(target, "scroll", handleScroll, { passive: true })
)
)
}, [container])
}
+33
View File
@@ -0,0 +1,33 @@
import * as React from "react"
import { useIsFetching, useQueryClient } from "@tanstack/react-query"
const ACTIVE_QUERY_FILTERS = { type: "active" } as const
export function useRefresh() {
const queryClient = useQueryClient()
const queryCache = queryClient.getQueryCache()
const isRefreshing = useIsFetching(ACTIVE_QUERY_FILTERS) > 0
const subscribe = React.useCallback(
(listener: VoidFunction) => queryCache.subscribe(listener),
[queryCache]
)
const getSnapshot = React.useCallback(
() => queryCache.findAll(ACTIVE_QUERY_FILTERS).length > 0,
[queryCache]
)
const canRefresh = React.useSyncExternalStore(
subscribe,
getSnapshot,
() => false
)
const refresh = React.useCallback(() => {
if (!canRefresh || isRefreshing) {
return
}
void queryClient.refetchQueries(ACTIVE_QUERY_FILTERS)
}, [canRefresh, isRefreshing, queryClient])
return { canRefresh, isRefreshing, refresh }
}