@gradeui/ui 3.3.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/ui/media-surface.md +1 -1
- package/dist/composer.d.mts +262 -0
- package/dist/composer.d.ts +262 -0
- package/dist/composer.js +3 -0
- package/dist/composer.js.map +1 -0
- package/dist/composer.mjs +3 -0
- package/dist/composer.mjs.map +1 -0
- package/dist/contracts.js +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/contracts.mjs +1 -1
- package/dist/contracts.mjs.map +1 -1
- package/dist/index.d.mts +28 -327
- package/dist/index.d.ts +28 -327
- package/dist/index.js +70 -70
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +70 -70
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types-DUwnWaxR.d.mts +43 -0
- package/dist/types-DUwnWaxR.d.ts +43 -0
- package/package.json +6 -1
- package/styles/globals.css +430 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../lib/demo/types.ts","../lib/demo/sleep.ts","../lib/motion/index.ts","../lib/demo/use-scripted-demo.ts","../lib/utils.ts","../components/ui/composer.tsx"],"names":["DEMO_SPEED_PRESETS","DEMO_IN_VIEW_AMOUNT","sleep","ms","signal","resolve","reject","timer","onAbort","typeText","text","onTick","stagger","i","isAbortError","err","MOTION_ATTR","MOTION_OFF","REDUCE_QUERY","readReduced","osReduced","toggledOff","useReducedMotion","reduced","setReduced","z","update","mql","observer","usePageActive","active","setActive","framed","compute","DEFAULT_LOOP_DELAY_MS","INSTANT_PRESET","useScriptedDemo","opts","steps","interpret","speed","trigger","playProp","loop","loopDelay","maxLoops","containerRef","onComplete","onLoopReset","preset","effectiveLoop","pageActive","isPlaying","setIsPlaying","b","isComplete","setIsComplete","currentIndex","setCurrentIndex","noopRef","inView","useInView","liveInView","playbackActive","manualPlayTick","setManualPlayTick","shouldPlay","interpretRef","completeRef","loopResetRef","controllerRef","restartTimerRef","stop","play","n","restart","delayMs","controller","ctx","cycles","cn","inputs","twMerge","clsx","SubmitPlugin","onSubmit","enabled","editor","useLexicalComposerContext","KEY_ENTER_COMMAND","event","COMMAND_PRIORITY_HIGH","PastePlugin","onImageFiles","rootElement","handler","e","imageFiles","it","f","AutoFocusPlugin","FORMAT_TEXT_KEYS","applyFormat","format","textKey","FORMAT_TEXT_COMMAND","INSERT_UNORDERED_LIST_COMMAND","INSERT_ORDERED_LIST_COMMAND","selection","$getSelection","$isRangeSelection","tag","$setBlocksType","$createHeadingNode","node","$createQuoteNode","insertMentionNode","value","data","$createBeautifulMentionNode","selectSubstring","needle","found","root","$getRoot","idx","cursor","startNode","startOffset","endNode","endOffset","walk","tn","len","nodeStart","nodeEnd","endIdx","child","sn","en","clearEditor","para","$createParagraphNode","snapshotContent","json","mentions","m","FORMAT_BUTTONS","Bold","Italic","Underline","Strikethrough","CodeIcon","Heading1","Heading2","Heading3","Quote","ListIcon","ListOrdered","ComposerToolbar","formats","activeFormats","setActiveFormats","mergeRegister","editorState","next","fmt","visible","Icon","label","isActive","AttachmentChips","attachments","onRemove","AnimatePresence","motion","att","X","ComposerInner","placeholder","triggers","attachmentsCfg","formatList","showToolbar","isLoading","onStop","maxLength","autoFocus","submitOnEnter","leftActions","rightActions","hideSend","bare","className","onChange","readOnly","handleEditorReady","submitRef","restartRef","setAttachments","ingestImages","fileInputRef","hasContent","setHasContent","onChangeRef","handleSubmit","content","hasText","hasAttachments","a","demoRestart","step","sel","focus","partial","head","p","$createTextNode","stripLen","char","sel2","showDefaultSend","showDefaultAttach","showActionRow","id","prev","target","RichTextPlugin","ContentEditable","LexicalErrorBoundary","HistoryPlugin","ListPlugin","LinkPlugin","OnChangePlugin","BeautifulMentionsPlugin","acc","t","item","Paperclip","Square","Send","Composer","props","forwardedRef","initialText","initialJson","toolbar","attachmentsProp","rest","obj","attachmentsRef","files","candidates","tok","room","file","editorRef","_steps","lexicalTheme","initialConfig","error","HeadingNode","QuoteNode","ListNode","ListItemNode","LinkNode","AutoLinkNode","CodeNode","CodeHighlightNode","BeautifulMentionNode","richMode","innerRest","LexicalRoot","ComposerReply","ref"],"mappings":"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","file":"composer.mjs","sourcesContent":["/**\n * Shared types + presets for scripted component demos.\n *\n * Lives at the bottom of the demo primitive stack so the hook, the\n * cursor, and any component that opts in (`<Code>`, `<Composer>`,\n * `<DemoStage>`) all read from one definition of \"what slow / normal /\n * fast feels like\" and one definition of \"when do we start playing\".\n *\n * If you ever want to retune the cadence of every demo on the marketing\n * site at once, this is the file to edit.\n */\n\n/**\n * Animation feel. Maps onto a triple of timing values so authors can\n * pick a vibe (slow / normal / fast) instead of hand-tuning ms.\n * Components that need finer control can still override the resolved\n * values per-instance.\n */\nexport type DemoSpeed = \"slow\" | \"normal\" | \"fast\";\n\n/**\n * What kicks the demo off:\n * - `mount` plays immediately on first paint\n * - `inView` waits for the container to cross the viewport threshold\n * - `manual` driven by the `play` prop or imperative ref\n */\nexport type DemoTrigger = \"mount\" | \"inView\" | \"manual\";\n\n/**\n * Speed presets shared across every scripted-demo surface. Three\n * unambiguously distinct feels: `slow` is \"I am being shown\", `normal`\n * is \"I am being told\", `fast` is \"I am being briefed\".\n *\n * - `tokenStagger` per-character cadence for typing-style steps\n * - `lineStagger` per-line cadence for reveal-style demos\n * - `preDelay` pause after the trigger fires before the first tick\n * - `fadeMs` default enter-transition duration for revealed parts\n */\nexport const DEMO_SPEED_PRESETS: Record<\n DemoSpeed,\n {\n tokenStagger: number;\n lineStagger: number;\n preDelay: number;\n fadeMs: number;\n }\n> = {\n slow: { tokenStagger: 70, lineStagger: 200, preDelay: 500, fadeMs: 480 },\n normal: { tokenStagger: 22, lineStagger: 55, preDelay: 200, fadeMs: 280 },\n fast: { tokenStagger: 8, lineStagger: 18, preDelay: 60, fadeMs: 160 },\n};\n\n/**\n * `inView` threshold + `once` semantics applied by the hook. Marketing\n * surfaces want the reveal to fire once, after enough of the block is\n * visible that the user actually sees it animate.\n */\nexport const DEMO_IN_VIEW_AMOUNT = 0.55;\n","/**\n * Cancellable sleep + type helpers for scripted demos.\n *\n * The runner threads an `AbortSignal` through every step so a `stop()`\n * call (or an unmount) can short-circuit long waits and typing loops\n * cleanly. Consumers write `await sleep(ms, signal)` inside their\n * interpret callback and the cancellation is automatic.\n *\n * Note: this throws DOMException(\"Aborted\", \"AbortError\") on cancel.\n * The runner's outer try/catch swallows that specific error and exits;\n * any other rejection bubbles up so authoring bugs aren't silenced.\n */\n\n/**\n * Promisified setTimeout that resolves after `ms` milliseconds, or\n * rejects with an AbortError if the signal fires first. Resolves\n * immediately if `ms <= 0`.\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timer = window.setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n window.clearTimeout(timer);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/**\n * Type `text` one character at a time, calling `onTick(partial)` for\n * each prefix. Stagger between ticks defaults to 22ms (the `normal`\n * speed preset's `tokenStagger`).\n *\n * `onTick` receives the cumulative typed text (not the new char), so\n * consumers can do `setBuffer(prefix + partial)` without tracking state\n * themselves. The final call fires with the complete text — there's no\n * separate \"done\" callback.\n */\nexport async function typeText(\n text: string,\n onTick: (partial: string) => void,\n stagger = 22,\n signal?: AbortSignal,\n): Promise<void> {\n // Non-positive stagger (the reduced-motion preset, or any caller that\n // wants no typing animation): emit the whole string in a single tick so\n // the surface lands on its final state without per-character motion, or\n // the flicker a 0ms loop would produce.\n if (stagger <= 0) {\n onTick(text);\n return;\n }\n for (let i = 1; i <= text.length; i++) {\n onTick(text.slice(0, i));\n if (i < text.length) await sleep(stagger, signal);\n }\n}\n\n/**\n * Helper for treating an unknown error as the runner's abort sentinel.\n * Returns true if the value is the AbortError thrown by `sleep` / a\n * downstream `fetch` / etc. Used by the runner's catch block to\n * distinguish cancellation (silent exit) from real bugs (rethrow).\n */\nexport function isAbortError(err: unknown): boolean {\n return (\n err instanceof DOMException && err.name === \"AbortError\"\n );\n}\n","/**\n * lib/motion — the global motion control for gradeui.\n *\n * One choke point for \"should this animate?\". Every animated component\n * (ThreeScene, RivePlayer, VideoPlayer, aura surfaces) asks\n * `useReducedMotion()`, so flipping motion off in one place stills them all.\n *\n * Two independent inputs, ORed together (reduce-only by design — the toggle\n * can ADD restriction but never override a user's OS preference to force\n * motion ON):\n *\n * 1. the OS `prefers-reduced-motion: reduce` media query, and\n * 2. a `data-motion=\"off\"` attribute on the document root (`<html>`),\n * the manual toggle.\n *\n * The attribute is the same mechanism the renderer's `data-fidelity` flag\n * uses: stamp it on `<html>` and CSS + components react. Inside Studio's\n * Fast Frame / embed / share iframes it is driven over postMessage\n * (`grade:set-motion`), so the toggle reaches into the sandbox where the\n * ThreeScene surfaces actually run. A matching `[data-motion=\"off\"]` CSS\n * reset in `styles/globals.css` covers pure-CSS animation/transition.\n *\n * Sibling to lib/demo (the scripted-reveal spine).\n */\n\nimport * as React from \"react\";\n\n/** The attribute stamped on `<html>` to force motion off. */\nexport const MOTION_ATTR = \"data-motion\";\nconst MOTION_OFF = \"off\";\n\nconst REDUCE_QUERY = \"(prefers-reduced-motion: reduce)\";\n\n/** Read the current effective reduced-motion state (OS query OR the global\n * toggle). SSR-safe: returns `false` when there's no `window`. */\nfunction readReduced(): boolean {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n return false;\n }\n const osReduced = window.matchMedia(REDUCE_QUERY).matches;\n const toggledOff =\n document.documentElement.getAttribute(MOTION_ATTR) === MOTION_OFF;\n return osReduced || toggledOff;\n}\n\n/**\n * Returns `true` when motion should be suppressed — either the OS reports\n * `prefers-reduced-motion: reduce`, or the global `data-motion=\"off\"` toggle\n * is set on `<html>`. Stays live: re-reads on media-query change and on the\n * attribute mutating.\n *\n * SSR-safe — defaults to `false` (motion allowed) on the server and\n * rehydrates in an effect, so it never causes a hydration mismatch.\n */\nexport function useReducedMotion(): boolean {\n const [reduced, setReduced] = React.useState(false);\n\n React.useEffect(() => {\n const update = () => setReduced(readReduced());\n update();\n\n const mql = window.matchMedia(REDUCE_QUERY);\n mql.addEventListener(\"change\", update);\n\n // Watch the toggle attribute on <html>.\n const observer = new MutationObserver(update);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [MOTION_ATTR],\n });\n\n return () => {\n mql.removeEventListener(\"change\", update);\n observer.disconnect();\n };\n }, []);\n\n return reduced;\n}\n\n/**\n * @deprecated Prefer {@link useReducedMotion}. Kept for back-compat with the\n * components that import it from `media-surface`; it now also folds in the\n * global `data-motion=\"off\"` toggle, not just the OS query.\n */\nexport const usePrefersReducedMotion = useReducedMotion;\n\n/**\n * Returns `true` when the page is actually being watched — the tab is visible\n * AND (for a top-level document) the window is focused. Inside an iframe the\n * focus check is skipped, because an iframe rarely \"has focus\" even when its\n * tab is frontmost; it falls back to visibility, which correctly tracks the\n * top tab. Use it to PAUSE autoplay loops when nobody's looking: a movie stops\n * when you tab away.\n *\n * SSR-safe — defaults to `true` and rehydrates in an effect.\n *\n * Scope, deliberately: this knows about tab visibility + window focus, NOT\n * whether the element is scrolled into view (pair it with an\n * IntersectionObserver — e.g. motion's `useInView` — for that), and NOT\n * whether a cross-document iframe has scrolled off its PARENT's viewport. An\n * iframe can't see the parent's scroll; the parent must observe the host and\n * pause it. See the grid poster/promote policy in STUDIO-CAPTURE.md.\n */\nexport function usePageActive(): boolean {\n const [active, setActive] = React.useState(true);\n\n React.useEffect(() => {\n // window.top identity check is safe cross-origin (no property access).\n const framed = window.self !== window.top;\n const compute = () =>\n document.visibilityState !== \"hidden\" && (framed || document.hasFocus());\n const update = () => setActive(compute());\n update();\n\n document.addEventListener(\"visibilitychange\", update);\n window.addEventListener(\"focus\", update);\n window.addEventListener(\"blur\", update);\n window.addEventListener(\"pageshow\", update);\n\n return () => {\n document.removeEventListener(\"visibilitychange\", update);\n window.removeEventListener(\"focus\", update);\n window.removeEventListener(\"blur\", update);\n window.removeEventListener(\"pageshow\", update);\n };\n }, []);\n\n return active;\n}\n\n/**\n * Imperatively set the global motion toggle on `<html>`.\n *\n * setMotion(false) → stamps `data-motion=\"off\"` (animation suppressed)\n * setMotion(true) → removes the attribute (default: respect the OS only)\n *\n * Reduce-only: turning motion \"on\" never forces animation for a viewer whose\n * OS asks for reduced motion — `useReducedMotion()` still honours the query.\n * No-op on the server.\n */\nexport function setMotion(enabled: boolean): void {\n if (typeof document === \"undefined\") return;\n const root = document.documentElement;\n if (enabled) root.removeAttribute(MOTION_ATTR);\n else root.setAttribute(MOTION_ATTR, MOTION_OFF);\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { useInView } from \"motion/react\";\nimport {\n DEMO_IN_VIEW_AMOUNT,\n DEMO_SPEED_PRESETS,\n type DemoSpeed,\n type DemoTrigger,\n} from \"./types\";\nimport { isAbortError, sleep } from \"./sleep\";\nimport { useReducedMotion, usePageActive } from \"../motion\";\n\n/**\n * useScriptedDemo — the shared step-machine hook behind every\n * scripted-demo surface in gradeui (`<Code>`, `<Composer>`,\n * `<DemoStage>`, anything else that wants the same play / stop / speed\n * semantics).\n *\n * Generic over the Step type. Each consumer defines its own verbs\n * (Code has `output`, Composer has `mention`, DemoStage has `reveal`)\n * and passes an `interpret(step, ctx)` callback that executes one\n * step. The hook owns: sequencing, cancellation, trigger detection\n * (mount / inView / manual), loop, pre-delay, completion signal, and\n * the imperative play() / stop() API.\n *\n * Reduced motion: this hook is the declarative-motion layer's accessibility\n * boundary. When the OS reports `prefers-reduced-motion: reduce` OR the\n * global `data-motion=\"off\"` toggle is set (both via `useReducedMotion`),\n * the runner settles on the FINAL frame instead of animating — zeroed\n * timings run every step instantly and the sequence never loops, so the\n * end state shows and holds. That keeps lib/demo honouring the same motion\n * control as ThreeScene and the CSS reset; without it, a screen built with\n * <Code> would keep typing under reduced motion and would not be accessible.\n *\n * Authoring guide for `interpret`:\n * - `await sleep(ms, ctx.signal)` for any pause so stop() can short\n * a long wait cleanly.\n * - `await typeText(text, onTick, stagger, ctx.signal)` for typing\n * loops, again so stop() interrupts mid-character. A non-positive\n * stagger (what the reduced-motion preset supplies) emits the whole\n * string in one tick, so the final state shows with no animation.\n * - Read `ctx.speed` to grab the resolved DEMO_SPEED_PRESETS entry\n * when a step doesn't pin its own cadence.\n * - Throw nothing on cancel — the helpers do it for you. Anything\n * else thrown is treated as a real bug and bubbles to the console.\n */\n\nexport interface ScriptedDemoContext {\n /** Resolved speed preset for the current run. Zeroed under reduced motion. */\n speed: (typeof DEMO_SPEED_PRESETS)[DemoSpeed];\n /** AbortSignal that fires on stop() / unmount / steps change. */\n signal: AbortSignal;\n /** Live cancellation check for code paths that can't take a signal. */\n cancelled: () => boolean;\n /** True when the run is in reduced-motion mode (settling, not animating).\n * Most interpreters don't need this — zeroed timings handle it — but a\n * step that does something non-timing-based (confetti, sound) can skip it. */\n reduced: boolean;\n}\n\nexport interface UseScriptedDemoOptions<TStep> {\n /** The steps to run. Undefined or empty means \"no script\". */\n steps?: TStep[];\n /**\n * Per-step interpreter. Receives one step + a context with timing\n * helpers, runs whatever the step means, and returns when done.\n * Synchronous or async — the runner awaits either.\n */\n interpret: (step: TStep, ctx: ScriptedDemoContext) => Promise<void> | void;\n /** Animation feel. Defaults to `normal`. */\n speed?: DemoSpeed;\n /** What kicks the script off. Defaults to `mount`. */\n trigger?: DemoTrigger;\n /** For `trigger=\"manual\"` — flip true to play. */\n play?: boolean;\n /** Loop the sequence forever after completion. Pause length controlled by `loopDelay`. */\n loop?: boolean;\n /** Cap the number of loop cycles, then settle and stop. A demo is a movie —\n * it shouldn't spin forever. Default Infinity. Grid/embed surfaces set a\n * small number so the loop ends instead of running unattended. */\n maxLoops?: number;\n /**\n * Milliseconds to pause between loop cycles. Only applies when\n * `loop` is true. Defaults to 2000. Marketing surfaces that want\n * the demo to breathe between repeats bump this to 4000-6000; tight\n * inline demos drop it to 800.\n */\n loopDelay?: number;\n /** Container ref for inView detection. Required when `trigger=\"inView\"`. */\n containerRef?: React.RefObject<HTMLElement | null>;\n /**\n * Fires when one loop iteration completes (or the whole run, when\n * `loop` is false). Consumers can use this to reset their buffer or\n * fire a parent callback.\n */\n onComplete?: () => void;\n /**\n * Fires before each loop iteration starts (after the 2s pause).\n * Use to reset per-iteration state in the consumer.\n */\n onLoopReset?: () => void;\n}\n\nexport interface ScriptedDemoState {\n /** True while the runner is actively walking steps. */\n isPlaying: boolean;\n /** True after the last step has run (and stays true until reset). */\n isComplete: boolean;\n /** 0-indexed pointer to the currently executing step, or -1 idle. */\n currentIndex: number;\n /** Imperative trigger. Mostly for `trigger=\"manual\"` ref handles. */\n play: () => void;\n /** Cancel the in-flight script. Idempotent. */\n stop: () => void;\n /**\n * One-shot replay. Cancels any in-flight run, then re-plays from\n * step 0. Pass a delay in ms to schedule the replay (useful for\n * \"play, finish, breathe, play once more\" cadences without the\n * commitment of `loop`).\n */\n restart: (delayMs?: number) => void;\n}\n\nconst DEFAULT_LOOP_DELAY_MS = 2000;\n\n// Reduced-motion timings — everything instant. `typeText` treats a\n// non-positive stagger as \"emit the whole string in one tick\", so steps\n// complete without the typing animation (or the flicker a 0ms loop causes)\n// and the surface lands on its final frame.\nconst INSTANT_PRESET: (typeof DEMO_SPEED_PRESETS)[DemoSpeed] = {\n tokenStagger: 0,\n lineStagger: 0,\n preDelay: 0,\n fadeMs: 0,\n};\n\nexport function useScriptedDemo<TStep>(\n opts: UseScriptedDemoOptions<TStep>,\n): ScriptedDemoState {\n const {\n steps,\n interpret,\n speed = \"normal\",\n trigger = \"mount\",\n play: playProp,\n loop = false,\n loopDelay = DEFAULT_LOOP_DELAY_MS,\n maxLoops = Infinity,\n containerRef,\n onComplete,\n onLoopReset,\n } = opts;\n\n // Reduced motion (OS preference OR the global data-motion toggle): settle\n // on the final frame instead of animating. Zeroed timings + no loop.\n const reduced = useReducedMotion();\n const preset = reduced ? INSTANT_PRESET : DEMO_SPEED_PRESETS[speed];\n const effectiveLoop = reduced ? false : loop;\n\n // Playback gate part 1 of 2: is the page even being watched? Pauses every\n // loop when the tab is hidden/unfocused — a movie stops when you tab away.\n // (Part 2, element-in-view, is wired below once we have the container ref.)\n const pageActive = usePageActive();\n\n const [isPlaying, setIsPlaying] = React.useState(false);\n const [isComplete, setIsComplete] = React.useState(false);\n const [currentIndex, setCurrentIndex] = React.useState(-1);\n\n // inView wiring — only active when trigger=\"inView\" AND a container\n // ref was supplied. amount + once mirror the marketing-hero contract.\n // `useInView` accepts a nullable ref so we always pass the ref, but\n // it only fires when actually observable.\n // motion's typing requires a non-null element ref. We assert via cast\n // because passing { current: null } is legal at runtime and behaves\n // as \"never in view\".\n const noopRef = React.useRef<HTMLElement>(null);\n const inView = useInView(\n (containerRef ?? noopRef) as React.RefObject<Element>,\n {\n once: true,\n amount: DEMO_IN_VIEW_AMOUNT,\n },\n );\n\n // Playback gate part 2 of 2: a LIVE in-view (re-fires on leave, unlike the\n // once-only trigger above). Only meaningful when a container ref is being\n // observed; with no ref we can't see the element, so we don't gate on it\n // (tab visibility still applies). Together: pause when nobody's watching.\n const liveInView = useInView(\n (containerRef ?? noopRef) as React.RefObject<Element>,\n { amount: DEMO_IN_VIEW_AMOUNT },\n );\n const playbackActive = pageActive && (containerRef ? liveInView : true);\n\n // Manual play trigger — a counter we bump from the imperative play()\n // method. The runner effect depends on it so a second play() call\n // (after stop() or after completion) cleanly re-runs the script.\n const [manualPlayTick, setManualPlayTick] = React.useState(0);\n\n const shouldPlay = React.useMemo(() => {\n if (trigger === \"mount\") return true;\n if (trigger === \"inView\") return inView;\n // manual: explicit prop OR imperative play() call (tick > 0)\n return Boolean(playProp) || manualPlayTick > 0;\n }, [trigger, inView, playProp, manualPlayTick]);\n\n // Stable interpret ref so the runner effect doesn't restart on every\n // render just because the consumer passes an inline function.\n const interpretRef = React.useRef(interpret);\n interpretRef.current = interpret;\n\n const completeRef = React.useRef(onComplete);\n completeRef.current = onComplete;\n const loopResetRef = React.useRef(onLoopReset);\n loopResetRef.current = onLoopReset;\n\n // Active AbortController so stop() can fire from outside the effect.\n const controllerRef = React.useRef<AbortController | null>(null);\n // Pending restart timeout id so a second restart() call supersedes\n // the first (and unmount cleans up before it fires).\n const restartTimerRef = React.useRef<number | null>(null);\n\n const stop = React.useCallback(() => {\n controllerRef.current?.abort();\n controllerRef.current = null;\n if (restartTimerRef.current !== null) {\n window.clearTimeout(restartTimerRef.current);\n restartTimerRef.current = null;\n }\n setIsPlaying(false);\n }, []);\n\n const play = React.useCallback(() => {\n // For mount/inView triggers, calling play() manually still works\n // (treated as a manual re-run): we bump the tick and rely on the\n // effect dependency to restart.\n setManualPlayTick((n) => n + 1);\n }, []);\n\n const restart = React.useCallback((delayMs = 0) => {\n // Cancel any in-flight run and any pending restart so the new\n // call is the source of truth.\n controllerRef.current?.abort();\n controllerRef.current = null;\n if (restartTimerRef.current !== null) {\n window.clearTimeout(restartTimerRef.current);\n restartTimerRef.current = null;\n }\n if (delayMs > 0) {\n restartTimerRef.current = window.setTimeout(() => {\n restartTimerRef.current = null;\n setManualPlayTick((n) => n + 1);\n }, delayMs);\n return;\n }\n setManualPlayTick((n) => n + 1);\n }, []);\n\n // Unmount cleanup for any orphaned restart timeout.\n React.useEffect(() => {\n return () => {\n if (restartTimerRef.current !== null) {\n window.clearTimeout(restartTimerRef.current);\n restartTimerRef.current = null;\n }\n };\n }, []);\n\n React.useEffect(() => {\n if (!steps || steps.length === 0) return;\n // Under reduced motion, run once to settle on the final frame even if\n // the normal trigger (inView / manual) hasn't fired — the content\n // should be present, just not animated.\n if (!reduced && (!shouldPlay || !playbackActive)) return;\n\n const controller = new AbortController();\n controllerRef.current = controller;\n const { signal } = controller;\n const cancelled = () => signal.aborted;\n\n const ctx: ScriptedDemoContext = { speed: preset, signal, cancelled, reduced };\n\n let active = true;\n let cycles = 0;\n\n const run = async () => {\n do {\n setIsPlaying(true);\n setIsComplete(false);\n setCurrentIndex(-1);\n\n try {\n // Initial pre-delay so the static state is visible for a\n // beat before the first step fires. Mostly matters for\n // `trigger=\"inView\"` (the eye needs an anchor) but it's\n // cheap to apply universally. Zero under reduced motion.\n await sleep(trigger === \"inView\" ? preset.preDelay : 0, signal);\n\n for (let i = 0; i < steps.length; i++) {\n if (signal.aborted) return;\n setCurrentIndex(i);\n await interpretRef.current(steps[i], ctx);\n }\n\n if (signal.aborted) return;\n setCurrentIndex(-1);\n setIsComplete(true);\n completeRef.current?.();\n\n cycles += 1;\n // Stop when not looping, or once we've hit the loop cap — settle on\n // the final frame and don't spin unattended.\n if (!effectiveLoop || cycles >= maxLoops) {\n setIsPlaying(false);\n return;\n }\n\n // Loop: pause (`loopDelay`, default 2000ms) then reset and replay.\n await sleep(loopDelay, signal);\n if (signal.aborted) return;\n loopResetRef.current?.();\n } catch (err) {\n if (isAbortError(err)) return;\n // Re-throw real errors so authoring bugs surface in the\n // console rather than being swallowed silently.\n if (process.env.NODE_ENV !== \"production\") {\n // eslint-disable-next-line no-console\n console.error(\"[useScriptedDemo] step error:\", err);\n }\n setIsPlaying(false);\n return;\n }\n } while (active && effectiveLoop && !signal.aborted);\n };\n\n void run();\n\n return () => {\n active = false;\n controller.abort();\n if (controllerRef.current === controller) controllerRef.current = null;\n };\n // `interpret` is intentionally NOT in the dep list — we read it\n // through interpretRef so the effect doesn't restart on every\n // render. Same for the completion callbacks. `reduced` IS a dep so\n // toggling motion re-runs (settles instantly when turned off,\n // re-animates when turned on).\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n steps,\n shouldPlay,\n playbackActive,\n effectiveLoop,\n loopDelay,\n preset,\n trigger,\n manualPlayTick,\n reduced,\n ]);\n\n return { isPlaying, isComplete, currentIndex, play, stop, restart };\n}\n","import { type ClassValue, clsx } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport {\n LexicalComposer as LexicalRoot,\n type InitialConfigType,\n} from \"@lexical/react/LexicalComposer\";\nimport { RichTextPlugin } from \"@lexical/react/LexicalRichTextPlugin\";\nimport { PlainTextPlugin } from \"@lexical/react/LexicalPlainTextPlugin\";\nimport { ContentEditable } from \"@lexical/react/LexicalContentEditable\";\nimport { LexicalErrorBoundary } from \"@lexical/react/LexicalErrorBoundary\";\nimport { HistoryPlugin } from \"@lexical/react/LexicalHistoryPlugin\";\nimport { OnChangePlugin } from \"@lexical/react/LexicalOnChangePlugin\";\nimport { ListPlugin } from \"@lexical/react/LexicalListPlugin\";\nimport { LinkPlugin } from \"@lexical/react/LexicalLinkPlugin\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport {\n HeadingNode,\n QuoteNode,\n $createHeadingNode,\n $createQuoteNode,\n type HeadingTagType,\n} from \"@lexical/rich-text\";\nimport {\n ListNode,\n ListItemNode,\n INSERT_ORDERED_LIST_COMMAND,\n INSERT_UNORDERED_LIST_COMMAND,\n} from \"@lexical/list\";\nimport { LinkNode, AutoLinkNode } from \"@lexical/link\";\nimport { CodeNode, CodeHighlightNode } from \"@lexical/code\";\nimport { $setBlocksType } from \"@lexical/selection\";\nimport { mergeRegister } from \"@lexical/utils\";\nimport {\n $getRoot,\n $getSelection,\n $createParagraphNode,\n $createTextNode,\n $isRangeSelection,\n FORMAT_TEXT_COMMAND,\n KEY_ENTER_COMMAND,\n CLEAR_EDITOR_COMMAND,\n COMMAND_PRIORITY_HIGH,\n type TextFormatType,\n type LexicalEditor,\n type LexicalNode,\n type TextNode,\n} from \"lexical\";\nimport {\n BeautifulMentionsPlugin,\n BeautifulMentionNode,\n $createBeautifulMentionNode,\n type BeautifulMentionsItem,\n} from \"lexical-beautiful-mentions\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport {\n Bold,\n Italic,\n Underline,\n Strikethrough,\n Code as CodeIcon,\n Heading1,\n Heading2,\n Heading3,\n Quote,\n List as ListIcon,\n ListOrdered,\n Paperclip,\n Send,\n Square,\n X,\n} from \"lucide-react\";\nimport {\n useScriptedDemo,\n BlinkingCursor,\n sleep,\n typeText,\n type DemoSpeed,\n type DemoTrigger,\n} from \"@/lib/demo\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Composer — the generic text composition surface for the design system.\n *\n * The answer wherever a user is composing a message: AI chat input,\n * comment thread reply, post-body editor, future copilot panels.\n * Replaces the textarea-with-buttons pattern that hosts kept rolling\n * by hand.\n *\n * Built on Lexical (Meta's React-first editor framework) so it can do:\n * - rich text formatting (B / I / U / S / code, headings, blockquote,\n * pullquote, lists)\n * - mentions and slash commands via lexical-beautiful-mentions, with\n * a typeahead popover and theme-able tokens\n * - image attachments (paperclip + clipboard paste) when opted in\n * - scripted demo playback for marketing surfaces (types text, opens\n * mention popovers, applies formatting, all via the same step\n * vocabulary as <Code>)\n *\n * Slot-based composition for the action row: hosts that need custom\n * affordances (template picker, voice button, attach-document) pass\n * `leftActions` / `rightActions`. Default Send / Stop / paperclip\n * render only when the host hasn't replaced the slot.\n *\n * Hosts that want the canned \"chat composer with paperclip + send\"\n * preset should reach for `<AIChatComposer>` instead, which configures\n * this Composer with the right slots wired up.\n *\n * Scripted demos: same `speed` / `trigger` / `play` / `loop` vocabulary\n * as `<Code>`, sharing the underlying `useScriptedDemo` hook from\n * `lib/demo/`. The Composer adds its own verbs (`mention`, `format`,\n * `select`, `submit`) on top of the universal `type` / `wait` / `clear`.\n */\n\n// ─── Public types ────────────────────────────────────────────────────\n\nexport type ComposerFormat =\n | \"bold\"\n | \"italic\"\n | \"underline\"\n | \"strikethrough\"\n | \"code\"\n | \"h1\"\n | \"h2\"\n | \"h3\"\n | \"blockquote\"\n | \"pullquote\"\n | \"ul\"\n | \"ol\";\n\nexport interface ComposerMentionItem {\n id: string;\n /** Display value (without the trigger char). */\n value: string;\n /** Optional secondary label shown in the suggester. */\n label?: string;\n /** Avatar URL or initials for the suggester row. */\n avatar?: string;\n /** Arbitrary payload the host can attach to the mention. */\n data?: Record<string, unknown>;\n}\n\nexport interface ComposerTriggerConfig {\n /** The trigger character, eg. \"@\" or \"/\". */\n char: string;\n /**\n * Items to populate the suggester. Either a static array or a\n * resolver function (sync or async) that receives the typed query.\n * The plugin filters automatically when items is an array.\n */\n items:\n | ComposerMentionItem[]\n | ((query: string) => ComposerMentionItem[] | Promise<ComposerMentionItem[]>);\n /**\n * Whether to strip the trigger char on insert. Defaults: keep for\n * \"@\" (mentions read as \"@alice\"), strip for \"/\" (commands read as\n * \"Insert image\" not \"/insert-image\").\n */\n stripTrigger?: boolean;\n}\n\nexport interface ComposerAttachmentConfig {\n /** Master enable. Set true on the prop to use defaults, or pass a config object. */\n enabled?: boolean;\n /** HTML accept attribute on the file input. Default \"image/*\". */\n accept?: string;\n /** Max number of attachments. Default 10. */\n maxItems?: number;\n /** Allow multiple selection in the file picker. Default true. */\n multiple?: boolean;\n}\n\nexport interface ComposerAttachment {\n id: string;\n file: File;\n /** Object URL owned by the composer. Hosts must NOT revoke it. */\n previewUrl: string;\n name: string;\n}\n\nexport interface ComposerContent {\n /** Plain text representation of the editor contents (whitespace preserved). */\n text: string;\n /** Lexical editor state serialised to JSON (for round-trip persistence). */\n json: string;\n /** Resolved mention tokens in document order. */\n mentions: Array<{ trigger: string; value: string; data?: Record<string, unknown> }>;\n}\n\nexport interface ComposerHandle {\n /** Run a demo script imperatively (vs. via `steps` + `trigger=\"manual\"`). */\n play: (steps: ComposerStep[]) => void;\n /** Cancel an in-flight demo. Idempotent. */\n stop: () => void;\n /**\n * One-shot replay of the configured `steps`. Cancels any in-flight\n * run, clears the editor, replays from step 0. Pass a delay (ms) to\n * schedule the replay (useful for chaining demos). Requires `steps`\n * to be configured.\n */\n restart: (delayMs?: number) => void;\n /** Move focus into the editor. */\n focus: () => void;\n /** Wipe the editor. */\n clear: () => void;\n /** Insert plain text at the current selection. */\n insert: (text: string) => void;\n /** Snapshot the current content + mentions. */\n getContent: () => ComposerContent;\n /** Direct access to the underlying Lexical editor (escape hatch). */\n getEditor: () => LexicalEditor | null;\n}\n\n/**\n * Demo step vocabulary for Composer scripts. Shares `type` / `wait` /\n * `clear` with the universal `lib/demo` verbs; adds composer-specific\n * `mention`, `format`, `select`, `newline`, `submit` on top.\n */\nexport type ComposerStep =\n | { type: \"type\"; text: string; speed?: DemoSpeed }\n | { type: \"wait\"; ms: number }\n | { type: \"clear\" }\n | { type: \"newline\" }\n | { type: \"submit\" }\n | {\n type: \"mention\";\n /** Trigger char (must match a registered ComposerTriggerConfig.char). */\n trigger: string;\n /** Value to insert (without the trigger). Looks up the matching item by `value`. */\n value: string;\n /** Optional pre-typed query — the demo types this after the trigger char before \"selecting\" the value, to show the typeahead in action. */\n query?: string;\n }\n | { type: \"format\"; format: ComposerFormat }\n | {\n type: \"select\";\n /** Substring to find and select. First match wins. */\n text: string;\n };\n\nexport interface ComposerProps {\n /** Placeholder copy shown when empty. */\n placeholder?: string;\n /** Initial plain text content. For richer initial state, use `initialJson`. */\n initialText?: string;\n /** Initial Lexical state JSON (from a previous `onSubmit` round-trip). */\n initialJson?: string;\n /**\n * Available formats. Pass false to disable rich text entirely\n * (plain text mode, half the bundle weight, no toolbar). Default\n * enables a sensible set for most chat / comment surfaces.\n */\n formats?: ComposerFormat[] | false;\n /**\n * Render the formatting toolbar. Default false because most uses\n * are short-form. Set true (or \"top\") to show the toolbar above the\n * editor. \"floating\" is planned; not yet implemented.\n */\n toolbar?: boolean | \"top\";\n /**\n * Mention / slash command configs. Each entry registers one trigger\n * char and its items. Pass `[{ char: \"@\", items: people }, { char: \"/\", items: commands }]`\n * for the common chat-app setup.\n */\n triggers?: ComposerTriggerConfig[];\n /**\n * Image attachments (paperclip + clipboard paste). Pass true for\n * defaults, an object to customise, or omit/false to skip the\n * attachment plumbing entirely.\n */\n attachments?: boolean | ComposerAttachmentConfig;\n /** Fires when the user submits (Enter, click Send, or scripted `submit` step). */\n onSubmit?: (content: ComposerContent, attachments?: ComposerAttachment[]) => void;\n /**\n * Fires on every editor change with the current plain text. Use for\n * length validation, controlled-value bridges (eg. AIChatComposer\n * forwarding to a host's `value`/`onChange` pair), or live preview\n * surfaces. Cheap, called frequently; debounce if you need the\n * Lexical state JSON (use `getContent()` via ref instead).\n */\n onChange?: (text: string) => void;\n /**\n * Loading state — disables the editor + paperclip and swaps the\n * default Send button for Stop. Has no effect when `rightActions`\n * overrides the default Send.\n */\n isLoading?: boolean;\n /** Stop handler — required for Stop to be active when loading. */\n onStop?: () => void;\n /** Hard character cap. */\n maxLength?: number;\n /** Auto-focus the editor on mount. Default false. */\n autoFocus?: boolean;\n /** Whether Enter submits. Default true (Shift-Enter still inserts a newline). */\n submitOnEnter?: boolean;\n /**\n * Custom content for the left action slot. Replaces the default\n * paperclip button when `attachments` is enabled.\n */\n leftActions?: React.ReactNode;\n /**\n * Custom content for the right action slot. Replaces the default\n * Send / Stop button. Use the `useComposer()` hook inside to access\n * imperative methods.\n */\n rightActions?: React.ReactNode;\n /** Hide the default Send button without replacing it. */\n hideSend?: boolean;\n /** Scripted demo steps. */\n steps?: ComposerStep[];\n /** What kicks the script off. Defaults to \"mount\". */\n trigger?: DemoTrigger;\n /** For trigger=\"manual\" — flip true to play. */\n play?: boolean;\n /** Animation feel. */\n speed?: DemoSpeed;\n /** Loop the script forever. */\n loop?: boolean;\n /**\n * Pause between loop iterations (ms). Defaults to 2000. Marketing\n * heroes that want the demo to breathe between repeats bump this\n * higher; tight inline demos drop it.\n */\n loopDelay?: number;\n /**\n * Fires once per loop cycle, AFTER the loopDelay pause and BEFORE\n * the script replays. Use to reset parent state that the script\n * mutated via onSubmit (e.g., wipe a messages list back to its\n * seed before the demo types into it again). The editor is cleared\n * automatically — you only need this hook if state outside the\n * Composer needs to reset too.\n */\n onLoopReset?: () => void;\n /**\n * Bare mode — strip the card chrome (border / bg / rounding). Use\n * when embedding inside an existing card or column layout.\n */\n bare?: boolean;\n /**\n * Read-only mode — disables editing AND focusability. Programmatic\n * updates (including scripted demo playback) still work. Use for\n * marketing surfaces that render a Composer purely for show, so the\n * scripted typing doesn't steal focus from other inputs on the page.\n * Hides the default Send / paperclip action row.\n */\n readOnly?: boolean;\n className?: string;\n}\n\n// ─── Plugins (internal) ──────────────────────────────────────────────\n\n/**\n * SubmitPlugin — wires Enter to submit (Shift-Enter still inserts a\n * newline). Captures the editor command before the rich-text plugin\n * inserts a paragraph break.\n */\nfunction SubmitPlugin({\n onSubmit,\n enabled,\n}: {\n onSubmit: () => void;\n enabled: boolean;\n}) {\n const [editor] = useLexicalComposerContext();\n\n React.useEffect(() => {\n if (!enabled) return;\n return editor.registerCommand(\n KEY_ENTER_COMMAND,\n (event) => {\n if (event && (event as KeyboardEvent).shiftKey) return false;\n event?.preventDefault();\n onSubmit();\n return true;\n },\n COMMAND_PRIORITY_HIGH,\n );\n }, [editor, enabled, onSubmit]);\n\n return null;\n}\n\n/**\n * PastePlugin — intercepts clipboard image pastes when attachments\n * are enabled, routes them to the attachment intake instead of\n * letting Lexical try to insert them as nodes.\n */\nfunction PastePlugin({\n onImageFiles,\n enabled,\n}: {\n onImageFiles: (files: File[]) => void;\n enabled: boolean;\n}) {\n const [editor] = useLexicalComposerContext();\n\n React.useEffect(() => {\n if (!enabled) return;\n const rootElement = editor.getRootElement();\n if (!rootElement) return;\n const handler = (e: ClipboardEvent) => {\n const items = Array.from(e.clipboardData?.items ?? []);\n const imageFiles = items\n .filter((it) => it.kind === \"file\" && it.type.startsWith(\"image/\"))\n .map((it) => it.getAsFile())\n .filter((f): f is File => f !== null);\n if (imageFiles.length > 0) {\n e.preventDefault();\n onImageFiles(imageFiles);\n }\n };\n rootElement.addEventListener(\"paste\", handler);\n return () => rootElement.removeEventListener(\"paste\", handler);\n }, [editor, enabled, onImageFiles]);\n\n return null;\n}\n\n/**\n * AutoFocusPlugin — focus the editor on mount when `autoFocus` is on.\n */\nfunction AutoFocusPlugin({ enabled }: { enabled: boolean }) {\n const [editor] = useLexicalComposerContext();\n React.useEffect(() => {\n if (!enabled) return;\n editor.focus();\n }, [editor, enabled]);\n return null;\n}\n\n/**\n * RefBridgePlugin — exposes the LexicalEditor instance up to the\n * outer forwardRef so ComposerHandle methods can drive it.\n */\nfunction RefBridgePlugin({\n onEditor,\n}: {\n onEditor: (editor: LexicalEditor) => void;\n}) {\n const [editor] = useLexicalComposerContext();\n React.useEffect(() => {\n onEditor(editor);\n }, [editor, onEditor]);\n return null;\n}\n\n// ─── Demo step interpreter ───────────────────────────────────────────\n\nconst FORMAT_TEXT_KEYS: Partial<Record<ComposerFormat, TextFormatType>> = {\n bold: \"bold\",\n italic: \"italic\",\n underline: \"underline\",\n strikethrough: \"strikethrough\",\n code: \"code\",\n};\n\n/**\n * Apply a block-level format to the current selection. Splits text\n * vs. block formats internally.\n */\nfunction applyFormat(editor: LexicalEditor, format: ComposerFormat) {\n const textKey = FORMAT_TEXT_KEYS[format];\n if (textKey) {\n editor.dispatchCommand(FORMAT_TEXT_COMMAND, textKey);\n return;\n }\n if (format === \"ul\") {\n editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);\n return;\n }\n if (format === \"ol\") {\n editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);\n return;\n }\n // Block-level conversions go through $setBlocksType inside an update.\n editor.update(() => {\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) return;\n if (format === \"h1\" || format === \"h2\" || format === \"h3\") {\n const tag = format as HeadingTagType;\n $setBlocksType(selection, () => $createHeadingNode(tag));\n return;\n }\n if (format === \"blockquote\" || format === \"pullquote\") {\n // Pullquote is a styled variant of blockquote: same node, plus a\n // data attribute the CSS targets. Lexical doesn't ship a custom\n // attribute API per quote, so we tag the DOM via theme below\n // and use a separate $createPullquoteNode if/when this graduates\n // into a custom node type.\n $setBlocksType(selection, () => {\n const node = $createQuoteNode();\n if (format === \"pullquote\") {\n // Best-effort marker so the theme can style it differently.\n // Stored on the element via a getter override later if we\n // need it round-trippable.\n (node as unknown as { __pullquote?: boolean }).__pullquote = true;\n }\n return node;\n });\n }\n });\n}\n\n/**\n * Insert a beautiful-mention node programmatically at the current\n * selection. Used by demo `mention` steps and the imperative\n * ComposerHandle.insert flow.\n */\nfunction insertMentionNode(\n editor: LexicalEditor,\n trigger: string,\n value: string,\n data?: Record<string, unknown>,\n) {\n editor.update(() => {\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) return;\n // BeautifulMentionsItemData restricts to primitives (string | number\n // | boolean | null). Our public `data` is wider (Record<string,\n // unknown>) so callers can pass through richer payloads — but at the\n // node-creation boundary we cast since the plugin will JSON-roundtrip\n // it and anything non-primitive would silently drop anyway.\n const node = $createBeautifulMentionNode(\n trigger,\n value,\n data as Record<string, string | boolean | number | null> | undefined,\n );\n selection.insertNodes([node]);\n // Insert a trailing space so the caret sits ready for the next\n // word rather than glued to the mention pill.\n selection.insertText(\" \");\n });\n}\n\n/**\n * Find a substring in the editor's plain text and set the selection\n * to it. Returns true on success, false if not found.\n */\nfunction selectSubstring(editor: LexicalEditor, needle: string): boolean {\n let found = false;\n editor.update(() => {\n const root = $getRoot();\n const fullText = root.getTextContent();\n const idx = fullText.indexOf(needle);\n if (idx === -1) return;\n // Walk the text nodes to translate idx -> (node, offset).\n let cursor = 0;\n let startNode: TextNode | null = null;\n let startOffset = 0;\n let endNode: TextNode | null = null;\n let endOffset = 0;\n const walk = (node: LexicalNode) => {\n if (startNode && endNode) return;\n if (node.getType() === \"text\") {\n const tn = node as TextNode;\n const len = tn.getTextContentSize();\n const nodeStart = cursor;\n const nodeEnd = cursor + len;\n if (!startNode && idx >= nodeStart && idx < nodeEnd) {\n startNode = tn;\n startOffset = idx - nodeStart;\n }\n const endIdx = idx + needle.length;\n if (!endNode && endIdx > nodeStart && endIdx <= nodeEnd) {\n endNode = tn;\n endOffset = endIdx - nodeStart;\n }\n cursor = nodeEnd;\n return;\n }\n if (\"getChildren\" in node) {\n for (const child of (node as unknown as { getChildren: () => LexicalNode[] }).getChildren()) {\n walk(child);\n }\n } else {\n cursor += node.getTextContentSize();\n }\n };\n walk(root);\n // Cast inside the truthy branch. TS's control-flow analysis of a\n // let-with-`null`-initial mutated inside a closure narrows the\n // truthy-branch type to `never` (closure mutations aren't tracked\n // for flow purposes). The `as TextNode` tells TS we know better —\n // walk() either populated both or returned early.\n if (startNode && endNode) {\n const sn = startNode as TextNode;\n const en = endNode as TextNode;\n const selection = sn.select(startOffset, 0);\n selection.focus.set(en.getKey(), endOffset, \"text\");\n found = true;\n }\n });\n return found;\n}\n\n/**\n * Wipe the editor contents and leave a fresh empty paragraph with\n * a collapsed selection. Used wherever we need to \"reset to empty\" —\n * after submit, on imperative clear(), on loop reset, on restart().\n *\n * Why not `dispatchCommand(CLEAR_EDITOR_COMMAND)`: that command is\n * registered by RichTextPlugin / PlainTextPlugin and short-circuits\n * silently in some editor states (notably readOnly mode, but also\n * observed in some controlled flows). Explicit root mutation always\n * works because it bypasses the command layer entirely.\n */\nfunction clearEditor(editor: LexicalEditor) {\n editor.update(() => {\n const root = $getRoot();\n root.clear();\n const para = $createParagraphNode();\n root.append(para);\n para.select();\n });\n}\n\n/**\n * Snapshot the current editor content into the ComposerContent shape\n * exposed to onSubmit and via ComposerHandle.getContent.\n */\nfunction snapshotContent(editor: LexicalEditor): ComposerContent {\n let text = \"\";\n let json = \"\";\n const mentions: ComposerContent[\"mentions\"] = [];\n editor.getEditorState().read(() => {\n text = $getRoot().getTextContent();\n json = JSON.stringify(editor.getEditorState().toJSON());\n const walk = (node: LexicalNode) => {\n if (node.getType() === \"beautifulMention\") {\n const m = node as unknown as {\n getTrigger: () => string;\n getValue: () => string;\n getData: () => Record<string, unknown> | undefined;\n };\n mentions.push({\n trigger: m.getTrigger(),\n value: m.getValue(),\n data: m.getData(),\n });\n }\n if (\"getChildren\" in node) {\n for (const child of (node as unknown as { getChildren: () => LexicalNode[] }).getChildren()) {\n walk(child);\n }\n }\n };\n walk($getRoot());\n });\n return { text, json, mentions };\n}\n\n// ─── Toolbar ─────────────────────────────────────────────────────────\n\nconst FORMAT_BUTTONS: Array<{\n format: ComposerFormat;\n icon: React.ComponentType<{ className?: string }>;\n label: string;\n}> = [\n { format: \"bold\", icon: Bold, label: \"Bold\" },\n { format: \"italic\", icon: Italic, label: \"Italic\" },\n { format: \"underline\", icon: Underline, label: \"Underline\" },\n { format: \"strikethrough\", icon: Strikethrough, label: \"Strikethrough\" },\n { format: \"code\", icon: CodeIcon, label: \"Inline code\" },\n { format: \"h1\", icon: Heading1, label: \"Heading 1\" },\n { format: \"h2\", icon: Heading2, label: \"Heading 2\" },\n { format: \"h3\", icon: Heading3, label: \"Heading 3\" },\n { format: \"blockquote\", icon: Quote, label: \"Blockquote\" },\n { format: \"ul\", icon: ListIcon, label: \"Bulleted list\" },\n { format: \"ol\", icon: ListOrdered, label: \"Numbered list\" },\n];\n\nfunction ComposerToolbar({ formats }: { formats: ComposerFormat[] }) {\n const [editor] = useLexicalComposerContext();\n const [activeFormats, setActiveFormats] = React.useState<Set<TextFormatType>>(\n () => new Set(),\n );\n\n React.useEffect(() => {\n return mergeRegister(\n editor.registerUpdateListener(({ editorState }) => {\n editorState.read(() => {\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) return;\n const next = new Set<TextFormatType>();\n for (const fmt of [\"bold\", \"italic\", \"underline\", \"strikethrough\", \"code\"] as TextFormatType[]) {\n if (selection.hasFormat(fmt)) next.add(fmt);\n }\n setActiveFormats(next);\n });\n }),\n );\n }, [editor]);\n\n const visible = FORMAT_BUTTONS.filter((b) => formats.includes(b.format));\n\n return (\n <div\n data-gds-part=\"composer-toolbar\"\n className={cn(\n \"flex flex-wrap items-center gap-0.5\",\n // No bottom border — the toolbar sits inside the same card\n // as the editor and the action row. Internal dividers were\n // reading as too many seams; let the surface flow as one.\n \"px-2 py-1\",\n )}\n >\n {visible.map(({ format, icon: Icon, label }) => {\n const textKey = FORMAT_TEXT_KEYS[format];\n const isActive = textKey ? activeFormats.has(textKey) : false;\n return (\n <button\n key={format}\n type=\"button\"\n aria-label={label}\n title={label}\n data-gds-part=\"composer-toolbar-button\"\n data-gds-active={isActive ? \"true\" : \"false\"}\n onClick={() => applyFormat(editor, format)}\n className={cn(\n \"h-7 w-7 inline-flex items-center justify-center rounded\",\n \"text-[var(--gds-composer-toolbar-fg)]\",\n \"hover:bg-[var(--gds-composer-toolbar-hover-bg)]\",\n \"focus:outline-none focus:ring-2 focus:ring-primary\",\n isActive && \"bg-[var(--gds-composer-toolbar-active-bg)] text-[var(--gds-composer-toolbar-active-fg)]\",\n )}\n >\n <Icon className=\"h-3.5 w-3.5\" />\n </button>\n );\n })}\n </div>\n );\n}\n\n// ─── Attachment chip row ─────────────────────────────────────────────\n\nfunction AttachmentChips({\n attachments,\n onRemove,\n}: {\n attachments: ComposerAttachment[];\n onRemove: (id: string) => void;\n}) {\n return (\n <AnimatePresence initial={false}>\n {attachments.length > 0 && (\n <motion.div\n initial={{ opacity: 0, height: 0 }}\n animate={{ opacity: 1, height: \"auto\" }}\n exit={{ opacity: 0, height: 0 }}\n transition={{ duration: 0.18 }}\n className=\"overflow-hidden\"\n >\n <div\n data-gds-part=\"composer-attachments\"\n className=\"flex flex-wrap gap-2 px-2 py-2\"\n >\n {attachments.map((att) => (\n <div key={att.id} className=\"relative group\">\n {att.file.type.startsWith(\"image/\") ? (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={att.previewUrl}\n alt={att.name}\n className=\"h-14 w-14 rounded-md object-cover border border-[var(--gds-composer-border)]\"\n />\n ) : (\n <div className=\"h-14 w-14 rounded-md border border-[var(--gds-composer-border)] flex items-center justify-center text-xs px-1 text-center text-[var(--gds-composer-muted-fg)]\">\n {att.name.slice(0, 18)}\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => onRemove(att.id)}\n aria-label={`Remove ${att.name}`}\n className={cn(\n \"absolute -top-1.5 -right-1.5 h-5 w-5 rounded-full\",\n \"bg-[var(--gds-composer-chip-remove-bg)]\",\n \"text-[var(--gds-composer-chip-remove-fg)]\",\n \"flex items-center justify-center\",\n \"opacity-0 group-hover:opacity-100 focus:opacity-100\",\n \"focus:outline-none focus:ring-2 focus:ring-primary\",\n \"transition-opacity\",\n )}\n >\n <X className=\"w-3 h-3\" />\n </button>\n </div>\n ))}\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n}\n\n// ─── Inner component (inside LexicalRoot) ────────────────────────────\n\ninterface InnerProps extends Omit<\n ComposerProps,\n \"initialJson\" | \"initialText\" | \"readOnly\" | \"attachments\"\n> {\n readOnly: boolean;\n /**\n * Bridge for the outer forwardRef to surface useScriptedDemo's\n * `restart()` on ComposerHandle. The inner component populates\n * this; the outer reads from it inside useImperativeHandle.\n */\n restartRef: React.MutableRefObject<(delayMs?: number) => void>;\n /** Resolved + normalised. */\n triggers: ComposerTriggerConfig[];\n attachmentsCfg: Required<ComposerAttachmentConfig> | null;\n formatList: ComposerFormat[];\n showToolbar: boolean;\n handleEditorReady: (editor: LexicalEditor) => void;\n /** Used by the demo player to drive submission. */\n submitRef: React.MutableRefObject<() => void>;\n /** Internal attachment state lifted up so the rightActions Send\n * button + the demo's submit step both see it. */\n attachments: ComposerAttachment[];\n setAttachments: React.Dispatch<React.SetStateAction<ComposerAttachment[]>>;\n /** Image intake callback (shared by paperclip + paste). */\n ingestImages: (files: File[]) => void;\n}\n\nfunction ComposerInner({\n placeholder,\n triggers,\n attachmentsCfg,\n formatList,\n showToolbar,\n isLoading,\n onStop,\n maxLength,\n autoFocus,\n submitOnEnter = true,\n leftActions,\n rightActions,\n hideSend,\n steps,\n trigger = \"mount\",\n play,\n speed = \"normal\",\n loop = false,\n loopDelay,\n onLoopReset,\n bare,\n className,\n onSubmit,\n onChange,\n readOnly,\n handleEditorReady,\n submitRef,\n restartRef,\n attachments,\n setAttachments,\n ingestImages,\n}: InnerProps) {\n const [editor] = useLexicalComposerContext();\n const containerRef = React.useRef<HTMLDivElement>(null);\n const fileInputRef = React.useRef<HTMLInputElement>(null);\n\n // Send-button enable state — driven by editor content.\n const [hasContent, setHasContent] = React.useState(false);\n\n React.useEffect(() => {\n handleEditorReady(editor);\n }, [editor, handleEditorReady]);\n\n // Read-only toggle — flips Lexical's editable flag. The contenteditable\n // element becomes non-focusable so a scripted demo running in this\n // composer can't steal focus from other inputs on the page.\n // editor.update() still applies, so demo playback continues to work.\n React.useEffect(() => {\n editor.setEditable(!readOnly);\n }, [editor, readOnly]);\n\n // Stable ref for onChange so the update listener doesn't re-register\n // every render. Read via ref so inline arrow functions don't churn it.\n const onChangeRef = React.useRef(onChange);\n onChangeRef.current = onChange;\n\n React.useEffect(() => {\n return editor.registerUpdateListener(({ editorState }) => {\n editorState.read(() => {\n const text = $getRoot().getTextContent();\n setHasContent(text.trim().length > 0);\n onChangeRef.current?.(text);\n });\n });\n }, [editor]);\n\n // ── Submit handler — shared by Enter, Send button, demo \"submit\" step.\n const handleSubmit = React.useCallback(() => {\n if (isLoading) return;\n const content = snapshotContent(editor);\n const hasText = content.text.trim().length > 0;\n const hasAttachments = attachments.length > 0;\n if (!hasText && !hasAttachments) return;\n onSubmit?.(content, hasAttachments ? attachments : undefined);\n // Reset for the next message. Explicit root.clear() rather than\n // CLEAR_EDITOR_COMMAND so it works reliably in every state\n // (readOnly, controlled-value flows, etc) — the command path\n // short-circuits silently in some configurations.\n clearEditor(editor);\n attachments.forEach((a) => URL.revokeObjectURL(a.previewUrl));\n setAttachments([]);\n }, [editor, isLoading, attachments, onSubmit, setAttachments]);\n\n // Keep submitRef pointing at the latest closure for the demo player.\n submitRef.current = handleSubmit;\n\n // ── Demo player ────────────────────────────────────────────────────\n //\n // The interpret callback runs Lexical updates per step. Typing\n // splits into per-character updates so the user sees the text\n // appear; mention inserts the node directly (we could open the\n // popover UI, but for v1 keep it simple); format dispatches the\n // matching command; submit calls handleSubmit; select walks text\n // nodes to set a range.\n const { restart: demoRestart } = useScriptedDemo<ComposerStep>({\n steps,\n speed,\n trigger,\n play,\n loop,\n loopDelay,\n containerRef,\n onLoopReset: () => {\n clearEditor(editor);\n // Consumer-provided callback fires after the editor clear so\n // any external state (parent messages list, etc.) resets in\n // the same tick before the script replays.\n onLoopReset?.();\n },\n interpret: async (step, ctx) => {\n const signal = ctx.signal;\n if (step.type === \"wait\") return sleep(step.ms, signal);\n if (step.type === \"clear\") {\n clearEditor(editor);\n return sleep(120, signal);\n }\n if (step.type === \"newline\") {\n editor.update(() => {\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) return;\n selection.insertParagraph();\n });\n return sleep(60, signal);\n }\n if (step.type === \"submit\") {\n handleSubmit();\n return sleep(120, signal);\n }\n if (step.type === \"format\") {\n applyFormat(editor, step.format);\n // Collapse the selection to its end so the next type step\n // appends instead of replacing the (still-selected) text.\n // Without this, scripted \"select word → format italic → type\n // more\" sequences would have the new typing wipe out the\n // formatted word. Lexical selection has no direct collapse\n // method, but anchor.set() with the focus's key/offset/type\n // produces a zero-width range at the end of the selection.\n editor.update(() => {\n const sel = $getSelection();\n if (!$isRangeSelection(sel) || sel.isCollapsed()) return;\n const focus = sel.focus;\n sel.anchor.set(focus.key, focus.offset, focus.type);\n });\n return sleep(80, signal);\n }\n if (step.type === \"select\") {\n selectSubstring(editor, step.text);\n return sleep(120, signal);\n }\n if (step.type === \"mention\") {\n // Optional pre-typed query — shows the typeahead in flight.\n if (step.query) {\n editor.update(() => {\n const sel = $getSelection();\n if ($isRangeSelection(sel)) sel.insertText(step.trigger);\n });\n await typeText(\n step.query,\n (partial) => {\n editor.update(() => {\n const root = $getRoot();\n const text = root.getTextContent();\n // Replace the trailing trigger + partial query each tick.\n const head = text.slice(0, text.length - step.trigger.length - (partial.length - 1));\n root.clear();\n const p = $createParagraphNode();\n if (head) p.append($createTextNode(head));\n p.append($createTextNode(step.trigger + partial));\n root.append(p);\n });\n },\n ctx.speed.tokenStagger,\n signal,\n );\n await sleep(180, signal);\n // Now strip the trigger + query and insert the mention.\n editor.update(() => {\n const root = $getRoot();\n const text = root.getTextContent();\n const stripLen = step.trigger.length + step.query!.length;\n const head = text.slice(0, text.length - stripLen);\n root.clear();\n const p = $createParagraphNode();\n if (head) p.append($createTextNode(head));\n root.append(p);\n p.select();\n });\n }\n insertMentionNode(editor, step.trigger, step.value);\n return sleep(120, signal);\n }\n // type — append one char per tick. Lexical wants its own update\n // transaction per insert, so we can't use typeText() directly\n // (that's a setter-style API for components that own a string\n // buffer). Run our own cancellable loop here instead.\n const stagger = step.speed\n ? { slow: 70, normal: 22, fast: 8 }[step.speed]\n : ctx.speed.tokenStagger;\n for (let i = 0; i < step.text.length; i++) {\n if (signal.aborted) return;\n const char = step.text[i];\n editor.update(() => {\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n // No active selection (eg. initial mount before the user\n // focused). Anchor at the end of the doc so the demo can\n // still type.\n $getRoot().selectEnd();\n const sel2 = $getSelection();\n if ($isRangeSelection(sel2)) sel2.insertText(char);\n return;\n }\n selection.insertText(char);\n });\n if (i < step.text.length - 1) await sleep(stagger, signal);\n }\n },\n });\n\n // Bridge useScriptedDemo's restart() up to the outer ComposerHandle.\n // Wrap it so the inner component also wipes the editor before the\n // new run begins (otherwise the replay's first type step would\n // append to the leftover text).\n React.useEffect(() => {\n restartRef.current = (delayMs?: number) => {\n clearEditor(editor);\n demoRestart(delayMs);\n };\n }, [editor, demoRestart, restartRef]);\n\n // ── Action row ─────────────────────────────────────────────────────\n\n // Read-only composers (marketing demos) shouldn't render the action\n // row — there's no user input to send, no attachments to attach.\n const showDefaultSend = !readOnly && !rightActions && !hideSend;\n const showDefaultAttach = !readOnly && !leftActions && attachmentsCfg !== null;\n const showActionRow = !readOnly && (showDefaultAttach || leftActions || showDefaultSend || rightActions);\n\n return (\n <div\n ref={containerRef}\n data-gds-part=\"composer\"\n data-gds-loading={isLoading ? \"true\" : \"false\"}\n className={cn(\n \"w-full\",\n // Match Input's chrome — rounded-md + border-input + shadow-sm\n // + softer focus ring. The Composer reads as a heavier sibling\n // of Input (multi-line, toolbar, attachments) and should sit\n // in the same form rhythm without looking like a different\n // family of control.\n !bare && [\n \"rounded-md\",\n \"bg-transparent\",\n \"border border-input\",\n \"shadow-sm\",\n \"focus-within:outline-none focus-within:ring-1 focus-within:ring-ring\",\n \"transition-colors\",\n ],\n className,\n )}\n >\n {showToolbar && <ComposerToolbar formats={formatList} />}\n\n {attachmentsCfg && (\n <AttachmentChips\n attachments={attachments}\n onRemove={(id) => {\n setAttachments((prev) => {\n const target = prev.find((a) => a.id === id);\n if (target) URL.revokeObjectURL(target.previewUrl);\n return prev.filter((a) => a.id !== id);\n });\n }}\n />\n )}\n\n <div className=\"relative\">\n <RichTextPlugin\n contentEditable={\n <ContentEditable\n data-gds-part=\"composer-editor\"\n className={cn(\n \"outline-none\",\n \"px-3 sm:px-4 py-3\",\n \"text-sm text-[var(--gds-composer-fg)]\",\n \"min-h-[44px] max-h-[300px] overflow-y-auto\",\n \"[&_p]:m-0 [&_p+p]:mt-2\",\n \"[&_h1]:text-xl [&_h1]:font-semibold [&_h1]:mt-2\",\n \"[&_h2]:text-lg [&_h2]:font-semibold [&_h2]:mt-2\",\n \"[&_h3]:text-base [&_h3]:font-semibold [&_h3]:mt-2\",\n \"[&_ul]:list-disc [&_ul]:pl-5 [&_ol]:list-decimal [&_ol]:pl-5\",\n \"[&_blockquote]:border-l-2 [&_blockquote]:border-[var(--gds-composer-border)] [&_blockquote]:pl-3 [&_blockquote]:italic\",\n \"[&_code]:bg-[var(--gds-composer-toolbar-active-bg)] [&_code]:px-1 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-xs [&_code]:font-mono\",\n isLoading && \"opacity-60 pointer-events-none\",\n )}\n aria-placeholder={placeholder ?? \"\"}\n placeholder={\n <div\n data-gds-part=\"composer-placeholder\"\n // Match the standard Input placeholder exactly.\n // (The old `text-[var(--gds-composer-muted-fg)]`\n // emitted a bare OKLCH triplet with no oklch()\n // wrapper — an invalid color — so the placeholder\n // fell back to the dark editor foreground and read\n // like already-typed text.)\n className=\"absolute top-3 left-3 sm:left-4 text-sm text-muted-foreground pointer-events-none select-none\"\n >\n {placeholder}\n </div>\n }\n spellCheck\n />\n }\n ErrorBoundary={LexicalErrorBoundary}\n />\n <HistoryPlugin />\n <ListPlugin />\n <LinkPlugin />\n <OnChangePlugin onChange={() => {}} />\n <AutoFocusPlugin enabled={Boolean(autoFocus)} />\n <SubmitPlugin onSubmit={handleSubmit} enabled={submitOnEnter} />\n {attachmentsCfg && (\n <PastePlugin onImageFiles={ingestImages} enabled={true} />\n )}\n {triggers.length > 0 && (\n <BeautifulMentionsPlugin\n items={triggers.reduce<Record<string, BeautifulMentionsItem[]>>(\n (acc, t) => {\n // Resolver functions are deferred to v2 — for now only\n // static arrays are wired into the plugin. The shape\n // here is intentionally minimal: just `value`. The\n // plugin spreads each item's extra props onto the DOM\n // (which is how it warns \"React does not recognize the\n // `foo` prop\"), so anything richer than primitives\n // needs the plugin's own custom-component slot to\n // render properly. Keep the surface narrow until we\n // need richer items.\n if (typeof t.items === \"function\") return acc;\n acc[t.char] = t.items.map((item) => ({\n value: item.value,\n })) as BeautifulMentionsItem[];\n return acc;\n },\n {},\n )}\n />\n )}\n </div>\n\n {/* Action row — paperclip on the left, send/stop on the right.\n Slots win when supplied; defaults render otherwise. Hidden\n entirely in readOnly mode. */}\n {showActionRow && (\n <div\n data-gds-part=\"composer-actions\"\n className=\"flex items-center justify-between gap-2 px-2 pb-2 pt-1\"\n >\n <div className=\"flex items-center gap-1\">\n {leftActions ?? (\n showDefaultAttach && (\n <button\n type=\"button\"\n onClick={() => fileInputRef.current?.click()}\n disabled={isLoading}\n aria-label=\"Attach image\"\n title=\"Attach image\"\n className={cn(\n \"h-8 w-8 rounded-lg flex items-center justify-center\",\n \"text-[var(--gds-composer-action-fg)]\",\n \"hover:text-[var(--gds-composer-fg)]\",\n \"hover:bg-[var(--gds-composer-toolbar-hover-bg)]\",\n \"focus:outline-none focus:ring-2 focus:ring-primary\",\n \"transition-colors\",\n \"disabled:opacity-50 disabled:cursor-not-allowed\",\n )}\n >\n <Paperclip className=\"w-4 h-4\" />\n </button>\n )\n )}\n </div>\n\n {rightActions ??\n (showDefaultSend && (\n <button\n type=\"button\"\n onClick={isLoading ? onStop : handleSubmit}\n disabled={\n isLoading\n ? !onStop\n : !hasContent && attachments.length === 0\n }\n aria-label={isLoading ? \"Stop\" : \"Send\"}\n className={cn(\n \"h-8 w-8 rounded-lg flex items-center justify-center transition-colors flex-shrink-0\",\n \"focus:outline-none focus:ring-2 focus:ring-primary\",\n isLoading\n ? \"bg-red-500 hover:bg-red-600 text-white disabled:opacity-50\"\n : hasContent || attachments.length > 0\n ? \"bg-primary hover:bg-primary/90 text-primary-foreground\"\n : \"bg-[var(--gds-composer-toolbar-active-bg)] text-[var(--gds-composer-muted-fg)] cursor-not-allowed\",\n )}\n >\n {isLoading ? (\n <Square className=\"w-3.5 h-3.5\" />\n ) : (\n <Send className=\"w-3.5 h-3.5\" />\n )}\n </button>\n ))}\n\n {/* Hidden file input wired to the paperclip. */}\n {attachmentsCfg && (\n <input\n ref={fileInputRef}\n type=\"file\"\n accept={attachmentsCfg.accept}\n multiple={attachmentsCfg.multiple}\n onChange={(e) => {\n if (e.target.files) ingestImages(Array.from(e.target.files));\n e.target.value = \"\";\n }}\n className=\"sr-only\"\n tabIndex={-1}\n aria-hidden=\"true\"\n />\n )}\n </div>\n )}\n </div>\n );\n}\n\n// ─── Outer component ────────────────────────────────────────────────\n\nexport const Composer = React.forwardRef<ComposerHandle, ComposerProps>(\n function Composer(props, forwardedRef) {\n const {\n initialText,\n initialJson,\n formats,\n toolbar,\n triggers,\n attachments: attachmentsProp,\n ...rest\n } = props;\n\n // Resolve formats — false means plain text, undefined means defaults.\n const formatList: ComposerFormat[] = React.useMemo(() => {\n if (formats === false) return [];\n return (\n formats ?? [\n \"bold\",\n \"italic\",\n \"underline\",\n \"strikethrough\",\n \"code\",\n \"h1\",\n \"h2\",\n \"blockquote\",\n \"ul\",\n \"ol\",\n ]\n );\n }, [formats]);\n\n const showToolbar = toolbar === true || toolbar === \"top\";\n\n // Resolve attachments config — true means defaults, object merges.\n const attachmentsCfg = React.useMemo<Required<ComposerAttachmentConfig> | null>(() => {\n if (!attachmentsProp) return null;\n const obj = attachmentsProp === true ? {} : attachmentsProp;\n if (obj.enabled === false) return null;\n return {\n enabled: true,\n accept: obj.accept ?? \"image/*\",\n maxItems: obj.maxItems ?? 10,\n multiple: obj.multiple ?? true,\n };\n }, [attachmentsProp]);\n\n const [attachments, setAttachments] = React.useState<ComposerAttachment[]>(\n [],\n );\n const attachmentsRef = React.useRef(attachments);\n attachmentsRef.current = attachments;\n\n // Revoke any outstanding object URLs on unmount.\n React.useEffect(() => {\n return () => {\n attachmentsRef.current.forEach((a) => URL.revokeObjectURL(a.previewUrl));\n };\n }, []);\n\n const ingestImages = React.useCallback(\n (files: File[]) => {\n if (!attachmentsCfg) return;\n const candidates = files.filter((f) => {\n if (!attachmentsCfg.accept) return true;\n if (attachmentsCfg.accept === \"image/*\") return f.type.startsWith(\"image/\");\n // Coarse check — full accept-attribute matching is more\n // permissive than we need for v1.\n return attachmentsCfg.accept.split(\",\").some((tok) => {\n const t = tok.trim();\n if (t.endsWith(\"/*\")) return f.type.startsWith(t.slice(0, -1));\n return f.type === t || f.name.toLowerCase().endsWith(t);\n });\n });\n if (candidates.length === 0) return;\n setAttachments((prev) => {\n const room = attachmentsCfg.maxItems - prev.length;\n if (room <= 0) return prev;\n const next = candidates.slice(0, room).map((file) => ({\n id: `${file.name}-${file.size}-${Date.now()}-${Math.random()\n .toString(36)\n .slice(2, 8)}`,\n file,\n previewUrl: URL.createObjectURL(file),\n name: file.name,\n }));\n return [...prev, ...next];\n });\n },\n [attachmentsCfg],\n );\n\n // Editor ref bridged via the inner plugin.\n const editorRef = React.useRef<LexicalEditor | null>(null);\n const submitRef = React.useRef<() => void>(() => {});\n // restart() is wired by the inner component (which owns the\n // useScriptedDemo state). Outer keeps a ref pointer it forwards\n // through ComposerHandle so callers can `ref.current.restart()`.\n const restartRef = React.useRef<(delayMs?: number) => void>(() => {});\n\n const handleEditorReady = React.useCallback((editor: LexicalEditor) => {\n editorRef.current = editor;\n }, []);\n\n // Expose imperative handle.\n React.useImperativeHandle(\n forwardedRef,\n (): ComposerHandle => ({\n play: (_steps) => {\n // For imperative play, we re-mount with a manual trigger.\n // V1: log + recommend using the `steps` prop with manual\n // trigger. The plumbing for hot-swapping a fresh script is\n // tracked as a follow-up.\n // eslint-disable-next-line no-console\n console.warn(\n \"[Composer] handle.play(steps) is not wired in v1 — pass `steps` + `trigger=\\\"manual\\\"` and toggle `play` instead.\",\n );\n },\n stop: () => {},\n restart: (delayMs) => restartRef.current(delayMs),\n focus: () => editorRef.current?.focus(),\n clear: () => {\n const editor = editorRef.current;\n if (editor) clearEditor(editor);\n },\n insert: (text) => {\n editorRef.current?.update(() => {\n const sel = $getSelection();\n if ($isRangeSelection(sel)) sel.insertText(text);\n });\n },\n getContent: () =>\n editorRef.current\n ? snapshotContent(editorRef.current)\n : { text: \"\", json: \"\", mentions: [] },\n getEditor: () => editorRef.current,\n }),\n [],\n );\n\n // Lexical theme classes — kept light so most of the styling\n // happens via Tailwind on the parent. The theme bridge is here\n // so consumers can override individual node classes via CSS\n // variables without forking the component.\n const lexicalTheme = React.useMemo(\n () => ({\n paragraph: \"gds-composer-paragraph\",\n quote: \"gds-composer-quote\",\n heading: {\n h1: \"gds-composer-h1\",\n h2: \"gds-composer-h2\",\n h3: \"gds-composer-h3\",\n },\n list: {\n ul: \"gds-composer-ul\",\n ol: \"gds-composer-ol\",\n listitem: \"gds-composer-li\",\n },\n text: {\n bold: \"font-semibold\",\n italic: \"italic\",\n underline: \"underline\",\n strikethrough: \"line-through\",\n code: \"gds-composer-code\",\n },\n beautifulMentions: {\n // Tailwind utilities so the pill styling works without\n // requiring consumers to load @gradeui/ui/styles.css. The\n // gds-composer-mention* tokens in globals.css are still\n // available for hosts that want to retheme without forking\n // — they layer over these defaults via class precedence.\n \"@\": \"gds-composer-mention px-1.5 py-0.5 mx-0.5 rounded bg-primary/10 text-primary font-medium\",\n \"/\": \"gds-composer-mention px-1.5 py-0.5 mx-0.5 rounded bg-violet-500/15 text-violet-600 dark:text-violet-400 font-medium\",\n },\n }),\n [],\n );\n\n // Initial config for LexicalRoot. Note nodes registration —\n // every custom node we use anywhere (BeautifulMentionNode for\n // mentions, HeadingNode/QuoteNode/etc for formatting) MUST be\n // declared here or Lexical throws at first paint.\n const initialConfig: InitialConfigType = React.useMemo(\n () => ({\n namespace: \"gds-composer\",\n theme: lexicalTheme,\n onError: (error: Error) => {\n // eslint-disable-next-line no-console\n console.error(\"[Composer]\", error);\n },\n nodes: [\n HeadingNode,\n QuoteNode,\n ListNode,\n ListItemNode,\n LinkNode,\n AutoLinkNode,\n CodeNode,\n CodeHighlightNode,\n BeautifulMentionNode,\n ],\n editorState: initialJson\n ? initialJson\n : initialText\n ? (editor: LexicalEditor) => {\n const root = $getRoot();\n const p = $createParagraphNode();\n p.append($createTextNode(initialText));\n root.append(p);\n }\n : undefined,\n }),\n [lexicalTheme, initialJson, initialText],\n );\n\n const richMode = formatList.length > 0;\n const { readOnly = false, ...innerRest } = rest;\n\n return (\n <LexicalRoot\n initialConfig={{\n ...initialConfig,\n editable: !readOnly,\n }}\n >\n <ComposerInner\n {...innerRest}\n readOnly={readOnly}\n triggers={triggers ?? []}\n attachmentsCfg={readOnly ? null : attachmentsCfg}\n formatList={formatList}\n showToolbar={showToolbar && richMode && !readOnly}\n handleEditorReady={handleEditorReady}\n submitRef={submitRef}\n restartRef={restartRef}\n attachments={attachments}\n setAttachments={setAttachments}\n ingestImages={ingestImages}\n />\n </LexicalRoot>\n );\n },\n);\n\n// ─── ComposerReply — preset for the reply use case ───────────────────\n//\n// Wraps Composer with sensible defaults for comment threads and\n// reply boxes: plain text only (no toolbar), no attachments, the\n// \"Write a reply…\" placeholder. Surfaced from playground use — three\n// scaffolds (comments, chat, hero preview) all reached for the same\n// shape and re-passed the same props.\n//\n// All defaults are overridable — pass `formats=[...]` to enable a\n// toolbar, `attachments` to allow uploads, etc. ComposerReply is\n// a starting point, not a lock-in.\n\nexport const ComposerReply = React.forwardRef<ComposerHandle, ComposerProps>(\n function ComposerReply(props, ref) {\n return (\n <Composer\n ref={ref}\n placeholder=\"Write a reply…\"\n formats={false}\n submitOnEnter={false}\n {...props}\n />\n );\n },\n);\n"]}
|
package/dist/contracts.js
CHANGED
|
@@ -43,7 +43,7 @@ toolbar buttons render when \`toolbar\` is on.
|
|
|
43
43
|
|
|
44
44
|
Shares the lib/demo step vocabulary with <Code> so scripted
|
|
45
45
|
typing/format/mention demos animate in the same rhythm as your
|
|
46
|
-
terminal demos.`,import:"@gradeui/ui",composesWith:["AIChatComposer (preset wrapping this with paperclip + send + attachments)","ComposerReply (preset for comment threads)","AIChat (uses AIChatComposer internally)","Card (host above for reply boxes)",'Avatar (in leftActions slot for "your" avatar next to the input)'],styleDefaults:{ComposerToolbar:"flex flex-wrap items-center gap-0.5",AttachmentChips:"absolute -top-1.5 -right-1.5 h-5 w-5 rounded-full",ComposerInner:"w-full"},props:{placeholder:{schema:zod.z.string().optional(),design:"content"},initialText:{schema:zod.z.string().optional(),design:"content",description:"plain text content to seed on mount"},initialJson:{schema:zod.z.string().optional(),design:"content",description:"Lexical state JSON (from a previous onSubmit round-trip)"},formats:{schema:zod.z.unknown().optional(),design:"plumbing",description:"available formats (defaults to bold/italic/underline/strikethrough/code/h1/h2/blockquote/ul/ol); pass false for plain text only"},toolbar:{schema:zod.z.boolean().optional(),design:"knob",description:"show the formatting toolbar above the editor; default false"},triggers:{schema:zod.z.unknown().optional(),design:"plumbing",description:'mention/slash configs, eg. `[{ char: "@", items: people }, { char: "/", items: commands }]`'},attachments:{schema:zod.z.boolean().optional(),design:"knob",description:"enable image paste + paperclip when true/object; default off"},onSubmit:{schema:zod.z.unknown().optional(),design:"event"},isLoading:{schema:zod.z.boolean().optional(),design:"knob",description:"disables editor, swaps default Send for Stop"},onStop:{schema:zod.z.unknown().optional(),design:"event"},maxLength:{schema:zod.z.number().optional(),design:"knob"},autoFocus:{schema:zod.z.boolean().optional(),design:"plumbing"},submitOnEnter:{schema:zod.z.boolean().optional(),design:"knob",description:"default true (Shift-Enter still inserts newline)"},leftActions:{schema:zod.z.unknown().optional(),design:"plumbing",description:"override the default paperclip"},rightActions:{schema:zod.z.unknown().optional(),design:"plumbing",description:"override the default Send/Stop"},hideSend:{schema:zod.z.boolean().optional(),design:"knob",description:"hide the default Send without replacing it"},steps:{schema:zod.z.unknown().optional(),design:"plumbing",description:"scripted demo sequence"},trigger:{schema:zod.z.unknown().optional(),design:"plumbing",description:'"mount" | "inView" | "manual"; default "mount"'},play:{schema:zod.z.boolean().optional(),design:"knob",description:'for trigger="manual"'},speed:{schema:zod.z.unknown().optional(),design:"plumbing",description:'"slow" | "normal" | "fast"; default "normal"'},loop:{schema:zod.z.boolean().optional(),design:"knob"},loopDelay:{schema:zod.z.number().optional(),design:"knob",description:"ms between loop iterations, default 2000"},readOnly:{schema:zod.z.boolean().optional(),design:"knob",description:"disables editing AND focusability; programmatic playback still works; use for marketing demos so the script doesn't steal focus"},bare:{schema:zod.z.boolean().optional(),design:"knob",description:"strip the card chrome"},className:{schema:zod.z.string().optional(),design:"plumbing"}}};var Ge={name:"DataView",description:'One dataset, drawn as a table, a list of cards, or a grid \u2014 without re-typing the TanStack boilerplate (sortable headers, flexRender, selection, view switch) on every page. Hand it data + a columns schema; columns declare a `type` (badge/tags/number/currency/percent/date/boolean/url/text) that DataView renders, with a `cell` override for bespoke cells (avatars, relations). The view toggle can live anywhere \u2014 `useDataView()` holds the state so a `<DataViewToggle>` or `<DataViewColumns>` in a page header drives a `<DataView>` lower down. Mark a column `pinned="left"` (with a `width`) for a fixed column and `stickyHeader` to freeze the header. For a single record\'s fields use PropertyList; for the raw table primitive use Table.',import:"@gradeui/ui",aliases:["data view","data table","datatable","data grid","dataview","table view","card view","grid view","list view","gallery","records list","master list","tanstack table","sortable table","column visibility","pinned column","frozen column","sticky header","view switcher"],composesWith:["Table","Card","Badge","Avatar","ToggleGroup","DropdownMenu","PropertyList","Combobox"],props:{data:{schema:zod.z.unknown(),design:"plumbing",description:"the rows"},columns:{schema:zod.z.unknown(),design:"plumbing",description:"the schema; one list drives table, cards, and grid"},getRowId:{schema:zod.z.unknown().optional(),design:"plumbing",description:"defaults to row.id"},view:{schema:zod.z.unknown().optional(),design:"plumbing",description:"controlled or uncontrolled view"},views:{schema:zod.z.unknown().optional(),design:"plumbing",description:"allowed views; one entry = single view, no toggle"},activeId:{schema:zod.z.unknown().optional(),design:"plumbing",description:"the selected row; click emits it"},sorting:{schema:zod.z.unknown().optional(),design:"plumbing",description:"TanStack SortingState"},columnVisibility:{schema:zod.z.unknown().optional(),design:"plumbing",description:"which fields show"},stickyHeader:{schema:zod.z.boolean().optional(),design:"knob",description:"freeze the header row on scroll"},toolbar:{schema:zod.z.boolean().optional(),design:"knob",description:"render the built-in columns menu + view toggle above the view"},renderCard:{schema:zod.z.unknown().optional(),design:"plumbing",description:"override card / grid tiles"},emptyMessage:{schema:zod.z.unknown().optional(),design:"plumbing"}}};var Oe={name:"DatePicker",description:'Any date or date-range entry. Use DatePicker for a single date (DOB, due date, booking). Use DateRangePicker for a span (report period, stay dates, filter window). Prefer these over <Input type="date"> \u2014 consistent theming, keyboard nav, a11y, and no browser-native UI drift.',import:"@gradeui/ui",aliases:["datepicker","calendar input","date field","date range","datepickerios","react native date picker","calendar input field","date field control"],subcomponents:["DateRangePicker"],composesWith:["Label","Form","Card (in CardContent)","Button (form submit)"],styleDefaults:{DatePicker:"w-[280px] justify-start text-left font-normal data-[empty=true]:text-muted-foreground",DateRangePicker:"w-[300px] justify-start text-left font-normal data-[empty=true]:text-muted-foreground"},props:{value:{schema:zod.z.unknown().optional(),design:"plumbing"},onChange:{schema:zod.z.unknown().optional(),design:"event",description:"called on select or clear"},placeholder:{schema:zod.z.string().optional(),design:"content",description:'trigger label when empty (default "Pick a date" / "Pick a date range")'},disabled:{schema:zod.z.boolean().optional(),design:"knob"},format:{schema:zod.z.string().optional(),design:"content",description:'date-fns format token for the trigger label (default "PPP" single, "LLL dd, y" range)'},align:{schema:zod.z.enum(["start","center","end"]).optional(),design:"knob",description:'popover align (default "start")'},side:{schema:zod.z.enum(["top","right","bottom","left"]).optional(),design:"knob",description:"popover side"},captionLayout:{schema:zod.z.enum(["label","dropdown","dropdown-months","dropdown-years"]).optional(),design:"knob"},className:{schema:zod.z.string().optional(),design:"plumbing",description:"on the trigger button"},contentClassName:{schema:zod.z.string().optional(),design:"content",description:"on the PopoverContent"},icon:{schema:zod.z.unknown().optional(),design:"plumbing",description:"replaces the default CalendarIcon"},numberOfMonths:{schema:zod.z.number().optional(),design:"knob",description:"DateRangePicker only, default 2"}}};var Ee={name:"Dialog",description:'Modal interruptions \u2014 confirmations, focused forms, detail views, AI suggestion sheets. Dialog is the right primitive for Apple HIG / React Native "Alert" (modal) semantics. For non-blocking inline messaging use Callout; for transient notifications use Toaster (Sonner). Always include DialogTitle (a11y requirement).',import:"@gradeui/ui",aliases:["modal","popup","overlay","alert","system alert","alert dialog","modal dialog","confirm dialog","react native modal","rn alert","glass modal","frosted modal","ai suggestion modal"],subcomponents:["DialogTrigger","DialogContent","DialogHeader","DialogTitle","DialogDescription","DialogFooter","DialogClose"],composesWith:["Button (as DialogTrigger asChild","and inside DialogFooter)","Input/Textarea/Select inside DialogContent","Code (for changelog / diff modals)","MediaSurface (for image / preview modals)"],styleDefaults:{DialogOverlay:"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",DialogContent:"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",DialogHeader:"flex flex-col space-y-1.5 text-center sm:text-left",DialogFooter:"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",DialogTitle:"text-lg font-semibold leading-none tracking-tight",DialogDescription:"text-sm text-muted-foreground"},props:{open:{schema:zod.z.unknown().optional(),design:"plumbing"},onOpenChange:{schema:zod.z.unknown().optional(),design:"event",description:"Radix controlled/uncontrolled pattern"},asChild:{schema:zod.z.enum(["wrap a Button"]).optional(),design:"plumbing"},surface:{schema:zod.z.enum(["solid","translucent","glass","glass-strong"]).optional(),design:"knob",description:"what the modal panel is *made of*. Defaults to `solid` (opaque `bg-background`). `glass` lets the page show through softly \u2014 pairs with rich backdrops or AI-suggestion modals."},accepts:{schema:zod.z.unknown().optional(),design:"plumbing"},used:{schema:zod.z.unknown().optional(),design:"plumbing"}}};var He={name:"DropdownMenu",description:'A small action menu attached to a trigger \u2014 overflow "\u2026" buttons on cards, user-avatar menus in headers, "Insert" menus in editors. For a full searchable list, use Command. For ONE primary action plus a secondary, use a Button next to a smaller ghost Button instead of a dropdown.',import:"@gradeui/ui",aliases:["dropdown","dropdown menu","overflow menu","kebab menu","more menu","action menu","context-style menu","menu","pull-down menu","pulldown menu","context menu","popup menu","actions menu","glass menu","frosted menu","ios menu","hig menu"],subcomponents:["DropdownMenuTrigger","DropdownMenuContent","DropdownMenuItem","DropdownMenuCheckboxItem","DropdownMenuRadioGroup","DropdownMenuRadioItem","DropdownMenuLabel","DropdownMenuSeparator","DropdownMenuShortcut","DropdownMenuGroup","DropdownMenuSub","DropdownMenuSubTrigger","DropdownMenuSubContent"],composesWith:["Button (as trigger asChild)","Avatar (user menu)","Card (overflow on a tile)","Tooltip (on the trigger)"],styleDefaults:{DropdownMenuSubContent:"z-50 min-w-[8rem] overflow-hidden rounded-md border text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",DropdownMenuContent:"z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border text-popover-foreground shadow-md",DropdownMenuItem:"relative flex cursor-default select-none items-center gap-2 rounded-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground focus:[&_svg]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:shrink-0",DropdownMenuCheckboxItem:"relative flex cursor-default select-none items-center rounded-sm pr-2 outline-none transition-colors focus:bg-accent focus:text-accent-foreground focus:[&_svg]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",DropdownMenuRadioItem:"relative flex cursor-default select-none items-center rounded-sm pr-2 outline-none transition-colors focus:bg-accent focus:text-accent-foreground focus:[&_svg]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",DropdownMenuLabel:"font-semibold",DropdownMenuSeparator:"-mx-1 my-1 h-px bg-muted",DropdownMenuShortcut:"ml-auto text-xs tracking-widest opacity-60"},props:{open:{schema:zod.z.unknown().optional(),design:"plumbing"},defaultOpen:{schema:zod.z.unknown().optional(),design:"plumbing"},onOpenChange:{schema:zod.z.unknown().optional(),design:"event"},modal:{schema:zod.z.unknown().optional(),design:"plumbing"},asChild:{schema:zod.z.boolean().optional(),design:"plumbing",description:"usually wraps a Button"},align:{schema:zod.z.enum(["start","center","end"]).optional(),design:"knob"},side:{schema:zod.z.enum(["top","right","bottom","left"]).optional(),design:"knob"},sideOffset:{schema:zod.z.number().optional(),design:"knob"},surface:{schema:zod.z.enum(["solid","translucent","glass","glass-strong"]).optional(),design:"knob",description:"what the menu surface is *made of*. `solid` (default) is `bg-popover`. `translucent` matches Apple HIG / iOS menu sheets. `glass` for menus floating over rich canvases."},size:{schema:zod.z.enum(["default","sm","xs"]).optional(),design:"knob",description:'menu density; cascades to every item (Item, Checkbox, Radio, SubTrigger, Label) via context so a compact trigger gets a compact menu. Use "xs" in dense tool panels.'},onSelect:{schema:zod.z.unknown().optional(),design:"event"},disabled:{schema:zod.z.unknown().optional(),design:"plumbing"},inset:{schema:zod.z.unknown().optional(),design:"plumbing"},DropdownMenuCheckboxItem:{schema:zod.z.unknown(),design:"plumbing"},DropdownMenuSub:{schema:zod.z.unknown(),design:"plumbing",description:"sub-trigger shows children, sub-content holds the deeper items"},children:{schema:zod.z.unknown().optional(),design:"plumbing",description:"right-aligned kbd hint"}}};var Ve={name:"Field",description:'Pair a bare control with a label and optional description in a row, with id + aria-describedby wired automatically. Use layout="setting" for the classic settings row (label on the left, Switch on the right). For a selectable CARD where the whole surface is the control, use RadioCard / CheckboxCard / SwitchCard instead.',import:"@gradeui/ui",aliases:["field","form field","control row","label and description","two line checkbox","option row","setting row","toggle row"],composesWith:["Checkbox","RadioGroup","RadioGroupItem","Switch","Badge (inside Field.Trailing)"],styleDefaults:{FieldLabel:"text-sm font-medium leading-none text-foreground cursor-pointer peer-disabled:cursor-not-allowed peer-disabled:opacity-70",FieldDescription:"text-sm text-muted-foreground",FieldTrailing:"flex shrink-0 items-center gap-2",FieldRoot:"flex gap-3"},props:{layout:{schema:zod.z.enum(["option","setting"]).optional(),design:"knob",description:"option (default): control leads, text beside it; setting: text leads, control pinned trailing"},children:{schema:zod.z.unknown(),design:"plumbing",description:"order does not matter"}}};var qe={name:"FillPicker",description:`Grade's paint picker \u2014 the control for choosing a frame's background fill, modelled on Figma's fill popover. A fill-type icon row (solid \xB7 gradient \xB7 image \xB7 pattern \xB7 video \xB7 shader) switches the panel below; a global opacity sits at the foot. Emits a FillValue that maps 1:1 onto BackgroundFill props. This is a Studio/inspector chrome control \u2014 pair it with BackgroundFill, which renders the chosen paint. Not for app content. Use the FillSection subcomponent to edit a LIST of fills (the Figma "Fill" inspector section): each row is a Solid/Gradient/Image toggle, the matching value control (ColorPicker / GradientEditor popover / image URL), an opacity %, a visibility eye, and a remove button, with an add button in the header.`,import:"@gradeui/ui",aliases:["fill picker","paint picker","background picker","fill chooser","fill popover","fill section","fill list","fills inspector","paint section"],subcomponents:["FillSection"],composesWith:["BackgroundFill (renders the FillValue)","Popover (host it in a popover)","ColorPicker (the solid value)","GradientEditor (the gradient value)","ShaderPresetPicker (the shader tab)","the inspector Fill section"],styleDefaults:{Swatch:"h-7 w-7 rounded-md border border-border/60 transition-shadow",FillPicker:"flex flex-col gap-3",FillSection:"flex flex-col gap-2"},props:{value:{schema:zod.z.unknown(),design:"plumbing",description:"current paint ({ type, color?, gradient?, src?, fit?, repeat?, tileSize?, preset?, palette?, postPreset?, opacity? }) (required)"},onChange:{schema:zod.z.unknown(),design:"event",description:"called on any change (required)"},title:{schema:zod.z.string().optional(),design:"plumbing",description:'section heading (default "Fills")'}}};var Ue={name:"Flex",description:"The unopinionated flexbox primitive \u2014 reach for Flex when Stack, Row, or Grid don't quite fit. Specifically when you need reverse direction (`row-reverse` / `col-reverse`), CSS defaults instead of Row's baked-in `items-center gap-md`, or baseline alignment. Otherwise prefer Stack / Row / Grid \u2014 they're easier to read and tuned for the 95% case. Flex is the escape hatch, not the default.",import:"@gradeui/ui",aliases:["flex","flexbox","flex container","hstack","vstack","horizontal","vertical","generic container","layout view"],composesWith:["any content component"],props:{direction:{schema:zod.z.enum(["row","col","row-reverse","col-reverse"]).optional(),design:"knob",description:"main-axis direction",default:"row"},gap:{schema:zod.z.enum(["none","xs","sm","md","lg","xl","2xl"]).optional(),design:"knob",description:"gap between children",default:"none"},align:{schema:zod.z.enum(["start","center","end","stretch","baseline"]).optional(),design:"knob",description:"cross-axis alignment",default:"stretch"},justify:{schema:zod.z.enum(["start","center","end","between","around","evenly"]).optional(),design:"knob",description:"main-axis distribution",default:"start"},wrap:{schema:zod.z.enum(["nowrap","wrap","wrap-reverse"]).optional(),design:"knob",description:"wrap behaviour when children overflow",default:"nowrap"},asChild:{schema:zod.z.boolean().optional(),design:"plumbing",description:"render as the child element via Slot",default:false},className:{schema:zod.z.string().optional(),design:"plumbing"},children:{schema:zod.z.unknown(),design:"plumbing"}}};var Ke={name:"GradientEditor",description:'Edit a multi-stop CSS gradient with token-led stops. A type Select (Linear / Radial / Angular) with reverse + rotate actions, a live full-width preview bar (a Swatch type="gradient"), then a Stops list where each stop is a position %, a colour (ColorPicker token or raw), an opacity %, and a remove button; an add button appends a stop. Token stops resolve to oklch(var(--<token>)) so the preview re-voices with the theme. Emits the structured GradientValue (kept editable + serialisable); the caller turns it into CSS with the exported gradientToCss(value). Use inside a Popover from a FillSection gradient row, or standalone in a theme builder. For a single solid colour use ColorPicker; for a full paint (solid / gradient / image / shader) use FillPicker.',import:"@gradeui/ui",aliases:["gradient editor","gradient picker","gradient builder","css gradient editor","stop editor","gradient stops","linear gradient editor","conic gradient editor"],composesWith:["Select","Button","Input","ColorPicker","Swatch","Popover","FillSection"],styleDefaults:{GradientEditor:"flex flex-col gap-3"},props:{value:{schema:zod.z.unknown(),design:"plumbing",description:"the structured gradient (type linear/radial/angular, optional angle in deg, ordered stops). NOT a CSS string \u2014 render the string via gradientToCss(value)."},onChange:{schema:zod.z.unknown(),design:"event",description:"fired with the next structured gradient on any edit"}}};var Ye={name:"Grid",description:"2D layouts where Stack (vertical) and Row (horizontal) don't fit \u2014 stat-card grids, feature tiles, pricing columns, photo grids. Reach for Grid over hand-rolled `grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4` so the column count is a prop the settings panel can mutate and the responsive ladder stays consistent across designs.",import:"@gradeui/ui",aliases:["grid","tiles","cards grid","stat grid","columns","feature grid","grid view","lazy v grid","lazyvgrid","lazy h grid","lazyhgrid","tile grid","masonry"],composesWith:["Card","Stack (inside each cell)","Row","Button","any content component"],styleDefaults:{Grid:"gds-grid grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 items-stretch"},variantDefaults:{cols:"3",gap:"md",align:"stretch"},props:{cols:{schema:zod.z.enum(["1","2","3","4","5","6","12"]).optional(),design:"knob",description:'desktop column count; each value has a baked-in responsive ladder (e.g. "4" \u2192 1 col mobile, 2 tablet, 4 desktop)',default:"3"},gap:{schema:zod.z.enum(["none","xs","sm","md","lg","xl","2xl"]).optional(),design:"knob",description:"gap between grid cells (same scale as Stack/Row)",default:"md"},align:{schema:zod.z.enum(["start","center","end","stretch"]).optional(),design:"knob",description:"cross-axis alignment of cells",default:"stretch"},asChild:{schema:zod.z.boolean().optional(),design:"plumbing",description:"render as the child element via Slot",default:false},className:{schema:zod.z.string().optional(),design:"plumbing"},children:{schema:zod.z.unknown(),design:"plumbing"}}};var Je={name:"HoverCard",description:"Rich preview content surfaced on hover \u2014 user profile mini-cards on @-mentions, link previews, definition popups, layer-thumbnail peeks. Pointer-only by design (no touch-friendly trigger); pair with a click target for touch devices, or fall back to Popover. NEVER use HoverCard for critical info \u2014 if the user can't reach it via keyboard or touch, it might as well not exist for accessibility.",import:"@gradeui/ui",aliases:["hover card","hover preview","mention preview","profile peek","link preview","rich tooltip","link preview card","profile hover","peek card","glass preview","frosted preview"],subcomponents:["HoverCardTrigger","HoverCardContent"],composesWith:["Avatar (user preview)","Card (richer content)","Link (the trigger)","MediaSurface (link/layer previews)","Code (snippet previews)"],styleDefaults:{HoverCardContent:"z-50 w-64 rounded-md border p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-hover-card-content-transform-origin]"},props:{open:{schema:zod.z.unknown().optional(),design:"plumbing"},defaultOpen:{schema:zod.z.unknown().optional(),design:"plumbing"},onOpenChange:{schema:zod.z.unknown().optional(),design:"event"},openDelay:{schema:zod.z.unknown().optional(),design:"plumbing"},closeDelay:{schema:zod.z.unknown().optional(),design:"plumbing"},asChild:{schema:zod.z.boolean().optional(),design:"plumbing",description:"usually a Link or Button"},side:{schema:zod.z.unknown().optional(),design:"plumbing"},align:{schema:zod.z.unknown().optional(),design:"plumbing"},sideOffset:{schema:zod.z.unknown().optional(),design:"plumbing"},alignOffset:{schema:zod.z.unknown().optional(),design:"plumbing"},className:{schema:zod.z.unknown().optional(),design:"plumbing"},surface:{schema:zod.z.enum(["solid","translucent","glass","glass-strong"]).optional(),design:"knob",description:"what the preview surface is *made of*. `solid` (default) is `bg-popover`. `glass` for hover previews over rich content (a media feed, a layout canvas)."}}};var Xe={name:"Input",description:'Any single-line text entry. Always pair with a Label for accessibility. Use startSlot/endSlot for icons, prefixes and units instead of hand-positioning absolute children; use size="sm"/"xs" in dense tool panels.',import:"@gradeui/ui",aliases:["text field","textbox","textfield","form field","text input","secure field","search field","url field","number field","textinput","text input field","react native textinput","unit input","input with icon"],composesWith:["Label","Form","Card (in CardContent)","Button (form submit)"],styleDefaults:{Input:"pointer-events-none absolute inset-y-0 left-0 flex items-center text-muted-foreground [&_svg]:size-3.5"},props:{type:{schema:zod.z.string().optional(),design:"content"},placeholder:{schema:zod.z.string().optional(),design:"content",description:"hint text shown while the input is empty. Model it explicitly (not just a native passthrough) so generated screens carry placeholders and the validator accepts them."},size:{schema:zod.z.enum(["default","sm","xs"]).optional(),design:"knob",description:"control density. `default` (h-9) for forms; `sm` (h-8) and `xs` (h-7) for dense tool panels like the inspector. NOTE: pre-unification scale \u2014 see Figma parity audit; due to migrate to the t-shirt scale (xs 24 | sm 28 | md 32 | lg 40, default\u2192md)."},startSlot:{schema:zod.z.unknown().optional(),design:"plumbing",description:"adornment rendered inside the leading edge (icon, prefix, currency symbol). Non-interactive by default so clicks focus the input."},endSlot:{schema:zod.z.unknown().optional(),design:"plumbing",description:'adornment rendered inside the trailing edge (unit like "px", a clear button, a stepper). Same pointer rules as startSlot.'}}};var Ze={name:"Label",description:'Every Input / Textarea / Checkbox / Switch / RadioGroup. Always use htmlFor so clicking the label focuses the control. Match `size` to the field it labels (size="xs" label over a size="xs" input).',import:"@gradeui/ui",aliases:["label","form label","field label","caption"],composesWith:["Input","Textarea","Checkbox","Switch","RadioGroup","Select"],styleDefaults:{Label:"font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-sm"},variantDefaults:{size:"default"},props:{htmlFor:{schema:zod.z.string().optional(),design:"content",description:"binds to the input's id"},size:{schema:zod.z.enum(["default","sm","xs"]).optional(),design:"knob",description:"text size, mirrors Input/Select/Textarea so a field and its label scale together. default = text-sm; xs = 11px for dense tool panels."}}};var $e={name:"Logo",description:"ALWAYS use <Logo> wherever a screen carries a brand mark \u2014",import:"@gradeui/ui",aliases:["logo","brand","brandmark","wordmark","lockup","brand logo","app logo","logotype","grade mark","g arrow"],composesWith:["AppShell","AppShellHeader","Sidebar","SidebarHeader","Toolbar","MotionOverlay","Row","Stack"],styleDefaults:{Logo:"gds-logo inline-flex shrink-0 select-none items-center"},props:{sources:{schema:zod.z.unknown().optional(),design:"plumbing",description:"artwork keyed by lockup then appearance: { square?: { light?, dark?, mono? }, horizontal?: {...}, icon?: {...} }. Each slot is any node (inline <svg>, <img>, component). OMIT ENTIRELY and the GRADE MARK renders (the square G-arrow, painted with currentColor so it sits correctly on any surface). A bare <Logo /> is always correct branding."},size:{schema:zod.z.enum(["sm","md","lg","xl"]).optional(),design:"knob",description:'height of the mark: 20 / 28 / 40 / 56px (a raw pixel number also works). Width is intrinsic (square/icon 1:1, horizontal keeps its ratio). Default "md"; use "sm" in dense rails and toolbars.'},lockup:{schema:zod.z.enum(["square","horizontal","icon"]).optional(),design:"knob",description:'which shape to show; falls back to the next-best available artwork. Default "horizontal".'},mode:{schema:zod.z.enum(["light","dark"]).optional(),design:"knob",description:'the background the logo SITS ON, selecting light/dark artwork. Explicit, not theme-coupled. Default "light".'},mono:{schema:zod.z.boolean().optional(),design:"knob",description:"render the single-colour treatment; inherits currentColor from the parent. Default false."},label:{schema:zod.z.string().optional(),design:"content",description:'accessible name (aria-label + role="img"); pair with decorative when the brand name is already beside it.'},decorative:{schema:zod.z.boolean().optional(),design:"knob",description:"aria-hidden, no role; use when text nearby names the brand."},href:{schema:zod.z.string().optional(),design:"content",description:"renders the logo as a link (logo-links-home)."},className:{schema:zod.z.string().optional(),design:"plumbing"}}};var et={name:"Map",description:"Any layout that needs a real map \u2014 listings (real estate, Airbnb-style), fleet/logistics dashboards, store locators, anywhere a user picks a location from a viewport. Reach for the controlled `hoveredId` prop when a sibling list and the map need to highlight each other.",import:"@gradeui/ui",aliases:["map","maps","mapbox","maplibre","google maps","geo","location","latlng","coordinates","marker","pin","airbnb","listings","fleet","real estate","logistics","map view","mapkit","mapview","react native maps","rn maps"],subcomponents:["MapMarker"],composesWith:["Card (as marker content)","Badge","Avatar","Button","Row","Stack","Skeleton"],props:{provider:{schema:zod.z.unknown(),design:"plumbing",description:'"maplibre" (default, free, no key) | "mapbox" (needs accessToken) | "google" (needs apiKey). Switching is one prop change.'},center:{schema:zod.z.unknown(),design:"plumbing",description:"`[lng, lat]` tuple. ALWAYS lng first. Required."},zoom:{schema:zod.z.unknown(),design:"plumbing",description:"number, 0\u201322. Required."},bounds:{schema:zod.z.unknown(),design:"plumbing",description:"`[[swLng, swLat], [neLng, neLat]]`. When set, takes precedence over center+zoom."},appearance:{schema:zod.z.unknown(),design:"plumbing",description:'"light" | "dark" | "satellite" | "auto" (default "auto", follows GradeThemeProvider mode).'},hoveredId:{schema:zod.z.unknown(),design:"plumbing",description:'controlled string id, pairs with onHoveredIdChange. The matching MapMarker gets `data-gds-state="hovered"` automatically. This is how you build list \u2194 map two-way sync.'},onHoveredIdChange:{schema:zod.z.unknown(),design:"event",description:"`(id: string | null) => void`. The other half of the controlled hover pair: fires when the pointer enters/leaves a marker so the sibling list can highlight in step. Without this entry in the contract, screens using the documented two-way sync fail save validation."},interactive:{schema:zod.z.unknown(),design:"plumbing",description:"false freezes pan/zoom, useful for static cards."},tools:{schema:zod.z.unknown(),design:"plumbing",description:'"auto" (default, zoom buttons follow `interactive`) | "zoom" (force zoom buttons) | "none" (chrome-free map; attribution always stays \u2014 license). One vocabulary across all providers.'},toolsPosition:{schema:zod.z.unknown(),design:"plumbing",description:'"top-left" (default) | "top-right" | "bottom-left" | "bottom-right". Corner the tools dock to; use when a search bar or legend sits over the default corner.'},onLoad:{schema:zod.z.unknown(),design:"event",description:"handle exposes flyTo, panTo, fitBounds, getCenter, getZoom, getBounds, instance."},tilerKey:{schema:zod.z.unknown().optional(),design:"plumbing",description:'MapLibre only (provider="maplibre"). Optional everywhere: omit on `gradeui.com`/`localhost` and the referrer-locked demo key is used; set it only when embedding off-domain. The contract never requires it.'},accessToken:{schema:zod.z.unknown().optional(),design:"plumbing",description:'Mapbox only. Pass it whenever provider="mapbox" \u2014 the component itself enforces this at runtime (throws a clear `provider="mapbox" requires an accessToken prop` error via onError if missing). It is OPTIONAL in the contract on purpose, so the validator never demands it from maplibre/google maps.'},apiKey:{schema:zod.z.unknown().optional(),design:"plumbing",description:'Google only. Pass it whenever provider="google" \u2014 the component enforces it at runtime (throws `provider="google" requires an apiKey prop` via onError if missing). OPTIONAL in the contract on purpose, so it\'s never demanded from maplibre/mapbox.'},id:{schema:zod.z.unknown().optional(),design:"plumbing",description:"string. Required. Stable marker id; pair with Map's `hoveredId` for list\u2194map hover sync."},at:{schema:zod.z.unknown().optional(),design:"plumbing",description:"`[lng, lat]` tuple. Required. THE coordinate prop. ALWAYS lng first. The prop is literally named `at` \u2014 it is NOT `lngLat`, `coordinates`, `position`, `latLng`, `center`, or separate `lng`/`lat` props. Passing any other name leaves the marker coord `undefined`, and MapLibre throws on mount, crashing the WHOLE screen in every renderer. When in doubt, copy the `airbnb-listings` scaffold: `<MapMarker id={l.id} at={l.coords}>`."},anchor:{schema:zod.z.unknown().optional(),design:"plumbing",description:'"center" | "bottom" (default "bottom", pin tip sits on the coord). Only these two values.'},onClick:{schema:zod.z.unknown().optional(),design:"event",description:"handler called with `({ id, coords, native })` on marker click."},children:{schema:zod.z.unknown().optional(),design:"plumbing",description:"DOM rendered as the marker (Badge, Card, Avatar, or any element). Inherits `--gds-*` tokens."}}};var _t=zod.z.object({kind:zod.z.literal("album"),artist:zod.z.string(),title:zod.z.string(),year:zod.z.number().optional(),description:zod.z.string().optional()}),Kt=zod.z.object({kind:zod.z.literal("tv-show"),title:zod.z.string(),year:zod.z.number().optional(),description:zod.z.string().optional()}),Yt=zod.z.object({kind:zod.z.literal("movie"),title:zod.z.string(),year:zod.z.number().optional(),description:zod.z.string().optional()}),Jt=zod.z.object({kind:zod.z.literal("game"),title:zod.z.string(),description:zod.z.string().optional()}),Xt=zod.z.object({kind:zod.z.literal("book"),title:zod.z.string().optional(),author:zod.z.string().optional(),isbn:zod.z.string().optional(),description:zod.z.string().optional()}),Qt=zod.z.object({kind:zod.z.literal("poster"),title:zod.z.string(),year:zod.z.number().optional(),description:zod.z.string().optional()}),Zt=zod.z.object({kind:zod.z.literal("portrait"),name:zod.z.string().optional(),role:zod.z.string().optional()}),$t=zod.z.object({kind:zod.z.literal("landscape"),location:zod.z.string().optional(),mood:zod.z.string().optional()}),eo=zod.z.object({kind:zod.z.literal("product"),name:zod.z.string().optional(),brand:zod.z.string().optional()}),to=zod.z.object({kind:zod.z.literal("food"),dish:zod.z.string().optional(),cuisine:zod.z.string().optional()}),oo=zod.z.object({kind:zod.z.literal("generic"),prompt:zod.z.string()}),no=zod.z.union([zod.z.object({kind:zod.z.literal("video")}),zod.z.object({kind:zod.z.literal("audio")}),zod.z.object({kind:zod.z.literal("embed")}),zod.z.object({kind:zod.z.literal("3d")})]),io=zod.z.union([_t,Kt,Yt,Jt,Xt,Qt,Zt,$t,eo,to,oo,no]),ao=zod.z.enum(["video","square","portrait","wide","auto"]),ro=zod.z.enum(["none","sm","md","lg","xl"]),so=zod.z.enum(["album","tv-show","movie","game","book","portrait","landscape","poster","product","food","video","audio","embed","3d","generic"]),lo=zod.z.union([zod.z.literal("auto"),zod.z.literal("icon"),zod.z.literal("none")]),tt={name:"MediaSurface",description:"The canonical media slot for ALL non-person imagery \u2014 album art, posters, hero images, landscape photos, video and 3D containers.",when:"Pass `hint` + `alt` + (optionally) `source` so the empty-state placeholder is meaningful and the generation pipeline can later fill the slot with a real image. Use directly for declarative slots; the higher-level VideoPlayer / RivePlayer / ThreeScene wrap this for runtime-heavy media.",antipatterns:["Don't wrap <Avatar> inside <MediaSurface> to get an initials fallback. Set `alt` + `hint` on MediaSurface directly \u2014 the placeholder renders initials at small sizes derived from `alt`.","Don't use <Avatar> for album art, posters, products, food, landscapes, etc. Avatar is for PEOPLE only.","Don't inline manual gradient backgrounds (`bg-gradient-to-br \u2026`) on MediaSurface as a 'placeholder vibe' \u2014 the empty-state is already styled via `--gds-media-placeholder-bg/-fg` tokens."],composesWith:["Card","CardBlock","MediaBlock","VideoPlayer","RivePlayer","ThreeScene"],aliases:["media","image slot","media slot","image placeholder","cover","thumbnail","poster slot"],import:"@gradeui/ui",props:{hint:{schema:so.optional(),design:"knob",group:"image",control:"glyph-picker",label:"Slot kind",description:"Picks the placeholder glyph + the default aspect + the future generation provider. Defaults to 'generic'.",default:"generic",examples:["album","portrait","landscape","poster"]},aspect:{schema:ao.optional(),design:"knob",control:"toggle-group",label:"Aspect ratio",description:"Override the slot's natural framing. When omitted, derived from `hint`: album/product/food \u2192 square, portrait/poster \u2192 portrait, landscape \u2192 wide, video/audio/embed/generic \u2192 video."},radius:{schema:ro.optional(),design:"knob",control:"toggle-group",label:"Corner radius",default:"lg",description:"Driven by the `--gds-media-radius` CSS variable."},border:{schema:zod.z.boolean().optional(),design:"knob",label:"Show border",default:false},loading:{schema:zod.z.boolean().optional(),design:"knob",label:"Loading state",default:false,description:"Overlays the muted-pulse skeleton on top of the slot."},emptyState:{schema:lo.optional(),design:"knob",control:"select",label:"Empty state",default:"auto",description:"'auto' renders the size-tiered placeholder (initials \u2192 glyph \u2192 glyph + caption). 'icon' is a legacy alias. 'none' renders a truly empty surface."},alt:{schema:zod.z.string().optional(),design:"content",group:"image",control:"text",label:"Alt text",description:"Becomes the eventual `<img alt>`. Also drives the placeholder caption (>160px slots) and the 2-letter initials fallback (<64px slots).",examples:["Travelling Without Moving \u2014 Jamiroquai","Sunset over Mount Fuji"]},src:{schema:zod.z.string().url().optional(),design:"content",group:"image",control:"url",label:"Image URL",description:"When set, renders an `<img>` filling the slot via object-cover. The wrapper keeps its aspect/radius/border. Generators patch this prop; manual values always win."},instanceId:{schema:zod.z.string().optional(),design:"content",group:"image",control:"text",label:"Instance id",description:"Stable per-instance id stamped as `data-gds-instance-id`. Use when rendering MediaSurfaces from a data array (`.map(item => <MediaSurface instanceId={item.id} \u2026/>)`) \u2014 it's how Studio's selection + Fill flows tell one card apart from its siblings and patch only that entry. Was missing from this hand-authored contract while the component documented it, which made save validation reject the documented pattern (June 2026)."},source:{schema:io.optional(),design:"structured",label:"Source descriptor",description:"Structured metadata for the generation pipeline. Opaque to MediaSurface itself; read by the resolver to look up real imagery from the right provider (MusicBrainz / Pollinations / etc.).",perKindFields:{album:{artist:"string",title:"string",year:"number?"},poster:{title:"string",year:"number?"},portrait:{name:"string?",role:"string?"},landscape:{location:"string?",mood:"string?"},product:{name:"string?",brand:"string?"},food:{dish:"string?",cuisine:"string?"},generic:{prompt:"string"},video:{},audio:{},embed:{},"3d":{}}},className:{schema:zod.z.string().optional(),design:"plumbing"},style:{schema:zod.z.record(zod.z.string(),zod.z.unknown()).optional(),design:"plumbing"},children:{schema:zod.z.unknown().optional(),design:"plumbing",description:"Escape hatch for putting a custom <video>, <canvas>, Rive runtime, etc. inside. When supplied, the placeholder is suppressed."},overlay:{schema:zod.z.unknown().optional(),design:"plumbing",description:"Decorative layer rendered ABOVE the media/placeholder (play buttons, hover gradients, corner badges). Does NOT suppress the placeholder."},glyph:{schema:zod.z.unknown().optional(),design:"plumbing",description:"Per-instance override of the hint-derived placeholder glyph. Most consumers should pick a `hint` and let the map decide."},fallback:{schema:zod.z.unknown().optional(),design:"plumbing",description:"Custom node shown while `loading` is true."},onVisibilityChange:{schema:zod.z.function().optional(),design:"event",description:"Fires when the surface enters / leaves the viewport (IntersectionObserver)."}},actions:{fill:{label:"Fill image",icon:"Sparkles",description:"Resolve this slot's source via the free providers (MusicBrainz \u2192 Pollinations \u2192 Picsum) and patch the result into the runtime URL map.",kind:"resolve-media-source",enabledWhen:{propPresent:"source"}}}};var ot={name:"Message",description:`The canonical "avatar + author + timestamp + body" row. THE PRIMITIVE
|
|
46
|
+
terminal demos.`,import:"@gradeui/ui",composesWith:["AIChatComposer (preset wrapping this with paperclip + send + attachments)","ComposerReply (preset for comment threads)","AIChat (uses AIChatComposer internally)","Card (host above for reply boxes)",'Avatar (in leftActions slot for "your" avatar next to the input)'],styleDefaults:{ComposerToolbar:"flex flex-wrap items-center gap-0.5",AttachmentChips:"absolute -top-1.5 -right-1.5 h-5 w-5 rounded-full",ComposerInner:"w-full"},props:{placeholder:{schema:zod.z.string().optional(),design:"content"},initialText:{schema:zod.z.string().optional(),design:"content",description:"plain text content to seed on mount"},initialJson:{schema:zod.z.string().optional(),design:"content",description:"Lexical state JSON (from a previous onSubmit round-trip)"},formats:{schema:zod.z.unknown().optional(),design:"plumbing",description:"available formats (defaults to bold/italic/underline/strikethrough/code/h1/h2/blockquote/ul/ol); pass false for plain text only"},toolbar:{schema:zod.z.boolean().optional(),design:"knob",description:"show the formatting toolbar above the editor; default false"},triggers:{schema:zod.z.unknown().optional(),design:"plumbing",description:'mention/slash configs, eg. `[{ char: "@", items: people }, { char: "/", items: commands }]`'},attachments:{schema:zod.z.boolean().optional(),design:"knob",description:"enable image paste + paperclip when true/object; default off"},onSubmit:{schema:zod.z.unknown().optional(),design:"event"},isLoading:{schema:zod.z.boolean().optional(),design:"knob",description:"disables editor, swaps default Send for Stop"},onStop:{schema:zod.z.unknown().optional(),design:"event"},maxLength:{schema:zod.z.number().optional(),design:"knob"},autoFocus:{schema:zod.z.boolean().optional(),design:"plumbing"},submitOnEnter:{schema:zod.z.boolean().optional(),design:"knob",description:"default true (Shift-Enter still inserts newline)"},leftActions:{schema:zod.z.unknown().optional(),design:"plumbing",description:"override the default paperclip"},rightActions:{schema:zod.z.unknown().optional(),design:"plumbing",description:"override the default Send/Stop"},hideSend:{schema:zod.z.boolean().optional(),design:"knob",description:"hide the default Send without replacing it"},steps:{schema:zod.z.unknown().optional(),design:"plumbing",description:"scripted demo sequence"},trigger:{schema:zod.z.unknown().optional(),design:"plumbing",description:'"mount" | "inView" | "manual"; default "mount"'},play:{schema:zod.z.boolean().optional(),design:"knob",description:'for trigger="manual"'},speed:{schema:zod.z.unknown().optional(),design:"plumbing",description:'"slow" | "normal" | "fast"; default "normal"'},loop:{schema:zod.z.boolean().optional(),design:"knob"},loopDelay:{schema:zod.z.number().optional(),design:"knob",description:"ms between loop iterations, default 2000"},readOnly:{schema:zod.z.boolean().optional(),design:"knob",description:"disables editing AND focusability; programmatic playback still works; use for marketing demos so the script doesn't steal focus"},bare:{schema:zod.z.boolean().optional(),design:"knob",description:"strip the card chrome"},className:{schema:zod.z.string().optional(),design:"plumbing"}}};var Ge={name:"DataView",description:'One dataset, drawn as a table, a list of cards, or a grid \u2014 without re-typing the TanStack boilerplate (sortable headers, flexRender, selection, view switch) on every page. Hand it data + a columns schema; columns declare a `type` (badge/tags/number/currency/percent/date/boolean/url/text) that DataView renders, with a `cell` override for bespoke cells (avatars, relations). The view toggle can live anywhere \u2014 `useDataView()` holds the state so a `<DataViewToggle>` or `<DataViewColumns>` in a page header drives a `<DataView>` lower down. Mark a column `pinned="left"` (with a `width`) for a fixed column and `stickyHeader` to freeze the header. For a single record\'s fields use PropertyList; for the raw table primitive use Table.',import:"@gradeui/ui",aliases:["data view","data table","datatable","data grid","dataview","table view","card view","grid view","list view","gallery","records list","master list","tanstack table","sortable table","column visibility","pinned column","frozen column","sticky header","view switcher"],composesWith:["Table","Card","Badge","Avatar","ToggleGroup","DropdownMenu","PropertyList","Combobox"],props:{data:{schema:zod.z.unknown(),design:"plumbing",description:"the rows"},columns:{schema:zod.z.unknown(),design:"plumbing",description:"the schema; one list drives table, cards, and grid"},getRowId:{schema:zod.z.unknown().optional(),design:"plumbing",description:"defaults to row.id"},view:{schema:zod.z.unknown().optional(),design:"plumbing",description:"controlled or uncontrolled view"},views:{schema:zod.z.unknown().optional(),design:"plumbing",description:"allowed views; one entry = single view, no toggle"},activeId:{schema:zod.z.unknown().optional(),design:"plumbing",description:"the selected row; click emits it"},sorting:{schema:zod.z.unknown().optional(),design:"plumbing",description:"TanStack SortingState"},columnVisibility:{schema:zod.z.unknown().optional(),design:"plumbing",description:"which fields show"},stickyHeader:{schema:zod.z.boolean().optional(),design:"knob",description:"freeze the header row on scroll"},toolbar:{schema:zod.z.boolean().optional(),design:"knob",description:"render the built-in columns menu + view toggle above the view"},renderCard:{schema:zod.z.unknown().optional(),design:"plumbing",description:"override card / grid tiles"},emptyMessage:{schema:zod.z.unknown().optional(),design:"plumbing"}}};var Oe={name:"DatePicker",description:'Any date or date-range entry. Use DatePicker for a single date (DOB, due date, booking). Use DateRangePicker for a span (report period, stay dates, filter window). Prefer these over <Input type="date"> \u2014 consistent theming, keyboard nav, a11y, and no browser-native UI drift.',import:"@gradeui/ui",aliases:["datepicker","calendar input","date field","date range","datepickerios","react native date picker","calendar input field","date field control"],subcomponents:["DateRangePicker"],composesWith:["Label","Form","Card (in CardContent)","Button (form submit)"],styleDefaults:{DatePicker:"w-[280px] justify-start text-left font-normal data-[empty=true]:text-muted-foreground",DateRangePicker:"w-[300px] justify-start text-left font-normal data-[empty=true]:text-muted-foreground"},props:{value:{schema:zod.z.unknown().optional(),design:"plumbing"},onChange:{schema:zod.z.unknown().optional(),design:"event",description:"called on select or clear"},placeholder:{schema:zod.z.string().optional(),design:"content",description:'trigger label when empty (default "Pick a date" / "Pick a date range")'},disabled:{schema:zod.z.boolean().optional(),design:"knob"},format:{schema:zod.z.string().optional(),design:"content",description:'date-fns format token for the trigger label (default "PPP" single, "LLL dd, y" range)'},align:{schema:zod.z.enum(["start","center","end"]).optional(),design:"knob",description:'popover align (default "start")'},side:{schema:zod.z.enum(["top","right","bottom","left"]).optional(),design:"knob",description:"popover side"},captionLayout:{schema:zod.z.enum(["label","dropdown","dropdown-months","dropdown-years"]).optional(),design:"knob"},className:{schema:zod.z.string().optional(),design:"plumbing",description:"on the trigger button"},contentClassName:{schema:zod.z.string().optional(),design:"content",description:"on the PopoverContent"},icon:{schema:zod.z.unknown().optional(),design:"plumbing",description:"replaces the default CalendarIcon"},numberOfMonths:{schema:zod.z.number().optional(),design:"knob",description:"DateRangePicker only, default 2"}}};var Ee={name:"Dialog",description:'Modal interruptions \u2014 confirmations, focused forms, detail views, AI suggestion sheets. Dialog is the right primitive for Apple HIG / React Native "Alert" (modal) semantics. For non-blocking inline messaging use Callout; for transient notifications use Toaster (Sonner). Always include DialogTitle (a11y requirement).',import:"@gradeui/ui",aliases:["modal","popup","overlay","alert","system alert","alert dialog","modal dialog","confirm dialog","react native modal","rn alert","glass modal","frosted modal","ai suggestion modal"],subcomponents:["DialogTrigger","DialogContent","DialogHeader","DialogTitle","DialogDescription","DialogFooter","DialogClose"],composesWith:["Button (as DialogTrigger asChild","and inside DialogFooter)","Input/Textarea/Select inside DialogContent","Code (for changelog / diff modals)","MediaSurface (for image / preview modals)"],styleDefaults:{DialogOverlay:"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",DialogContent:"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",DialogHeader:"flex flex-col space-y-1.5 text-center sm:text-left",DialogFooter:"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",DialogTitle:"text-lg font-semibold leading-none tracking-tight",DialogDescription:"text-sm text-muted-foreground"},props:{open:{schema:zod.z.unknown().optional(),design:"plumbing"},onOpenChange:{schema:zod.z.unknown().optional(),design:"event",description:"Radix controlled/uncontrolled pattern"},asChild:{schema:zod.z.enum(["wrap a Button"]).optional(),design:"plumbing"},surface:{schema:zod.z.enum(["solid","translucent","glass","glass-strong"]).optional(),design:"knob",description:"what the modal panel is *made of*. Defaults to `solid` (opaque `bg-background`). `glass` lets the page show through softly \u2014 pairs with rich backdrops or AI-suggestion modals."},accepts:{schema:zod.z.unknown().optional(),design:"plumbing"},used:{schema:zod.z.unknown().optional(),design:"plumbing"}}};var He={name:"DropdownMenu",description:'A small action menu attached to a trigger \u2014 overflow "\u2026" buttons on cards, user-avatar menus in headers, "Insert" menus in editors. For a full searchable list, use Command. For ONE primary action plus a secondary, use a Button next to a smaller ghost Button instead of a dropdown.',import:"@gradeui/ui",aliases:["dropdown","dropdown menu","overflow menu","kebab menu","more menu","action menu","context-style menu","menu","pull-down menu","pulldown menu","context menu","popup menu","actions menu","glass menu","frosted menu","ios menu","hig menu"],subcomponents:["DropdownMenuTrigger","DropdownMenuContent","DropdownMenuItem","DropdownMenuCheckboxItem","DropdownMenuRadioGroup","DropdownMenuRadioItem","DropdownMenuLabel","DropdownMenuSeparator","DropdownMenuShortcut","DropdownMenuGroup","DropdownMenuSub","DropdownMenuSubTrigger","DropdownMenuSubContent"],composesWith:["Button (as trigger asChild)","Avatar (user menu)","Card (overflow on a tile)","Tooltip (on the trigger)"],styleDefaults:{DropdownMenuSubContent:"z-50 min-w-[8rem] overflow-hidden rounded-md border text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",DropdownMenuContent:"z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border text-popover-foreground shadow-md",DropdownMenuItem:"relative flex cursor-default select-none items-center gap-2 rounded-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground focus:[&_svg]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:shrink-0",DropdownMenuCheckboxItem:"relative flex cursor-default select-none items-center rounded-sm pr-2 outline-none transition-colors focus:bg-accent focus:text-accent-foreground focus:[&_svg]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",DropdownMenuRadioItem:"relative flex cursor-default select-none items-center rounded-sm pr-2 outline-none transition-colors focus:bg-accent focus:text-accent-foreground focus:[&_svg]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",DropdownMenuLabel:"font-semibold",DropdownMenuSeparator:"-mx-1 my-1 h-px bg-muted",DropdownMenuShortcut:"ml-auto text-xs tracking-widest opacity-60"},props:{open:{schema:zod.z.unknown().optional(),design:"plumbing"},defaultOpen:{schema:zod.z.unknown().optional(),design:"plumbing"},onOpenChange:{schema:zod.z.unknown().optional(),design:"event"},modal:{schema:zod.z.unknown().optional(),design:"plumbing"},asChild:{schema:zod.z.boolean().optional(),design:"plumbing",description:"usually wraps a Button"},align:{schema:zod.z.enum(["start","center","end"]).optional(),design:"knob"},side:{schema:zod.z.enum(["top","right","bottom","left"]).optional(),design:"knob"},sideOffset:{schema:zod.z.number().optional(),design:"knob"},surface:{schema:zod.z.enum(["solid","translucent","glass","glass-strong"]).optional(),design:"knob",description:"what the menu surface is *made of*. `solid` (default) is `bg-popover`. `translucent` matches Apple HIG / iOS menu sheets. `glass` for menus floating over rich canvases."},size:{schema:zod.z.enum(["default","sm","xs"]).optional(),design:"knob",description:'menu density; cascades to every item (Item, Checkbox, Radio, SubTrigger, Label) via context so a compact trigger gets a compact menu. Use "xs" in dense tool panels.'},onSelect:{schema:zod.z.unknown().optional(),design:"event"},disabled:{schema:zod.z.unknown().optional(),design:"plumbing"},inset:{schema:zod.z.unknown().optional(),design:"plumbing"},DropdownMenuCheckboxItem:{schema:zod.z.unknown(),design:"plumbing"},DropdownMenuSub:{schema:zod.z.unknown(),design:"plumbing",description:"sub-trigger shows children, sub-content holds the deeper items"},children:{schema:zod.z.unknown().optional(),design:"plumbing",description:"right-aligned kbd hint"}}};var Ve={name:"Field",description:'Pair a bare control with a label and optional description in a row, with id + aria-describedby wired automatically. Use layout="setting" for the classic settings row (label on the left, Switch on the right). For a selectable CARD where the whole surface is the control, use RadioCard / CheckboxCard / SwitchCard instead.',import:"@gradeui/ui",aliases:["field","form field","control row","label and description","two line checkbox","option row","setting row","toggle row"],composesWith:["Checkbox","RadioGroup","RadioGroupItem","Switch","Badge (inside Field.Trailing)"],styleDefaults:{FieldLabel:"text-sm font-medium leading-none text-foreground cursor-pointer peer-disabled:cursor-not-allowed peer-disabled:opacity-70",FieldDescription:"text-sm text-muted-foreground",FieldTrailing:"flex shrink-0 items-center gap-2",FieldRoot:"flex gap-3"},props:{layout:{schema:zod.z.enum(["option","setting"]).optional(),design:"knob",description:"option (default): control leads, text beside it; setting: text leads, control pinned trailing"},children:{schema:zod.z.unknown(),design:"plumbing",description:"order does not matter"}}};var qe={name:"FillPicker",description:`Grade's paint picker \u2014 the control for choosing a frame's background fill, modelled on Figma's fill popover. A fill-type icon row (solid \xB7 gradient \xB7 image \xB7 pattern \xB7 video \xB7 shader) switches the panel below; a global opacity sits at the foot. Emits a FillValue that maps 1:1 onto BackgroundFill props. This is a Studio/inspector chrome control \u2014 pair it with BackgroundFill, which renders the chosen paint. Not for app content. Use the FillSection subcomponent to edit a LIST of fills (the Figma "Fill" inspector section): each row is a Solid/Gradient/Image toggle, the matching value control (ColorPicker / GradientEditor popover / image URL), an opacity %, a visibility eye, and a remove button, with an add button in the header.`,import:"@gradeui/ui",aliases:["fill picker","paint picker","background picker","fill chooser","fill popover","fill section","fill list","fills inspector","paint section"],subcomponents:["FillSection"],composesWith:["BackgroundFill (renders the FillValue)","Popover (host it in a popover)","ColorPicker (the solid value)","GradientEditor (the gradient value)","ShaderPresetPicker (the shader tab)","the inspector Fill section"],styleDefaults:{Swatch:"h-7 w-7 rounded-md border border-border/60 transition-shadow",FillPicker:"flex flex-col gap-3",FillSection:"flex flex-col gap-2"},props:{value:{schema:zod.z.unknown(),design:"plumbing",description:"current paint ({ type, color?, gradient?, src?, fit?, repeat?, tileSize?, preset?, palette?, postPreset?, opacity? }) (required)"},onChange:{schema:zod.z.unknown(),design:"event",description:"called on any change (required)"},title:{schema:zod.z.string().optional(),design:"plumbing",description:'section heading (default "Fills")'}}};var Ue={name:"Flex",description:"The unopinionated flexbox primitive \u2014 reach for Flex when Stack, Row, or Grid don't quite fit. Specifically when you need reverse direction (`row-reverse` / `col-reverse`), CSS defaults instead of Row's baked-in `items-center gap-md`, or baseline alignment. Otherwise prefer Stack / Row / Grid \u2014 they're easier to read and tuned for the 95% case. Flex is the escape hatch, not the default.",import:"@gradeui/ui",aliases:["flex","flexbox","flex container","hstack","vstack","horizontal","vertical","generic container","layout view"],composesWith:["any content component"],props:{direction:{schema:zod.z.enum(["row","col","row-reverse","col-reverse"]).optional(),design:"knob",description:"main-axis direction",default:"row"},gap:{schema:zod.z.enum(["none","xs","sm","md","lg","xl","2xl"]).optional(),design:"knob",description:"gap between children",default:"none"},align:{schema:zod.z.enum(["start","center","end","stretch","baseline"]).optional(),design:"knob",description:"cross-axis alignment",default:"stretch"},justify:{schema:zod.z.enum(["start","center","end","between","around","evenly"]).optional(),design:"knob",description:"main-axis distribution",default:"start"},wrap:{schema:zod.z.enum(["nowrap","wrap","wrap-reverse"]).optional(),design:"knob",description:"wrap behaviour when children overflow",default:"nowrap"},asChild:{schema:zod.z.boolean().optional(),design:"plumbing",description:"render as the child element via Slot",default:false},className:{schema:zod.z.string().optional(),design:"plumbing"},children:{schema:zod.z.unknown(),design:"plumbing"}}};var Ke={name:"GradientEditor",description:'Edit a multi-stop CSS gradient with token-led stops. A type Select (Linear / Radial / Angular) with reverse + rotate actions, a live full-width preview bar (a Swatch type="gradient"), then a Stops list where each stop is a position %, a colour (ColorPicker token or raw), an opacity %, and a remove button; an add button appends a stop. Token stops resolve to oklch(var(--<token>)) so the preview re-voices with the theme. Emits the structured GradientValue (kept editable + serialisable); the caller turns it into CSS with the exported gradientToCss(value). Use inside a Popover from a FillSection gradient row, or standalone in a theme builder. For a single solid colour use ColorPicker; for a full paint (solid / gradient / image / shader) use FillPicker.',import:"@gradeui/ui",aliases:["gradient editor","gradient picker","gradient builder","css gradient editor","stop editor","gradient stops","linear gradient editor","conic gradient editor"],composesWith:["Select","Button","Input","ColorPicker","Swatch","Popover","FillSection"],styleDefaults:{GradientEditor:"flex flex-col gap-3"},props:{value:{schema:zod.z.unknown(),design:"plumbing",description:"the structured gradient (type linear/radial/angular, optional angle in deg, ordered stops). NOT a CSS string \u2014 render the string via gradientToCss(value)."},onChange:{schema:zod.z.unknown(),design:"event",description:"fired with the next structured gradient on any edit"}}};var Ye={name:"Grid",description:"2D layouts where Stack (vertical) and Row (horizontal) don't fit \u2014 stat-card grids, feature tiles, pricing columns, photo grids. Reach for Grid over hand-rolled `grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4` so the column count is a prop the settings panel can mutate and the responsive ladder stays consistent across designs.",import:"@gradeui/ui",aliases:["grid","tiles","cards grid","stat grid","columns","feature grid","grid view","lazy v grid","lazyvgrid","lazy h grid","lazyhgrid","tile grid","masonry"],composesWith:["Card","Stack (inside each cell)","Row","Button","any content component"],styleDefaults:{Grid:"gds-grid grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 items-stretch"},variantDefaults:{cols:"3",gap:"md",align:"stretch"},props:{cols:{schema:zod.z.enum(["1","2","3","4","5","6","12"]).optional(),design:"knob",description:'desktop column count; each value has a baked-in responsive ladder (e.g. "4" \u2192 1 col mobile, 2 tablet, 4 desktop)',default:"3"},gap:{schema:zod.z.enum(["none","xs","sm","md","lg","xl","2xl"]).optional(),design:"knob",description:"gap between grid cells (same scale as Stack/Row)",default:"md"},align:{schema:zod.z.enum(["start","center","end","stretch"]).optional(),design:"knob",description:"cross-axis alignment of cells",default:"stretch"},asChild:{schema:zod.z.boolean().optional(),design:"plumbing",description:"render as the child element via Slot",default:false},className:{schema:zod.z.string().optional(),design:"plumbing"},children:{schema:zod.z.unknown(),design:"plumbing"}}};var Je={name:"HoverCard",description:"Rich preview content surfaced on hover \u2014 user profile mini-cards on @-mentions, link previews, definition popups, layer-thumbnail peeks. Pointer-only by design (no touch-friendly trigger); pair with a click target for touch devices, or fall back to Popover. NEVER use HoverCard for critical info \u2014 if the user can't reach it via keyboard or touch, it might as well not exist for accessibility.",import:"@gradeui/ui",aliases:["hover card","hover preview","mention preview","profile peek","link preview","rich tooltip","link preview card","profile hover","peek card","glass preview","frosted preview"],subcomponents:["HoverCardTrigger","HoverCardContent"],composesWith:["Avatar (user preview)","Card (richer content)","Link (the trigger)","MediaSurface (link/layer previews)","Code (snippet previews)"],styleDefaults:{HoverCardContent:"z-50 w-64 rounded-md border p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-hover-card-content-transform-origin]"},props:{open:{schema:zod.z.unknown().optional(),design:"plumbing"},defaultOpen:{schema:zod.z.unknown().optional(),design:"plumbing"},onOpenChange:{schema:zod.z.unknown().optional(),design:"event"},openDelay:{schema:zod.z.unknown().optional(),design:"plumbing"},closeDelay:{schema:zod.z.unknown().optional(),design:"plumbing"},asChild:{schema:zod.z.boolean().optional(),design:"plumbing",description:"usually a Link or Button"},side:{schema:zod.z.unknown().optional(),design:"plumbing"},align:{schema:zod.z.unknown().optional(),design:"plumbing"},sideOffset:{schema:zod.z.unknown().optional(),design:"plumbing"},alignOffset:{schema:zod.z.unknown().optional(),design:"plumbing"},className:{schema:zod.z.unknown().optional(),design:"plumbing"},surface:{schema:zod.z.enum(["solid","translucent","glass","glass-strong"]).optional(),design:"knob",description:"what the preview surface is *made of*. `solid` (default) is `bg-popover`. `glass` for hover previews over rich content (a media feed, a layout canvas)."}}};var Xe={name:"Input",description:'Any single-line text entry. Always pair with a Label for accessibility. Use startSlot/endSlot for icons, prefixes and units instead of hand-positioning absolute children; use size="sm"/"xs" in dense tool panels.',import:"@gradeui/ui",aliases:["text field","textbox","textfield","form field","text input","secure field","search field","url field","number field","textinput","text input field","react native textinput","unit input","input with icon"],composesWith:["Label","Form","Card (in CardContent)","Button (form submit)"],styleDefaults:{Input:"pointer-events-none absolute inset-y-0 left-0 flex items-center text-muted-foreground [&_svg]:size-3.5"},props:{type:{schema:zod.z.string().optional(),design:"content"},placeholder:{schema:zod.z.string().optional(),design:"content",description:"hint text shown while the input is empty. Model it explicitly (not just a native passthrough) so generated screens carry placeholders and the validator accepts them."},size:{schema:zod.z.enum(["default","sm","xs"]).optional(),design:"knob",description:"control density. `default` (h-9) for forms; `sm` (h-8) and `xs` (h-7) for dense tool panels like the inspector. NOTE: pre-unification scale \u2014 see Figma parity audit; due to migrate to the t-shirt scale (xs 24 | sm 28 | md 32 | lg 40, default\u2192md)."},startSlot:{schema:zod.z.unknown().optional(),design:"plumbing",description:"adornment rendered inside the leading edge (icon, prefix, currency symbol). Non-interactive by default so clicks focus the input."},endSlot:{schema:zod.z.unknown().optional(),design:"plumbing",description:'adornment rendered inside the trailing edge (unit like "px", a clear button, a stepper). Same pointer rules as startSlot.'}}};var Ze={name:"Label",description:'Every Input / Textarea / Checkbox / Switch / RadioGroup. Always use htmlFor so clicking the label focuses the control. Match `size` to the field it labels (size="xs" label over a size="xs" input).',import:"@gradeui/ui",aliases:["label","form label","field label","caption"],composesWith:["Input","Textarea","Checkbox","Switch","RadioGroup","Select"],styleDefaults:{Label:"font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-sm"},variantDefaults:{size:"default"},props:{htmlFor:{schema:zod.z.string().optional(),design:"content",description:"binds to the input's id"},size:{schema:zod.z.enum(["default","sm","xs"]).optional(),design:"knob",description:"text size, mirrors Input/Select/Textarea so a field and its label scale together. default = text-sm; xs = 11px for dense tool panels."}}};var $e={name:"Logo",description:"ALWAYS use <Logo> wherever a screen carries a brand mark \u2014",import:"@gradeui/ui",aliases:["logo","brand","brandmark","wordmark","lockup","brand logo","app logo","logotype","grade mark","g arrow"],composesWith:["AppShell","AppShellHeader","Sidebar","SidebarHeader","Toolbar","MotionOverlay","Row","Stack"],styleDefaults:{Logo:"gds-logo inline-flex shrink-0 select-none items-center"},props:{sources:{schema:zod.z.unknown().optional(),design:"plumbing",description:"artwork keyed by lockup then appearance: { square?: { light?, dark?, mono? }, horizontal?: {...}, icon?: {...} }. Each slot is any node (inline <svg>, <img>, component). OMIT ENTIRELY and the GRADE MARK renders (the square G-arrow, painted with currentColor so it sits correctly on any surface). A bare <Logo /> is always correct branding."},size:{schema:zod.z.enum(["sm","md","lg","xl"]).optional(),design:"knob",description:'height of the mark: 20 / 28 / 40 / 56px (a raw pixel number also works). Width is intrinsic (square/icon 1:1, horizontal keeps its ratio). Default "md"; use "sm" in dense rails and toolbars.'},lockup:{schema:zod.z.enum(["square","horizontal","icon"]).optional(),design:"knob",description:'which shape to show; falls back to the next-best available artwork. Default "horizontal".'},mode:{schema:zod.z.enum(["light","dark"]).optional(),design:"knob",description:'the background the logo SITS ON, selecting light/dark artwork. Explicit, not theme-coupled. Default "light".'},mono:{schema:zod.z.boolean().optional(),design:"knob",description:"render the single-colour treatment; inherits currentColor from the parent. Default false."},label:{schema:zod.z.string().optional(),design:"content",description:'accessible name (aria-label + role="img"); pair with decorative when the brand name is already beside it.'},decorative:{schema:zod.z.boolean().optional(),design:"knob",description:"aria-hidden, no role; use when text nearby names the brand."},href:{schema:zod.z.string().optional(),design:"content",description:"renders the logo as a link (logo-links-home)."},className:{schema:zod.z.string().optional(),design:"plumbing"}}};var et={name:"Map",description:"Any layout that needs a real map \u2014 listings (real estate, Airbnb-style), fleet/logistics dashboards, store locators, anywhere a user picks a location from a viewport. Reach for the controlled `hoveredId` prop when a sibling list and the map need to highlight each other.",import:"@gradeui/ui",aliases:["map","maps","mapbox","maplibre","google maps","geo","location","latlng","coordinates","marker","pin","airbnb","listings","fleet","real estate","logistics","map view","mapkit","mapview","react native maps","rn maps"],subcomponents:["MapMarker"],composesWith:["Card (as marker content)","Badge","Avatar","Button","Row","Stack","Skeleton"],props:{provider:{schema:zod.z.unknown(),design:"plumbing",description:'"maplibre" (default, free, no key) | "mapbox" (needs accessToken) | "google" (needs apiKey). Switching is one prop change.'},center:{schema:zod.z.unknown(),design:"plumbing",description:"`[lng, lat]` tuple. ALWAYS lng first. Required."},zoom:{schema:zod.z.unknown(),design:"plumbing",description:"number, 0\u201322. Required."},bounds:{schema:zod.z.unknown(),design:"plumbing",description:"`[[swLng, swLat], [neLng, neLat]]`. When set, takes precedence over center+zoom."},appearance:{schema:zod.z.unknown(),design:"plumbing",description:'"light" | "dark" | "satellite" | "auto" (default "auto", follows GradeThemeProvider mode).'},hoveredId:{schema:zod.z.unknown(),design:"plumbing",description:'controlled string id, pairs with onHoveredIdChange. The matching MapMarker gets `data-gds-state="hovered"` automatically. This is how you build list \u2194 map two-way sync.'},onHoveredIdChange:{schema:zod.z.unknown(),design:"event",description:"`(id: string | null) => void`. The other half of the controlled hover pair: fires when the pointer enters/leaves a marker so the sibling list can highlight in step. Without this entry in the contract, screens using the documented two-way sync fail save validation."},interactive:{schema:zod.z.unknown(),design:"plumbing",description:"false freezes pan/zoom, useful for static cards."},tools:{schema:zod.z.unknown(),design:"plumbing",description:'"auto" (default, zoom buttons follow `interactive`) | "zoom" (force zoom buttons) | "none" (chrome-free map; attribution always stays \u2014 license). One vocabulary across all providers.'},toolsPosition:{schema:zod.z.unknown(),design:"plumbing",description:'"top-left" (default) | "top-right" | "bottom-left" | "bottom-right". Corner the tools dock to; use when a search bar or legend sits over the default corner.'},onLoad:{schema:zod.z.unknown(),design:"event",description:"handle exposes flyTo, panTo, fitBounds, getCenter, getZoom, getBounds, instance."},tilerKey:{schema:zod.z.unknown().optional(),design:"plumbing",description:'MapLibre only (provider="maplibre"). Optional everywhere: omit on `gradeui.com`/`localhost` and the referrer-locked demo key is used; set it only when embedding off-domain. The contract never requires it.'},accessToken:{schema:zod.z.unknown().optional(),design:"plumbing",description:'Mapbox only. Pass it whenever provider="mapbox" \u2014 the component itself enforces this at runtime (throws a clear `provider="mapbox" requires an accessToken prop` error via onError if missing). It is OPTIONAL in the contract on purpose, so the validator never demands it from maplibre/google maps.'},apiKey:{schema:zod.z.unknown().optional(),design:"plumbing",description:'Google only. Pass it whenever provider="google" \u2014 the component enforces it at runtime (throws `provider="google" requires an apiKey prop` via onError if missing). OPTIONAL in the contract on purpose, so it\'s never demanded from maplibre/mapbox.'},id:{schema:zod.z.unknown().optional(),design:"plumbing",description:"string. Required. Stable marker id; pair with Map's `hoveredId` for list\u2194map hover sync."},at:{schema:zod.z.unknown().optional(),design:"plumbing",description:"`[lng, lat]` tuple. Required. THE coordinate prop. ALWAYS lng first. The prop is literally named `at` \u2014 it is NOT `lngLat`, `coordinates`, `position`, `latLng`, `center`, or separate `lng`/`lat` props. Passing any other name leaves the marker coord `undefined`, and MapLibre throws on mount, crashing the WHOLE screen in every renderer. When in doubt, copy the `airbnb-listings` scaffold: `<MapMarker id={l.id} at={l.coords}>`."},anchor:{schema:zod.z.unknown().optional(),design:"plumbing",description:'"center" | "bottom" (default "bottom", pin tip sits on the coord). Only these two values.'},onClick:{schema:zod.z.unknown().optional(),design:"event",description:"handler called with `({ id, coords, native })` on marker click."},children:{schema:zod.z.unknown().optional(),design:"plumbing",description:"DOM rendered as the marker (Badge, Card, Avatar, or any element). Inherits `--gds-*` tokens."}}};var _t=zod.z.object({kind:zod.z.literal("album"),artist:zod.z.string(),title:zod.z.string(),year:zod.z.number().optional(),description:zod.z.string().optional()}),Kt=zod.z.object({kind:zod.z.literal("tv-show"),title:zod.z.string(),year:zod.z.number().optional(),description:zod.z.string().optional()}),Yt=zod.z.object({kind:zod.z.literal("movie"),title:zod.z.string(),year:zod.z.number().optional(),description:zod.z.string().optional()}),Jt=zod.z.object({kind:zod.z.literal("game"),title:zod.z.string(),description:zod.z.string().optional()}),Xt=zod.z.object({kind:zod.z.literal("book"),title:zod.z.string().optional(),author:zod.z.string().optional(),isbn:zod.z.string().optional(),description:zod.z.string().optional()}),Qt=zod.z.object({kind:zod.z.literal("poster"),title:zod.z.string(),year:zod.z.number().optional(),description:zod.z.string().optional()}),Zt=zod.z.object({kind:zod.z.literal("portrait"),name:zod.z.string().optional(),role:zod.z.string().optional()}),$t=zod.z.object({kind:zod.z.literal("landscape"),location:zod.z.string().optional(),mood:zod.z.string().optional()}),eo=zod.z.object({kind:zod.z.literal("product"),name:zod.z.string().optional(),brand:zod.z.string().optional()}),to=zod.z.object({kind:zod.z.literal("food"),dish:zod.z.string().optional(),cuisine:zod.z.string().optional()}),oo=zod.z.object({kind:zod.z.literal("generic"),prompt:zod.z.string()}),no=zod.z.union([zod.z.object({kind:zod.z.literal("video")}),zod.z.object({kind:zod.z.literal("audio")}),zod.z.object({kind:zod.z.literal("embed")}),zod.z.object({kind:zod.z.literal("3d")})]),io=zod.z.union([_t,Kt,Yt,Jt,Xt,Qt,Zt,$t,eo,to,oo,no]),ao=zod.z.enum(["video","square","portrait","wide","auto"]),ro=zod.z.enum(["none","sm","md","lg","xl"]),so=zod.z.enum(["album","tv-show","movie","game","book","portrait","landscape","poster","product","food","video","audio","embed","3d","generic"]),lo=zod.z.union([zod.z.literal("auto"),zod.z.literal("icon"),zod.z.literal("none")]),tt={name:"MediaSurface",description:"The canonical media slot for ALL non-person imagery \u2014 album art, posters, hero images, landscape photos, video and 3D containers.",when:"Pass `hint` + `alt` + (optionally) `source` so the empty-state placeholder is meaningful and the generation pipeline can later fill the slot with a real image. Use directly for declarative slots; the higher-level VideoPlayer / RivePlayer / ThreeScene wrap this for runtime-heavy media.",antipatterns:["Don't wrap <Avatar> inside <MediaSurface> to get an initials fallback. Set `alt` + `hint` on MediaSurface directly \u2014 the placeholder renders initials at small sizes derived from `alt`.","Don't use <Avatar> for album art, posters, products, food, landscapes, etc. Avatar is for PEOPLE only.","Don't inline manual gradient backgrounds (`bg-gradient-to-br \u2026`) on MediaSurface as a 'placeholder vibe' \u2014 the empty-state is already styled via `--gds-media-placeholder-bg/-fg` tokens."],composesWith:["Card","CardBlock","MediaBlock","VideoPlayer","RivePlayer","ThreeScene"],aliases:["media","image slot","media slot","image placeholder","cover","thumbnail","poster slot"],import:"@gradeui/ui",props:{hint:{schema:so.optional(),design:"knob",group:"image",control:"glyph-picker",label:"Slot kind",description:"Picks the placeholder glyph + the default aspect + the future generation provider. Defaults to 'generic'.",default:"generic",examples:["album","portrait","landscape","poster"]},aspect:{schema:ao.optional(),design:"knob",control:"toggle-group",label:"Aspect ratio",description:"Override the slot's natural framing. When omitted, derived from `hint`: album/product/food \u2192 square, portrait/poster \u2192 portrait, landscape \u2192 wide, video/audio/embed/generic \u2192 video."},radius:{schema:ro.optional(),design:"knob",control:"toggle-group",label:"Corner radius",default:"none",description:"Driven by the `--gds-media-radius` CSS variable. Defaults to `none` so the slot sits flush \u2014 set `lg`/`xl` for a standalone rounded image. (A media slot mounted flush at the top of a Card should stay square and let the Card clip it.)"},border:{schema:zod.z.boolean().optional(),design:"knob",label:"Show border",default:false},loading:{schema:zod.z.boolean().optional(),design:"knob",label:"Loading state",default:false,description:"Overlays the muted-pulse skeleton on top of the slot."},emptyState:{schema:lo.optional(),design:"knob",control:"select",label:"Empty state",default:"auto",description:"'auto' renders the size-tiered placeholder (initials \u2192 glyph \u2192 glyph + caption). 'icon' is a legacy alias. 'none' renders a truly empty surface."},alt:{schema:zod.z.string().optional(),design:"content",group:"image",control:"text",label:"Alt text",description:"Becomes the eventual `<img alt>`. Also drives the placeholder caption (>160px slots) and the 2-letter initials fallback (<64px slots).",examples:["Travelling Without Moving \u2014 Jamiroquai","Sunset over Mount Fuji"]},src:{schema:zod.z.string().url().optional(),design:"content",group:"image",control:"url",label:"Image URL",description:"When set, renders an `<img>` filling the slot via object-cover. The wrapper keeps its aspect/radius/border. Generators patch this prop; manual values always win."},instanceId:{schema:zod.z.string().optional(),design:"content",group:"image",control:"text",label:"Instance id",description:"Stable per-instance id stamped as `data-gds-instance-id`. Use when rendering MediaSurfaces from a data array (`.map(item => <MediaSurface instanceId={item.id} \u2026/>)`) \u2014 it's how Studio's selection + Fill flows tell one card apart from its siblings and patch only that entry. Was missing from this hand-authored contract while the component documented it, which made save validation reject the documented pattern (June 2026)."},source:{schema:io.optional(),design:"structured",label:"Source descriptor",description:"Structured metadata for the generation pipeline. Opaque to MediaSurface itself; read by the resolver to look up real imagery from the right provider (MusicBrainz / Pollinations / etc.).",perKindFields:{album:{artist:"string",title:"string",year:"number?"},poster:{title:"string",year:"number?"},portrait:{name:"string?",role:"string?"},landscape:{location:"string?",mood:"string?"},product:{name:"string?",brand:"string?"},food:{dish:"string?",cuisine:"string?"},generic:{prompt:"string"},video:{},audio:{},embed:{},"3d":{}}},className:{schema:zod.z.string().optional(),design:"plumbing"},style:{schema:zod.z.record(zod.z.string(),zod.z.unknown()).optional(),design:"plumbing"},children:{schema:zod.z.unknown().optional(),design:"plumbing",description:"Escape hatch for putting a custom <video>, <canvas>, Rive runtime, etc. inside. When supplied, the placeholder is suppressed."},overlay:{schema:zod.z.unknown().optional(),design:"plumbing",description:"Decorative layer rendered ABOVE the media/placeholder (play buttons, hover gradients, corner badges). Does NOT suppress the placeholder."},glyph:{schema:zod.z.unknown().optional(),design:"plumbing",description:"Per-instance override of the hint-derived placeholder glyph. Most consumers should pick a `hint` and let the map decide."},fallback:{schema:zod.z.unknown().optional(),design:"plumbing",description:"Custom node shown while `loading` is true."},onVisibilityChange:{schema:zod.z.function().optional(),design:"event",description:"Fires when the surface enters / leaves the viewport (IntersectionObserver)."}},actions:{fill:{label:"Fill image",icon:"Sparkles",description:"Resolve this slot's source via the free providers (MusicBrainz \u2192 Pollinations \u2192 Picsum) and patch the result into the runtime URL map.",kind:"resolve-media-source",enabledWhen:{propPresent:"source"}}}};var ot={name:"Message",description:`The canonical "avatar + author + timestamp + body" row. THE PRIMITIVE
|
|
47
47
|
for any chat surface, comment thread, post-reply, activity log, or
|
|
48
48
|
notification feed that follows the people-and-text shape.
|
|
49
49
|
|