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:
@@ -0,0 +1,35 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { fireEvent, render } from "@testing-library/react"
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
import {
|
||||
getNavigationCommandProps,
|
||||
NavigationActionTarget,
|
||||
navigationActions,
|
||||
} from "./command-actions"
|
||||
|
||||
describe("navigation command", () => {
|
||||
it("connects an action button to its command target", () => {
|
||||
expect(getNavigationCommandProps(navigationActions.search)).toEqual({
|
||||
command: "--invoke",
|
||||
commandfor: "navigation-action-search",
|
||||
})
|
||||
})
|
||||
|
||||
it("invokes the matching command target", () => {
|
||||
const onInvoke = vi.fn()
|
||||
const { container } = render(
|
||||
<NavigationActionTarget
|
||||
action={navigationActions.search}
|
||||
onInvoke={onInvoke}
|
||||
/>
|
||||
)
|
||||
const target = container.querySelector("#navigation-action-search")
|
||||
const event = new Event("command")
|
||||
Object.defineProperty(event, "command", { value: "--invoke" })
|
||||
|
||||
fireEvent(target!, event)
|
||||
|
||||
expect(onInvoke).toHaveBeenCalledOnce()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from "react"
|
||||
|
||||
export const navigationActions = {
|
||||
appearance: "appearance",
|
||||
logout: "logout",
|
||||
passwords: "passwords",
|
||||
search: "search",
|
||||
userMenu: "user-menu",
|
||||
} as const
|
||||
|
||||
export type NavigationAction =
|
||||
(typeof navigationActions)[keyof typeof navigationActions]
|
||||
|
||||
const NAVIGATION_INVOKE_COMMAND = "--invoke"
|
||||
const NAVIGATION_ACTION_TARGET_PREFIX = "navigation-action-"
|
||||
|
||||
export function getNavigationCommandProps(action: NavigationAction) {
|
||||
return {
|
||||
command: NAVIGATION_INVOKE_COMMAND,
|
||||
commandfor: `${NAVIGATION_ACTION_TARGET_PREFIX}${action}`,
|
||||
}
|
||||
}
|
||||
|
||||
export function NavigationActionTarget({
|
||||
action,
|
||||
onInvoke,
|
||||
}: {
|
||||
action: NavigationAction
|
||||
onInvoke: VoidFunction
|
||||
}) {
|
||||
const targetRef = React.useRef<HTMLSpanElement>(null)
|
||||
const invoke = React.useEffectEvent(onInvoke)
|
||||
|
||||
React.useEffect(() => {
|
||||
const target = targetRef.current
|
||||
if (!target) return
|
||||
|
||||
const handleCommand = (event: Event) => {
|
||||
if ((event as CommandEvent).command === NAVIGATION_INVOKE_COMMAND) {
|
||||
invoke()
|
||||
}
|
||||
}
|
||||
|
||||
target.addEventListener("command", handleCommand)
|
||||
return () => target.removeEventListener("command", handleCommand)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<span
|
||||
ref={targetRef}
|
||||
id={`${NAVIGATION_ACTION_TARGET_PREFIX}${action}`}
|
||||
hidden
|
||||
/>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user