@kznlabs/labsui 0.1.12 → 0.1.14
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/README.md +5 -3
- package/dist/index.js +283 -60
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13148,7 +13148,7 @@ export function AdminLayout({ children }: { children: React.ReactNode }) {
|
|
|
13148
13148
|
</main>
|
|
13149
13149
|
</SidebarLayout>
|
|
13150
13150
|
);
|
|
13151
|
-
}` }], api: { entries: [{ kind: "component", name: "SidebarLayout", props: [{ name: "collapsed", type: "boolean", required: false, description: "Controlled rail state; pair with `onCollapsedChange`." }, { name: "defaultCollapsed", type: "boolean", required: false, default: "false", description: "Start as the icon rail (uncontrolled)." }, { name: "onCollapsedChange", type: "(collapsed: boolean) => void", required: false, description: "Called from the trigger or the shortcut." }, { name: "shortcut", type: "boolean", required: false, default: "true", description: "Toggle with ⌘B / Ctrl+B (default: true). Ignored while typing in a field." }], extends: "<div>", description: "Lays the sidebar and the page side by side and holds the rail state. Below md, the sidebar is a left drawer." }, { kind: "component", name: "Sidebar", props: [{ name: "groups", type: "SidebarGroup[]", required: true, description: "The navigation, top to bottom." }, { name: "current", type: "string", required: false, description: 'Id of the current item: highlighted, `aria-current="page"` on links.' }, { name: "footer", type: "SidebarItem[]", required: false, description: "Entries pinned to the bottom, e.g. settings." }, { name: "header", type: "ReactNode", required: false, description: "Top slot, e.g. the workspace name." }, { name: "label", type: "string", required: false, default: '"Sidebar"', description: "Accessible name for the navigation landmark." }, { name: "onSelect", type: "(id: string) => void", required: false, description: "Called with an item's id when it is picked (links too, e.g. for client routing)." }], description: "Navigation sidebar from data. Collapses to an icon rail on a spring; labels clip but stay in the a11y tree." }, { kind: "component", name: "SidebarTrigger", props: [] }], types: { SidebarItem: "{ id?: string; label: string; icon?: ReactNode; href?: string; badge?: ReactNode; }", SidebarGroup: "{ label?: string; items: SidebarItem[]; collapsible?: boolean; defaultOpen?: boolean; }" } }, dependencies: ["lucide-react@1.63.0", "motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/button.json", "https://kznlabs.github.io/labsUI/r/collapsible.json", "https://kznlabs.github.io/labsUI/r/drawer.json", "https://kznlabs.github.io/labsUI/r/platform.json", "https://kznlabs.github.io/labsUI/r/use-controllable-state.json", "https://kznlabs.github.io/labsUI/r/use-hotkey.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/sidebar.tsx", type: "registry:ui", content: `"use client";
|
|
13151
|
+
}` }], api: { entries: [{ kind: "component", name: "SidebarLayout", props: [{ name: "collapsed", type: "boolean", required: false, description: "Controlled rail state; pair with `onCollapsedChange`." }, { name: "defaultCollapsed", type: "boolean", required: false, default: "false", description: "Start as the icon rail (uncontrolled)." }, { name: "onCollapsedChange", type: "(collapsed: boolean) => void", required: false, description: "Called from the trigger or the shortcut." }, { name: "shortcut", type: "boolean", required: false, default: "true", description: "Toggle with ⌘B / Ctrl+B (default: true). Ignored while typing in a field." }], extends: "<div>", description: "Lays the sidebar and the page side by side and holds the rail state. Below md, the sidebar is a left drawer." }, { kind: "component", name: "Sidebar", props: [{ name: "groups", type: "SidebarGroup[]", required: true, description: "The navigation, top to bottom." }, { name: "current", type: "string", required: false, description: 'Id of the current item: highlighted, `aria-current="page"` on links.' }, { name: "footer", type: "SidebarItem[]", required: false, description: "Entries pinned to the bottom, e.g. settings." }, { name: "header", type: "ReactNode", required: false, description: "Top slot, e.g. the workspace name." }, { name: "label", type: "string", required: false, default: '"Sidebar"', description: "Accessible name for the navigation landmark." }, { name: "onSelect", type: "(id: string) => void", required: false, description: "Called with an item's id when it is picked (links too, e.g. for client routing)." }], description: "Navigation sidebar from data. Collapses to an icon rail on a spring; labels clip but stay in the a11y tree." }, { kind: "component", name: "SidebarTrigger", props: [] }], types: { SidebarItem: "{ id?: string; label: string; icon?: ReactNode; href?: string; badge?: ReactNode; }", SidebarGroup: "{ label?: string; items: SidebarItem[]; collapsible?: boolean; defaultOpen?: boolean; }" } }, dependencies: ["lucide-react@1.63.0", "motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/button.json", "https://kznlabs.github.io/labsUI/r/collapsible.json", "https://kznlabs.github.io/labsUI/r/drawer.json", "https://kznlabs.github.io/labsUI/r/kbd.json", "https://kznlabs.github.io/labsUI/r/platform.json", "https://kznlabs.github.io/labsUI/r/tooltip.json", "https://kznlabs.github.io/labsUI/r/use-controllable-state.json", "https://kznlabs.github.io/labsUI/r/use-hotkey.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/sidebar.tsx", type: "registry:ui", content: `"use client";
|
|
13152
13152
|
|
|
13153
13153
|
import { PanelLeft } from "lucide-react";
|
|
13154
13154
|
import { motion } from "motion/react";
|
|
@@ -13156,7 +13156,9 @@ import { type ComponentProps, createContext, type ReactNode, use, useState } fro
|
|
|
13156
13156
|
import { Button } from "@/components/ui/button";
|
|
13157
13157
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
|
13158
13158
|
import { Drawer, DrawerContent, DrawerTitle } from "@/components/ui/drawer";
|
|
13159
|
-
import {
|
|
13159
|
+
import { KbdGroup } from "@/components/ui/kbd";
|
|
13160
|
+
import { Tooltip } from "@/components/ui/tooltip";
|
|
13161
|
+
import { useIsMac, useMediaQuery } from "@/lib/platform";
|
|
13160
13162
|
import { useControllableState } from "@/lib/use-controllable-state";
|
|
13161
13163
|
import { useHotkey } from "@/lib/use-hotkey";
|
|
13162
13164
|
import { cn, spring } from "@/lib/utils";
|
|
@@ -13168,6 +13170,7 @@ type SidebarCtx = {
|
|
|
13168
13170
|
mobile: boolean;
|
|
13169
13171
|
openMobile: boolean;
|
|
13170
13172
|
setOpenMobile: (open: boolean) => void;
|
|
13173
|
+
shortcut: boolean;
|
|
13171
13174
|
};
|
|
13172
13175
|
|
|
13173
13176
|
const SidebarContext = createContext<SidebarCtx | null>(null);
|
|
@@ -13215,7 +13218,9 @@ export function SidebarLayout({
|
|
|
13215
13218
|
useHotkey(shortcut && "mod+b", () => setCollapsed(!collapsed), { skipInFields: true });
|
|
13216
13219
|
|
|
13217
13220
|
return (
|
|
13218
|
-
<SidebarContext
|
|
13221
|
+
<SidebarContext
|
|
13222
|
+
value={{ collapsed, setCollapsed, mobile, openMobile, setOpenMobile, shortcut }}
|
|
13223
|
+
>
|
|
13219
13224
|
<div className={cn("flex min-h-0 w-full", className)} {...props}>
|
|
13220
13225
|
{children}
|
|
13221
13226
|
</div>
|
|
@@ -13413,21 +13418,31 @@ function Entry({
|
|
|
13413
13418
|
}
|
|
13414
13419
|
|
|
13415
13420
|
export function SidebarTrigger({ className }: { className?: string }) {
|
|
13416
|
-
const { collapsed, setCollapsed, mobile, openMobile } = useSidebar();
|
|
13421
|
+
const { collapsed, setCollapsed, mobile, openMobile, shortcut } = useSidebar();
|
|
13422
|
+
const mac = useIsMac();
|
|
13417
13423
|
const expanded = mobile ? openMobile : !collapsed;
|
|
13418
13424
|
|
|
13419
|
-
|
|
13425
|
+
const button = (
|
|
13420
13426
|
<Button
|
|
13421
13427
|
variant="ghost"
|
|
13422
13428
|
size="icon"
|
|
13423
13429
|
aria-label={expanded ? "Collapse sidebar" : "Expand sidebar"}
|
|
13424
13430
|
aria-expanded={expanded}
|
|
13431
|
+
aria-keyshortcuts={shortcut ? (mac ? "Meta+B" : "Control+B") : undefined}
|
|
13425
13432
|
onClick={() => setCollapsed(!collapsed)}
|
|
13426
13433
|
className={cn("size-8", className)}
|
|
13427
13434
|
>
|
|
13428
13435
|
<PanelLeft aria-hidden="true" />
|
|
13429
13436
|
</Button>
|
|
13430
13437
|
);
|
|
13438
|
+
|
|
13439
|
+
return shortcut ? (
|
|
13440
|
+
<Tooltip content={<KbdGroup keys={mac ? ["⌘", "B"] : ["Ctrl", "B"]} />} side="bottom">
|
|
13441
|
+
{button}
|
|
13442
|
+
</Tooltip>
|
|
13443
|
+
) : (
|
|
13444
|
+
button
|
|
13445
|
+
);
|
|
13431
13446
|
}
|
|
13432
13447
|
` }] }, "site-header": { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", name: "site-header", type: "registry:block", title: "Site Header", description: "Sticky, translucent site header from one nav array: brand, NavigationMenu panels from md up, a right Drawer with the same links below it, and your actions on the right. The hairline underneath appears only once content scrolls beneath it.", docs: "https://kznlabs.github.io/labsUI/components/site-header", usage: '<SiteHeader brand={<a href="/">Acme</a>} current="/blog" nav={[{ label: "Products", items: [{ title: "Bot", href: "/bot", icon: <Bot /> }] }, { label: "Blog", href: "/blog" }]} actions={<ThemeToggle />} />', examples: [{ name: "header.tsx", code: `import { BookOpen, Gauge } from "lucide-react";
|
|
13433
13448
|
import { SiteHeader } from "@/components/blocks/site-header";
|
|
@@ -15886,7 +15901,7 @@ function Cursor() {
|
|
|
15886
15901
|
return (
|
|
15887
15902
|
<span
|
|
15888
15903
|
aria-hidden="true"
|
|
15889
|
-
className="ml-px inline-block h-[1.1em] w-[0.55em] translate-y-[0.2em] animate-pulse bg-foreground/70"
|
|
15904
|
+
className="ml-px inline-block h-[1.1em] w-[0.55em] translate-y-[0.2em] animate-pulse bg-foreground/70 motion-reduce:animate-none"
|
|
15890
15905
|
/>
|
|
15891
15906
|
);
|
|
15892
15907
|
}
|
|
@@ -16156,10 +16171,13 @@ export function DynamicTag() {
|
|
|
16156
16171
|
<TextSwap items={["Next.js 16", "Tailwind CSS 4", "motion 14"]} direction="up" />
|
|
16157
16172
|
</span>
|
|
16158
16173
|
);
|
|
16159
|
-
}` }], api: { entries: [{ kind: "component", name: "TextSwap", props: [{ name: "children", type: "string", required: false, description: "A single string; changing it swaps to the new text." }, { name: "direction", type: '"up" | "down" | "fade"', required: false, default: '"up"', description: 'Swap direction (default: "up").' }, { name: "index", type: "number", required: false, description: "Active index when controlling externally." }, { name: "interval", type: "number", required: false, default: "3000", description: "Milliseconds between swaps when cycling automatically (default: 3000)." }, { name: "items", type: "readonly string[]", required: false, description: "Array of items to cycle through, or pass a single changing child." }, { name: "pauseOnHover", type: "boolean", required: false, default: "true", description: "Pause the cycle on mouse hover (default: true)." }] }], types: {} }, dependencies: ["motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/text-swap.tsx", type: "registry:ui", content: `"use client";
|
|
16174
|
+
}` }], api: { entries: [{ kind: "component", name: "TextSwap", props: [{ name: "children", type: "string", required: false, description: "A single string; changing it swaps to the new text." }, { name: "controls", type: "boolean", required: false, default: "false", description: "Show a pause button after the text, so keyboard and touch users can stop it too (WCAG 2.2.2). With a mouse it shows on hover or focus. Use it when it cycles next to other content. Hidden with reduced motion." }, { name: "direction", type: '"up" | "down" | "fade"', required: false, default: '"up"', description: 'Swap direction (default: "up").' }, { name: "index", type: "number", required: false, description: "Active index when controlling externally." }, { name: "interval", type: "number", required: false, default: "3000", description: "Milliseconds between swaps when cycling automatically (default: 3000). With reduced motion it doesn't cycle." }, { name: "items", type: "readonly string[]", required: false, description: "Array of items to cycle through, or pass a single changing child." }, { name: "pauseLabel", type: "string", required: false, default: '"Pause animation"', description: 'Accessible name of the pause button (default: "Pause animation").' }, { name: "pauseOnHover", type: "boolean", required: false, default: "true", description: "Pause the cycle on mouse hover (default: true)." }] }], types: {} }, dependencies: ["lucide-react@1.63.0", "motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/button-variants.json", "https://kznlabs.github.io/labsUI/r/platform.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/text-swap.tsx", type: "registry:ui", content: `"use client";
|
|
16160
16175
|
|
|
16161
|
-
import {
|
|
16176
|
+
import { Pause, Play } from "lucide-react";
|
|
16177
|
+
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
16162
16178
|
import { useEffect, useState } from "react";
|
|
16179
|
+
import { buttonVariants } from "@/components/ui/button-variants";
|
|
16180
|
+
import { useMounted } from "@/lib/platform";
|
|
16163
16181
|
import { cn, spring } from "@/lib/utils";
|
|
16164
16182
|
|
|
16165
16183
|
type TextSwapProps = {
|
|
@@ -16167,12 +16185,16 @@ type TextSwapProps = {
|
|
|
16167
16185
|
items?: readonly string[];
|
|
16168
16186
|
/** Active index when controlling externally. */
|
|
16169
16187
|
index?: number;
|
|
16170
|
-
/** Milliseconds between swaps when cycling automatically (default: 3000). */
|
|
16188
|
+
/** Milliseconds between swaps when cycling automatically (default: 3000). With reduced motion it doesn't cycle. */
|
|
16171
16189
|
interval?: number;
|
|
16172
16190
|
/** Swap direction (default: "up"). */
|
|
16173
16191
|
direction?: "up" | "down" | "fade";
|
|
16174
16192
|
/** Pause the cycle on mouse hover (default: true). */
|
|
16175
16193
|
pauseOnHover?: boolean;
|
|
16194
|
+
/** Show a pause button after the text, so keyboard and touch users can stop it too (WCAG 2.2.2). With a mouse it shows on hover or focus. Use it when it cycles next to other content. Hidden with reduced motion. */
|
|
16195
|
+
controls?: boolean;
|
|
16196
|
+
/** Accessible name of the pause button (default: "Pause animation"). */
|
|
16197
|
+
pauseLabel?: string;
|
|
16176
16198
|
className?: string;
|
|
16177
16199
|
/** A single string; changing it swaps to the new text. */
|
|
16178
16200
|
children?: string;
|
|
@@ -16184,6 +16206,8 @@ export function TextSwap({
|
|
|
16184
16206
|
interval = 3000,
|
|
16185
16207
|
direction = "up",
|
|
16186
16208
|
pauseOnHover = true,
|
|
16209
|
+
controls = false,
|
|
16210
|
+
pauseLabel = "Pause animation",
|
|
16187
16211
|
className,
|
|
16188
16212
|
children,
|
|
16189
16213
|
}: TextSwapProps) {
|
|
@@ -16191,29 +16215,37 @@ export function TextSwap({
|
|
|
16191
16215
|
const isControlled = controlledIndex !== undefined;
|
|
16192
16216
|
|
|
16193
16217
|
const [uncontrolledIndex, setUncontrolledIndex] = useState(0);
|
|
16194
|
-
const [
|
|
16218
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
16219
|
+
const [isStopped, setIsStopped] = useState(false);
|
|
16220
|
+
const isPaused = isHovered || isStopped;
|
|
16221
|
+
const reduce = useReducedMotion();
|
|
16222
|
+
const mounted = useMounted();
|
|
16195
16223
|
|
|
16196
16224
|
const activeIndex = isControlled ? controlledIndex : uncontrolledIndex;
|
|
16197
16225
|
const currentItem = list[activeIndex % (list.length || 1)] ?? "";
|
|
16198
16226
|
|
|
16199
16227
|
useEffect(() => {
|
|
16200
|
-
|
|
16228
|
+
// Reduced motion: no auto-cycling, the first item stays.
|
|
16229
|
+
if (isControlled || list.length <= 1 || isPaused || reduce || interval <= 0) return;
|
|
16201
16230
|
|
|
16202
16231
|
const timer = setInterval(() => {
|
|
16203
16232
|
setUncontrolledIndex((prev) => (prev + 1) % list.length);
|
|
16204
16233
|
}, interval);
|
|
16205
16234
|
|
|
16206
16235
|
return () => clearInterval(timer);
|
|
16207
|
-
}, [isControlled, list.length, isPaused, interval]);
|
|
16236
|
+
}, [isControlled, list.length, isPaused, reduce, interval]);
|
|
16208
16237
|
|
|
16209
16238
|
const yOffset = direction === "up" ? 14 : direction === "down" ? -14 : 0;
|
|
16210
16239
|
const exitY = direction === "up" ? -14 : direction === "down" ? 14 : 0;
|
|
16211
16240
|
|
|
16212
|
-
|
|
16213
|
-
|
|
16241
|
+
const withButton = controls && mounted && !reduce && !isControlled && list.length > 1;
|
|
16242
|
+
const hover = pauseOnHover
|
|
16243
|
+
? { onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false) }
|
|
16244
|
+
: {};
|
|
16245
|
+
|
|
16246
|
+
const swap = (
|
|
16214
16247
|
<span
|
|
16215
|
-
|
|
16216
|
-
onMouseLeave={pauseOnHover ? () => setIsPaused(false) : undefined}
|
|
16248
|
+
{...(!withButton && hover)}
|
|
16217
16249
|
// Every item sits invisibly in one grid cell, so the box is always the longest item and swaps
|
|
16218
16250
|
// never reflow the layout (e.g. a two-line quote).
|
|
16219
16251
|
className={cn("relative inline-grid overflow-hidden", className)}
|
|
@@ -16252,6 +16284,29 @@ export function TextSwap({
|
|
|
16252
16284
|
</AnimatePresence>
|
|
16253
16285
|
</span>
|
|
16254
16286
|
);
|
|
16287
|
+
|
|
16288
|
+
if (!withButton) return swap;
|
|
16289
|
+
|
|
16290
|
+
return (
|
|
16291
|
+
<span {...hover} className="group/pause">
|
|
16292
|
+
{swap}
|
|
16293
|
+
|
|
16294
|
+
<button
|
|
16295
|
+
type="button"
|
|
16296
|
+
aria-label={pauseLabel}
|
|
16297
|
+
aria-pressed={isStopped}
|
|
16298
|
+
onClick={() => setIsStopped(!isStopped)}
|
|
16299
|
+
className={buttonVariants({
|
|
16300
|
+
variant: "ghost",
|
|
16301
|
+
size: "icon",
|
|
16302
|
+
className:
|
|
16303
|
+
"ml-1 size-6 rounded-md align-middle text-muted-foreground [&_svg]:size-3.5 opacity-0 group-hover/pause:opacity-100 focus-visible:opacity-100 aria-pressed:opacity-100 pointer-coarse:opacity-100",
|
|
16304
|
+
})}
|
|
16305
|
+
>
|
|
16306
|
+
{isStopped ? <Play /> : <Pause />}
|
|
16307
|
+
</button>
|
|
16308
|
+
</span>
|
|
16309
|
+
);
|
|
16255
16310
|
}
|
|
16256
16311
|
` }] }, textarea: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", name: "textarea", type: "registry:ui", title: "Forms", description: "Input, Textarea, Label and Switch: smooth focus rings, native :user-invalid validation (red only after interaction), aria-invalid, auto-growing textarea with an optional character count, and a spring switch.", docs: "https://kznlabs.github.io/labsUI/components/forms", usage: '<Label htmlFor="email">Email</Label><Input id="email" type="email" aria-invalid={err} /><Textarea aria-label="Bio" maxLength={280} showCount /><Switch aria-label="Notifications" checked={on} onCheckedChange={setOn} />', examples: [{ name: "sign-up.tsx", code: `import { Input } from "@/components/ui/input";
|
|
16257
16312
|
import { Label } from "@/components/ui/label";
|
|
@@ -16571,14 +16626,15 @@ export function TimelineItem({
|
|
|
16571
16626
|
</li>
|
|
16572
16627
|
);
|
|
16573
16628
|
}
|
|
16574
|
-
` }] }, toast: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", name: "toast", type: "registry:ui", title: "Toast", description: "Stacked notifications. Hover to expand, swipe to dismiss, timers pause on hover or focus, and F8 jumps to the newest toast from the keyboard.", docs: "https://kznlabs.github.io/labsUI/components/toast", mount: "<Toaster />", usage: 'toast.success("Saved", { description: "All changes synced", action: { label: "Undo", onClick } })', examples: [{ name: "save.tsx", code: `import { toast } from "@/components/ui/toast";
|
|
16629
|
+
` }] }, toast: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", name: "toast", type: "registry:ui", title: "Toast", description: "Stacked notifications. Hover to expand, swipe to dismiss, timers pause on hover or focus, a toast with an action stays until it's used or dismissed, and F8 jumps to the newest toast with an action (else the newest) from the keyboard.", docs: "https://kznlabs.github.io/labsUI/components/toast", mount: "<Toaster />", usage: 'toast.success("Saved", { description: "All changes synced", action: { label: "Undo", onClick } })', examples: [{ name: "save.tsx", code: `import { toast } from "@/components/ui/toast";
|
|
16575
16630
|
export function notifySaved(undo: () => void) {
|
|
16576
16631
|
toast.success("Changes saved", {
|
|
16577
16632
|
description: "All changes synced",
|
|
16578
16633
|
action: { label: "Undo", onClick: undo },
|
|
16579
16634
|
});
|
|
16580
|
-
}` }], api: { entries: [{ kind: "function", name: "toast", signature: 'toast(title: ReactNode, opts?: Partial<Omit<ToastData, "id" | "title">>) => number', description: '`toast("Saved")`, `toast.success(title, { description, action, duration })`, `toast.loading(title)`, `toast.promise(p, { loading, success, error })`, `toast.dismiss(id?)`.' }, { kind: "component", name: "Toaster", props: [], description: `Mount once (root layout). Stacked toasts: hover, focus or a tap expands, swipe dismisses. F8
|
|
16581
|
-
jumps to the newest toast
|
|
16635
|
+
}` }], api: { entries: [{ kind: "function", name: "toast", signature: 'toast(title: ReactNode, opts?: Partial<Omit<ToastData, "id" | "title">>) => number', description: '`toast("Saved")`, `toast.success(title, { description, action, duration })`, `toast.loading(title)`, `toast.promise(p, { loading, success, error })`, `toast.dismiss(id?)`.' }, { kind: "component", name: "Toaster", props: [{ name: "actionDuration", type: "number", required: false, default: "Number.POSITIVE_INFINITY", description: "ms before a toast with an action closes; Infinity by default, so it stays until used or dismissed. A toast's own `duration` still wins." }, { name: "duration", type: "number", required: false, default: "4000", description: "ms before a toast without an action closes; a toast's own `duration` wins." }], description: `Mount once (root layout). Stacked toasts: hover, focus or a tap expands, swipe dismisses. F8
|
|
16636
|
+
jumps to the newest toast with an action, else the newest (timers pause while focus is there);
|
|
16637
|
+
after a key press that toast shows the shortcut. Esc, the action or Dismiss sends focus back.` }], types: { ToastData: "{ id: number; title: ReactNode; description?: ReactNode; variant: ToastVariant; duration?: number; action?: { label: string; onClick: () => void }; }" } }, dependencies: ["lucide-react@1.63.0", "motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/focus.json", "https://kznlabs.github.io/labsUI/r/kbd.json", "https://kznlabs.github.io/labsUI/r/platform.json", "https://kznlabs.github.io/labsUI/r/use-dismiss.json", "https://kznlabs.github.io/labsUI/r/use-hotkey.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/toast.tsx", type: "registry:ui", content: `"use client";
|
|
16582
16638
|
|
|
16583
16639
|
import { CircleAlert, CircleCheck, Info, Loader2, X } from "lucide-react";
|
|
16584
16640
|
import { AnimatePresence, animate, motion, type PanInfo, useMotionValue } from "motion/react";
|
|
@@ -16590,7 +16646,9 @@ import {
|
|
|
16590
16646
|
useState,
|
|
16591
16647
|
useSyncExternalStore,
|
|
16592
16648
|
} from "react";
|
|
16649
|
+
import { Kbd } from "@/components/ui/kbd";
|
|
16593
16650
|
import { tabbables } from "@/lib/focus";
|
|
16651
|
+
import { useIsMac } from "@/lib/platform";
|
|
16594
16652
|
import { useDismiss } from "@/lib/use-dismiss";
|
|
16595
16653
|
import { useHotkey } from "@/lib/use-hotkey";
|
|
16596
16654
|
import { cn, material, spring } from "@/lib/utils";
|
|
@@ -16603,8 +16661,8 @@ export type ToastData = {
|
|
|
16603
16661
|
title: ReactNode;
|
|
16604
16662
|
description?: ReactNode;
|
|
16605
16663
|
variant: ToastVariant;
|
|
16606
|
-
/** ms; Infinity = sticky. */
|
|
16607
|
-
duration
|
|
16664
|
+
/** ms; Infinity = sticky. Defaults to the Toaster's \`duration\` or \`actionDuration\`. */
|
|
16665
|
+
duration?: number;
|
|
16608
16666
|
action?: { label: string; onClick: () => void };
|
|
16609
16667
|
};
|
|
16610
16668
|
type ToastOptions = Partial<Omit<ToastData, "id" | "title">>;
|
|
@@ -16631,7 +16689,7 @@ const MAX = 6;
|
|
|
16631
16689
|
|
|
16632
16690
|
function create(title: ReactNode, opts: ToastOptions = {}) {
|
|
16633
16691
|
const id = ++nextId;
|
|
16634
|
-
const next: ToastData = { id, title, variant: "default",
|
|
16692
|
+
const next: ToastData = { id, title, variant: "default", ...opts };
|
|
16635
16693
|
|
|
16636
16694
|
toasts = [next, ...toasts].slice(0, MAX);
|
|
16637
16695
|
emit();
|
|
@@ -16662,7 +16720,7 @@ function promise<T>(
|
|
|
16662
16720
|
|
|
16663
16721
|
pending.then(
|
|
16664
16722
|
(value) =>
|
|
16665
|
-
update(id, { title: resolve(msgs.success, value), variant: "success", duration:
|
|
16723
|
+
update(id, { title: resolve(msgs.success, value), variant: "success", duration: undefined }),
|
|
16666
16724
|
(error) => update(id, { title: resolve(msgs.error, error), variant: "error", duration: 6000 }),
|
|
16667
16725
|
);
|
|
16668
16726
|
|
|
@@ -16711,11 +16769,24 @@ const icons = {
|
|
|
16711
16769
|
loading: <Loader2 className="size-4 animate-spin text-muted-foreground" aria-hidden="true" />,
|
|
16712
16770
|
};
|
|
16713
16771
|
|
|
16772
|
+
export type ToasterProps = {
|
|
16773
|
+
/** ms before a toast without an action closes; a toast's own \`duration\` wins. */
|
|
16774
|
+
duration?: number;
|
|
16775
|
+
/** ms before a toast with an action closes; Infinity by default, so it stays until used or dismissed. A toast's own \`duration\` still wins. */
|
|
16776
|
+
actionDuration?: number;
|
|
16777
|
+
className?: string;
|
|
16778
|
+
};
|
|
16779
|
+
|
|
16714
16780
|
/**
|
|
16715
16781
|
* Mount once (root layout). Stacked toasts: hover, focus or a tap expands, swipe dismisses. F8
|
|
16716
|
-
* jumps to the newest toast
|
|
16782
|
+
* jumps to the newest toast with an action, else the newest (timers pause while focus is there);
|
|
16783
|
+
* after a key press that toast shows the shortcut. Esc, the action or Dismiss sends focus back.
|
|
16717
16784
|
*/
|
|
16718
|
-
export function Toaster({
|
|
16785
|
+
export function Toaster({
|
|
16786
|
+
duration = 4000,
|
|
16787
|
+
actionDuration = Number.POSITIVE_INFINITY,
|
|
16788
|
+
className,
|
|
16789
|
+
}: ToasterProps) {
|
|
16719
16790
|
const items = useSyncExternalStore(
|
|
16720
16791
|
subscribe,
|
|
16721
16792
|
() => toasts,
|
|
@@ -16723,11 +16794,30 @@ export function Toaster({ className }: { className?: string }) {
|
|
|
16723
16794
|
);
|
|
16724
16795
|
const section = useRef<HTMLElement>(null);
|
|
16725
16796
|
const back = useRef<HTMLElement | null>(null);
|
|
16797
|
+
const waiting = items.find((item) => item.action)?.id;
|
|
16798
|
+
const [keyboard, setKeyboard] = useState(false);
|
|
16799
|
+
const mac = useIsMac();
|
|
16800
|
+
|
|
16801
|
+
useEffect(() => {
|
|
16802
|
+
const onKey = () => setKeyboard(true);
|
|
16803
|
+
// motion's tap fires a pointerdown without a pointerType when Enter presses a button.
|
|
16804
|
+
const onPointer = (event: PointerEvent) => event.pointerType && setKeyboard(false);
|
|
16805
|
+
|
|
16806
|
+
window.addEventListener("keydown", onKey, true);
|
|
16807
|
+
window.addEventListener("pointerdown", onPointer, true);
|
|
16808
|
+
|
|
16809
|
+
return () => {
|
|
16810
|
+
window.removeEventListener("keydown", onKey, true);
|
|
16811
|
+
window.removeEventListener("pointerdown", onPointer, true);
|
|
16812
|
+
};
|
|
16813
|
+
}, []);
|
|
16726
16814
|
|
|
16727
16815
|
useHotkey(
|
|
16728
16816
|
"F8",
|
|
16729
16817
|
() => {
|
|
16730
|
-
const first =
|
|
16818
|
+
const first =
|
|
16819
|
+
tabbables(section.current?.querySelector<HTMLElement>("[data-waiting]") ?? null)[0] ??
|
|
16820
|
+
tabbables(section.current)[0];
|
|
16731
16821
|
|
|
16732
16822
|
if (!first) return false;
|
|
16733
16823
|
back.current = document.activeElement as HTMLElement | null;
|
|
@@ -16736,6 +16826,19 @@ export function Toaster({ className }: { className?: string }) {
|
|
|
16736
16826
|
{ skipInFields: false },
|
|
16737
16827
|
);
|
|
16738
16828
|
|
|
16829
|
+
// The buttons unmount with their toast, so focus moves first.
|
|
16830
|
+
const leave = useCallback((from: Element) => {
|
|
16831
|
+
if (!section.current?.contains(document.activeElement)) return;
|
|
16832
|
+
|
|
16833
|
+
const toast = from.closest("li");
|
|
16834
|
+
const target = back.current?.isConnected
|
|
16835
|
+
? back.current
|
|
16836
|
+
: tabbables(section.current).find((element) => !toast?.contains(element));
|
|
16837
|
+
|
|
16838
|
+
back.current = null;
|
|
16839
|
+
target?.focus();
|
|
16840
|
+
}, []);
|
|
16841
|
+
|
|
16739
16842
|
const [expanded, setExpanded] = useState(false);
|
|
16740
16843
|
const [heights, setHeights] = useState<Record<number, number>>({});
|
|
16741
16844
|
const onHeight = useCallback(
|
|
@@ -16785,12 +16888,16 @@ export function Toaster({ className }: { className?: string }) {
|
|
|
16785
16888
|
<ToastItem
|
|
16786
16889
|
key={item.id}
|
|
16787
16890
|
toast={item}
|
|
16891
|
+
duration={item.duration ?? (item.action ? actionDuration : duration)}
|
|
16788
16892
|
index={i}
|
|
16893
|
+
waiting={item.id === waiting}
|
|
16894
|
+
hint={keyboard ? (mac ? ["fn", "F8"] : ["F8"]) : undefined}
|
|
16789
16895
|
y={y}
|
|
16790
16896
|
expanded={expanded}
|
|
16791
16897
|
frontHeight={frontH}
|
|
16792
16898
|
total={items.length}
|
|
16793
16899
|
onHeight={onHeight}
|
|
16900
|
+
onLeave={leave}
|
|
16794
16901
|
/>
|
|
16795
16902
|
);
|
|
16796
16903
|
})}
|
|
@@ -16802,15 +16909,31 @@ export function Toaster({ className }: { className?: string }) {
|
|
|
16802
16909
|
|
|
16803
16910
|
type ItemProps = {
|
|
16804
16911
|
toast: ToastData;
|
|
16912
|
+
duration: number;
|
|
16805
16913
|
index: number;
|
|
16914
|
+
waiting: boolean;
|
|
16915
|
+
hint?: string[];
|
|
16806
16916
|
y: number;
|
|
16807
16917
|
expanded: boolean;
|
|
16808
16918
|
frontHeight: number;
|
|
16809
16919
|
total: number;
|
|
16810
16920
|
onHeight: (id: number, height: number) => void;
|
|
16921
|
+
onLeave: (from: Element) => void;
|
|
16811
16922
|
};
|
|
16812
16923
|
|
|
16813
|
-
function ToastItem({
|
|
16924
|
+
function ToastItem({
|
|
16925
|
+
toast: data,
|
|
16926
|
+
duration,
|
|
16927
|
+
index,
|
|
16928
|
+
waiting,
|
|
16929
|
+
hint,
|
|
16930
|
+
y,
|
|
16931
|
+
expanded,
|
|
16932
|
+
frontHeight,
|
|
16933
|
+
total,
|
|
16934
|
+
onHeight,
|
|
16935
|
+
onLeave,
|
|
16936
|
+
}: ItemProps) {
|
|
16814
16937
|
const inner = useRef<HTMLDivElement>(null);
|
|
16815
16938
|
const [height, setHeight] = useState(0);
|
|
16816
16939
|
const x = useMotionValue(0);
|
|
@@ -16819,7 +16942,7 @@ function ToastItem({ toast: data, index, y, expanded, frontHeight, total, onHeig
|
|
|
16819
16942
|
// Paused while you read the stack and while the tab is in the background.
|
|
16820
16943
|
const visible = useSyncExternalStore(onVisibility, isVisible, () => true);
|
|
16821
16944
|
const paused = expanded || !visible;
|
|
16822
|
-
const remaining = useRef({ duration
|
|
16945
|
+
const remaining = useRef({ duration, left: duration });
|
|
16823
16946
|
|
|
16824
16947
|
useEffect(() => {
|
|
16825
16948
|
const element = inner.current;
|
|
@@ -16838,9 +16961,8 @@ function ToastItem({ toast: data, index, y, expanded, frontHeight, total, onHeig
|
|
|
16838
16961
|
|
|
16839
16962
|
useEffect(() => {
|
|
16840
16963
|
// Time left restarts when the duration changes (a promise toast settling).
|
|
16841
|
-
if (remaining.current.duration !==
|
|
16842
|
-
|
|
16843
|
-
if (paused || !Number.isFinite(data.duration)) return;
|
|
16964
|
+
if (remaining.current.duration !== duration) remaining.current = { duration, left: duration };
|
|
16965
|
+
if (paused || !Number.isFinite(duration)) return;
|
|
16844
16966
|
|
|
16845
16967
|
const start = Date.now();
|
|
16846
16968
|
const timer = setTimeout(() => dismiss(data.id), remaining.current.left);
|
|
@@ -16849,7 +16971,7 @@ function ToastItem({ toast: data, index, y, expanded, frontHeight, total, onHeig
|
|
|
16849
16971
|
clearTimeout(timer);
|
|
16850
16972
|
remaining.current.left -= Date.now() - start;
|
|
16851
16973
|
};
|
|
16852
|
-
}, [paused, data.id,
|
|
16974
|
+
}, [paused, data.id, duration]);
|
|
16853
16975
|
|
|
16854
16976
|
const onDragEnd = (_: unknown, info: PanInfo) => {
|
|
16855
16977
|
if (Math.abs(info.offset.x) > 80 || Math.abs(info.velocity.x) > 500) {
|
|
@@ -16879,6 +17001,7 @@ function ToastItem({ toast: data, index, y, expanded, frontHeight, total, onHeig
|
|
|
16879
17001
|
dragMomentum={false}
|
|
16880
17002
|
onDragEnd={onDragEnd}
|
|
16881
17003
|
inert={hidden}
|
|
17004
|
+
data-waiting={waiting || undefined}
|
|
16882
17005
|
className={cn(
|
|
16883
17006
|
"group absolute inset-x-0 bottom-0 touch-pan-y rounded-2xl border text-popover-foreground shadow-lg",
|
|
16884
17007
|
material,
|
|
@@ -16902,16 +17025,27 @@ function ToastItem({ toast: data, index, y, expanded, frontHeight, total, onHeig
|
|
|
16902
17025
|
<p className="font-medium text-sm">{data.title}</p>
|
|
16903
17026
|
|
|
16904
17027
|
{data.description && <p className="text-muted-foreground text-sm">{data.description}</p>}
|
|
17028
|
+
|
|
17029
|
+
{data.action && <span className="sr-only">Press F8 for {data.action.label}.</span>}
|
|
16905
17030
|
</div>
|
|
16906
17031
|
|
|
17032
|
+
{waiting && hint && (
|
|
17033
|
+
<span aria-hidden="true" className="flex shrink-0 gap-1 group-focus-within:hidden">
|
|
17034
|
+
{hint.map((key) => (
|
|
17035
|
+
<Kbd key={key}>{key}</Kbd>
|
|
17036
|
+
))}
|
|
17037
|
+
</span>
|
|
17038
|
+
)}
|
|
17039
|
+
|
|
16907
17040
|
{data.action && (
|
|
16908
17041
|
<button
|
|
16909
17042
|
type="button"
|
|
16910
|
-
onClick={() => {
|
|
17043
|
+
onClick={(event) => {
|
|
16911
17044
|
data.action?.onClick();
|
|
17045
|
+
onLeave(event.currentTarget);
|
|
16912
17046
|
dismiss(data.id);
|
|
16913
17047
|
}}
|
|
16914
|
-
className="h-7 shrink-0 cursor-pointer rounded-md bg-primary px-2.5 font-medium text-primary-foreground text-xs outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
|
17048
|
+
className="-my-1 h-7 shrink-0 cursor-pointer rounded-md bg-primary px-2.5 font-medium text-primary-foreground text-xs outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
|
16915
17049
|
>
|
|
16916
17050
|
{data.action.label}
|
|
16917
17051
|
</button>
|
|
@@ -16919,8 +17053,11 @@ function ToastItem({ toast: data, index, y, expanded, frontHeight, total, onHeig
|
|
|
16919
17053
|
|
|
16920
17054
|
<button
|
|
16921
17055
|
type="button"
|
|
16922
|
-
onClick={() =>
|
|
16923
|
-
|
|
17056
|
+
onClick={(event) => {
|
|
17057
|
+
onLeave(event.currentTarget);
|
|
17058
|
+
dismiss(data.id);
|
|
17059
|
+
}}
|
|
17060
|
+
className="absolute -top-2 -right-2 inline-flex size-6 cursor-pointer items-center justify-center rounded-full border bg-popover text-muted-foreground opacity-0 shadow-sm outline-none transition-opacity hover:text-foreground focus-visible:opacity-100 focus-visible:ring-[3px] focus-visible:ring-ring/50 group-hover:opacity-100"
|
|
16924
17061
|
>
|
|
16925
17062
|
<X className="size-3.5" aria-hidden="true" />
|
|
16926
17063
|
|
|
@@ -17325,10 +17462,12 @@ export function Headline() {
|
|
|
17325
17462
|
/>
|
|
17326
17463
|
</h2>
|
|
17327
17464
|
);
|
|
17328
|
-
}` }], api: { entries: [{ kind: "component", name: "Typewriter", props: [{ name: "text", type: "string | readonly string[]", required: true, description: "Text to type, or an array of phrases to cycle through." }, { name: "cursor", type: "boolean", required: false, default: "true", description: "Whether to show a blinking cursor (default: true)." }, { name: "cursorChar", type: "string", required: false, default: '"|"', description: 'Custom character for the cursor (default: "|").' }, { name: "cursorClassName", type: "string", required: false, description: "Classes for the blinking cursor." }, { name: "delay", type: "number", required: false, default: "0", description: "Milliseconds to wait before starting typing (default: 0)." }, { name: "deleteSpeed", type: "number", required: false, default: "24", description: "Milliseconds per character while deleting (default: 24)." }, { name: "loop", type: "boolean", required: false, default: "true", description: "Whether to loop through phrases when text is an array (default: true)." }, { name: "onComplete", type: "() => void", required: false, description: "Called when the text is fully typed." }, { name: "pauseDuration", type: "number", required: false, default: "2000", description: "Milliseconds to pause once a phrase is fully typed (default: 2000)." }, { name: "speed", type: "number", required: false, default: "40", description: "Milliseconds per character while typing (default: 40)." }], description: "Typewriter effect for headers, quotes, and hero text. Full text stays in the DOM for SEO." }], types: {} }, dependencies: ["motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/platform.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/typewriter.tsx", type: "registry:ui", content: `"use client";
|
|
17465
|
+
}` }], api: { entries: [{ kind: "component", name: "Typewriter", props: [{ name: "text", type: "string | readonly string[]", required: true, description: "Text to type, or an array of phrases to cycle through." }, { name: "controls", type: "boolean", required: false, default: "false", description: "Show a pause button, so keyboard and touch users can stop it too (WCAG 2.2.2). With a mouse it shows on hover or focus. Use it when the text keeps moving next to other content. Hidden with reduced motion." }, { name: "cursor", type: "boolean", required: false, default: "true", description: "Whether to show a blinking cursor (default: true)." }, { name: "cursorChar", type: "string", required: false, default: '"|"', description: 'Custom character for the cursor (default: "|").' }, { name: "cursorClassName", type: "string", required: false, description: "Classes for the blinking cursor." }, { name: "delay", type: "number", required: false, default: "0", description: "Milliseconds to wait before starting typing (default: 0)." }, { name: "deleteSpeed", type: "number", required: false, default: "24", description: "Milliseconds per character while deleting (default: 24)." }, { name: "loop", type: "boolean", required: false, default: "true", description: "Whether to loop through phrases when text is an array (default: true)." }, { name: "onComplete", type: "() => void", required: false, description: "Called when the text is fully typed." }, { name: "pauseDuration", type: "number", required: false, default: "2000", description: "Milliseconds to pause once a phrase is fully typed (default: 2000)." }, { name: "pauseLabel", type: "string", required: false, default: '"Pause animation"', description: 'Accessible name of the pause button (default: "Pause animation").' }, { name: "pauseOnHover", type: "boolean", required: false, default: "true", description: "Pause typing on mouse hover (default: true)." }, { name: "speed", type: "number", required: false, default: "40", description: "Milliseconds per character while typing (default: 40)." }], description: "Typewriter effect for headers, quotes, and hero text. Full text stays in the DOM for SEO." }], types: {} }, dependencies: ["lucide-react@1.63.0", "motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/button-variants.json", "https://kznlabs.github.io/labsUI/r/platform.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/typewriter.tsx", type: "registry:ui", content: `"use client";
|
|
17329
17466
|
|
|
17467
|
+
import { Pause, Play } from "lucide-react";
|
|
17330
17468
|
import { useReducedMotion } from "motion/react";
|
|
17331
17469
|
import { useEffect, useEffectEvent, useMemo, useState } from "react";
|
|
17470
|
+
import { buttonVariants } from "@/components/ui/button-variants";
|
|
17332
17471
|
import { useMounted } from "@/lib/platform";
|
|
17333
17472
|
import { cn } from "@/lib/utils";
|
|
17334
17473
|
|
|
@@ -17345,6 +17484,12 @@ type TypewriterProps = {
|
|
|
17345
17484
|
pauseDuration?: number;
|
|
17346
17485
|
/** Whether to loop through phrases when text is an array (default: true). */
|
|
17347
17486
|
loop?: boolean;
|
|
17487
|
+
/** Pause typing on mouse hover (default: true). */
|
|
17488
|
+
pauseOnHover?: boolean;
|
|
17489
|
+
/** Show a pause button, so keyboard and touch users can stop it too (WCAG 2.2.2). With a mouse it shows on hover or focus. Use it when the text keeps moving next to other content. Hidden with reduced motion. */
|
|
17490
|
+
controls?: boolean;
|
|
17491
|
+
/** Accessible name of the pause button (default: "Pause animation"). */
|
|
17492
|
+
pauseLabel?: string;
|
|
17348
17493
|
/** Whether to show a blinking cursor (default: true). */
|
|
17349
17494
|
cursor?: boolean;
|
|
17350
17495
|
/** Custom character for the cursor (default: "|"). */
|
|
@@ -17364,6 +17509,9 @@ export function Typewriter({
|
|
|
17364
17509
|
delay = 0,
|
|
17365
17510
|
pauseDuration = 2000,
|
|
17366
17511
|
loop = true,
|
|
17512
|
+
pauseOnHover = true,
|
|
17513
|
+
controls = false,
|
|
17514
|
+
pauseLabel = "Pause animation",
|
|
17367
17515
|
cursor = true,
|
|
17368
17516
|
cursorChar = "|",
|
|
17369
17517
|
cursorClassName,
|
|
@@ -17375,6 +17523,9 @@ export function Typewriter({
|
|
|
17375
17523
|
const [phraseIndex, setPhraseIndex] = useState(0);
|
|
17376
17524
|
const [subIndex, setSubIndex] = useState(0);
|
|
17377
17525
|
const [isDeleting, setIsDeleting] = useState(false);
|
|
17526
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
17527
|
+
const [isStopped, setIsStopped] = useState(false);
|
|
17528
|
+
const isPaused = isHovered || isStopped;
|
|
17378
17529
|
|
|
17379
17530
|
const mounted = useMounted();
|
|
17380
17531
|
// After mount only, so hydration matches.
|
|
@@ -17394,7 +17545,7 @@ export function Typewriter({
|
|
|
17394
17545
|
}, [delay]);
|
|
17395
17546
|
|
|
17396
17547
|
useEffect(() => {
|
|
17397
|
-
if (!started || phrases.length === 0) return;
|
|
17548
|
+
if (!started || isPaused || phrases.length === 0) return;
|
|
17398
17549
|
|
|
17399
17550
|
if (!isDeleting && subIndex === currentPhrase.length) {
|
|
17400
17551
|
if (phrases.length === 1 && !loop) {
|
|
@@ -17434,6 +17585,7 @@ export function Typewriter({
|
|
|
17434
17585
|
return () => clearTimeout(timer);
|
|
17435
17586
|
}, [
|
|
17436
17587
|
started,
|
|
17588
|
+
isPaused,
|
|
17437
17589
|
subIndex,
|
|
17438
17590
|
isDeleting,
|
|
17439
17591
|
phraseIndex,
|
|
@@ -17449,7 +17601,12 @@ export function Typewriter({
|
|
|
17449
17601
|
const displayed = reduce ? (phrases[0] ?? "") : currentPhrase.slice(0, subIndex);
|
|
17450
17602
|
|
|
17451
17603
|
return (
|
|
17452
|
-
|
|
17604
|
+
/* biome-ignore lint/a11y/noStaticElementInteractions: pause typing on hover */
|
|
17605
|
+
<span
|
|
17606
|
+
onMouseEnter={pauseOnHover ? () => setIsHovered(true) : undefined}
|
|
17607
|
+
onMouseLeave={pauseOnHover ? () => setIsHovered(false) : undefined}
|
|
17608
|
+
className={cn("group/pause inline-flex items-baseline", className)}
|
|
17609
|
+
>
|
|
17453
17610
|
<span className="sr-only">{fullText}</span>
|
|
17454
17611
|
|
|
17455
17612
|
<span aria-hidden="true" data-typewriter-animated="">
|
|
@@ -17464,16 +17621,48 @@ export function Typewriter({
|
|
|
17464
17621
|
<span
|
|
17465
17622
|
aria-hidden="true"
|
|
17466
17623
|
className={cn(
|
|
17467
|
-
"ml-0.5 inline-block animate-pulse font-light text-muted-foreground select-none",
|
|
17624
|
+
"ml-0.5 inline-block animate-pulse font-light motion-reduce:animate-none text-muted-foreground select-none",
|
|
17468
17625
|
cursorClassName,
|
|
17469
17626
|
)}
|
|
17470
17627
|
>
|
|
17471
17628
|
{cursorChar}
|
|
17472
17629
|
</span>
|
|
17473
17630
|
)}
|
|
17631
|
+
|
|
17632
|
+
{controls && mounted && !reduce && (
|
|
17633
|
+
<PauseButton label={pauseLabel} pressed={isStopped} onPressedChange={setIsStopped} />
|
|
17634
|
+
)}
|
|
17474
17635
|
</span>
|
|
17475
17636
|
);
|
|
17476
17637
|
}
|
|
17638
|
+
|
|
17639
|
+
function PauseButton({
|
|
17640
|
+
label,
|
|
17641
|
+
pressed,
|
|
17642
|
+
onPressedChange,
|
|
17643
|
+
}: {
|
|
17644
|
+
label: string;
|
|
17645
|
+
pressed: boolean;
|
|
17646
|
+
onPressedChange: (pressed: boolean) => void;
|
|
17647
|
+
}) {
|
|
17648
|
+
return (
|
|
17649
|
+
<button
|
|
17650
|
+
type="button"
|
|
17651
|
+
aria-label={label}
|
|
17652
|
+
aria-pressed={pressed}
|
|
17653
|
+
onClick={() => onPressedChange(!pressed)}
|
|
17654
|
+
className={buttonVariants({
|
|
17655
|
+
variant: "ghost",
|
|
17656
|
+
size: "icon",
|
|
17657
|
+
// A mouse pauses it by hovering, so the button waits for hover or focus; touch always sees it.
|
|
17658
|
+
className:
|
|
17659
|
+
"ml-1 size-6 self-center rounded-md text-muted-foreground [&_svg]:size-3.5 opacity-0 group-hover/pause:opacity-100 focus-visible:opacity-100 aria-pressed:opacity-100 pointer-coarse:opacity-100",
|
|
17660
|
+
})}
|
|
17661
|
+
>
|
|
17662
|
+
{pressed ? <Play /> : <Pause />}
|
|
17663
|
+
</button>
|
|
17664
|
+
);
|
|
17665
|
+
}
|
|
17477
17666
|
` }] }, "use-anchor": { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", name: "use-anchor", type: "registry:hook", description: `Fixed-position coordinates for a floating panel next to its anchor: on the preferred side,
|
|
17478
17667
|
flipped when it doesn't fit, clamped to the viewport, kept in place on scroll and resize.
|
|
17479
17668
|
Measures before paint, so nothing jumps.`, api: { entries: [{ kind: "function", name: "useAnchor", signature: `useAnchor(open: boolean, anchor: RefObject<HTMLElement | null>, floating: RefObject<HTMLElement | null>, { align, side: preferred, offset }?: {
|
|
@@ -17854,7 +18043,7 @@ export const clamp = (value: number, min: number, max: number) =>
|
|
|
17854
18043
|
` }] } },
|
|
17855
18044
|
index: [{ name: "accent-picker", type: "registry:ui", title: "Accent Picker", description: "Accent color row: eight swatches (red to slate) that retint primary, focus rings, selection, switches and checkboxes app-wide. Persisted, applied before paint, or set your own hue with --accent-hue.", exports: ["AccentPicker"], keywords: ["accent", "app", "color", "radio", "retint", "row", "setaccent", "swatches", "that", "the", "via", "whole"], registryDependencies: ["theme", "utils"] }, { name: "accordion", type: "registry:ui", title: "Accordion", description: "Single or multiple panels with spring height animation. Closed content stays in the DOM for SEO.", exports: ["Accordion", "AccordionItem", "AccordionTrigger", "AccordionContent"], keywords: ["accordion", "accordioncontent", "accordionitem", "accordiontrigger", "allow", "and", "called", "children", "closed", "closes", "closing", "collapsible", "content", "controlled", "crawlable", "default", "defaultvalue", "every", "first", "heading", "height", "html", "independent", "inert", "item", "items", "level", "match", "matched", "mounted", "multiple", "none", "one", "only", "onvaluechange", "open", "outline", "page", "panel", "panels", "question", "remains", "render", "single", "stays", "text", "the", "time", "type", "uncontrolled", "unique", "value", "when", "while", "with", "within", "wrapping"], registryDependencies: ["use-controllable-state", "utils"] }, { name: "alert", type: "registry:ui", title: "Alert", description: "Server-safe inline banner: tinted surface, colored icon, title, description and an optional action. Info, success, warning and destructive tones follow the accent and status tokens.", exports: ["Alert", "AlertTitle", "AlertDescription"], keywords: ["action", "add", "alert", "aligned", "and", "announced", "appears", "banner", "button", "colored", "default", "description", "did", "for", "icon", "inline", "live", "load", "none", "not", "null", "pass", "region", "replaces", "response", "right", "role", "sets", "should", "slot", "small", "something", "static", "surface", "the", "tint", "tinted", "title", "tone", "user", "variant", "when"], registryDependencies: ["utils"] }, { name: "app-window", type: "registry:ui", title: "AppWindow", description: "Window container with close / minimize / zoom controls, title, custom header slot and actions. SegmentedTabs is the compact view switcher for its title bar.", exports: ["AppWindow"], keywords: ["actions", "aligned", "area", "bar", "blurred", "bodyclassname", "button", "called", "centered", "classes", "content", "control", "custom", "dot", "for", "glass", "handler", "header", "into", "matching", "maximize", "minimize", "navbar", "onclose", "onmaximize", "onminimize", "passing", "pressed", "replaces", "right", "search", "shows", "slot", "surface", "tabs", "text", "the", "title", "titlebar", "translucent", "turns", "under", "when"], registryDependencies: ["utils"] }, { name: "aspect-ratio", type: "registry:ui", title: "Aspect Ratio", description: "Server-safe box locked to a ratio (16:9 thumbnails, 9:16 clips). A direct image or video child fills it with object-cover.", exports: ["AspectRatio"], keywords: ["box", "child", "cover", "direct", "fills", "height", "img", "locked", "object", "ratio", "video", "width"], registryDependencies: ["utils"] }, { name: "attachment", type: "registry:ui", title: "Attachment & Dropzone", description: "File chips with image thumbnails or type icons, size, a springing upload bar, error state and remove; the list springs chips in and closes the gap. FileDropzone wraps a real file input: click, keyboard and the native picker work, drop and paste are checked against accept and maxSize, and dropped files land in the input so forms and Server Actions receive them.", exports: ["formatBytes", "Attachment", "AttachmentList", "FileDropzone"], keywords: ["accept", "actions", "along", "and", "are", "area", "aria", "around", "attachments", "attribute", "back", "bar", "bcp", "blocks", "both", "bottom", "browse", "button", "bytes", "called", "child", "children", "chip", "click", "content", "createobjecturl", "default", "describedby", "disabled", "download", "drop", "dropped", "drops", "each", "edge", "enforced", "enter", "error", "failed", "field", "file", "files", "fills", "for", "form", "forms", "fresh", "from", "get", "given", "hint", "href", "icon", "ids", "image", "input", "into", "invalid", "key", "least", "line", "link", "locale", "makes", "maxsize", "mime", "more", "multiple", "name", "native", "needs", "one", "onfiles", "onreject", "onremove", "out", "passed", "paste", "pdf", "picked", "picker", "picks", "place", "plain", "pressed", "progress", "real", "reason", "red", "remove", "removed", "replaces", "required", "same", "server", "sets", "show", "shows", "size", "source", "space", "spring", "springs", "src", "styling", "submit", "submitted", "syntax", "text", "than", "that", "the", "them", "thumbnail", "too", "turns", "type", "types", "upload", "uploading", "url", "when", "while", "with", "work", "written"], registryDependencies: ["field", "utils"] }, { name: "avatar", type: "registry:ui", title: "Avatar", description: "Image with initials fallback on error, three sizes and online/away/busy/offline dots.", exports: ["Avatar"], keywords: ["alt", "and", "announced", "corner", "diameter", "dot", "fails", "fallback", "for", "icon", "image", "initials", "loads", "name", "overrides", "person", "presence", "show", "size", "src", "status", "text", "the", "url", "used", "while", "with"], registryDependencies: ["utils"] }, { name: "avatar-group", type: "registry:ui", title: "AvatarGroup", description: "Overlapping avatars with a +N overflow chip. Hover gently enlarges the avatar on a spring, keeping stack order.", exports: ["AvatarGroup"], keywords: ["alt", "and", "avatar", "avatars", "before", "chip", "each", "enlarges", "every", "gently", "hover", "how", "many", "max", "name", "one", "order", "overflow", "overlapping", "people", "show", "size", "spring", "stack", "staying", "the", "users", "with"], registryDependencies: ["avatar", "utils"] }, { name: "badge", type: "registry:ui", title: "Badge & Pill", description: "Minimal status tags. Rounded badge or fully rounded pill, with optional dot. pulse adds an expanding live-status ring.", exports: ["Badge"], keywords: ["and", "color", "current", "dot", "emits", "expanding", "forever", "fully", "leading", "like", "live", "needs", "pill", "pulse", "ring", "rounded", "shape", "status", "surface", "tag", "text", "the", "variant"], registryDependencies: ["utils"] }, { name: "breadcrumb", type: "registry:ui", title: "Breadcrumb", description: "Navigation trail from a list of items: links up to the current page, which is marked for assistive tech. Server-safe, custom separator.", exports: ["Breadcrumb"], keywords: ["between", "chevron", "current", "default", "drawn", "ending", "first", "item", "items", "last", "links", "list", "outermost", "page", "safe", "separator", "server", "site", "sits", "the", "trail", "where"], registryDependencies: [] }, { name: "button", type: "registry:ui", title: "Button", description: "Five variants, four sizes, spring press feedback, an animated loading spinner and in-place success state, a two-step confirm that swaps the label on first click, and a configurable swap direction. Width stays fixed to the longest label.", exports: ["Button"], keywords: ["action", "also", "and", "aria", "armed", "before", "blur", "both", "button", "can", "check", "children", "click", "clicks", "confirm", "confirmlabel", "confirmtimeout", "default", "defaults", "destructive", "done", "emphasis", "enter", "esc", "feedback", "fill", "first", "focusable", "for", "form", "give", "height", "icon", "ignore", "ignores", "irreversible", "its", "label", "left", "loading", "loadinglabel", "never", "old", "onclick", "one", "ones", "out", "padding", "per", "place", "primary", "reserved", "resets", "resize", "runs", "second", "shown", "shows", "size", "slides", "spinner", "spins", "square", "status", "stays", "step", "submit", "success", "successlabel", "swap", "swaps", "text", "the", "this", "twice", "two", "variant", "view", "where", "while", "width", "with"], registryDependencies: ["button-variants", "utils"] }, { name: "button-group", type: "registry:ui", title: "Button Group", description: "Joins buttons into one strip that shares borders, corners and active states. Horizontal or vertical.", exports: ["ButtonGroup"], keywords: ["and", "attached", "borders", "buttons", "connected", "control", "corners", "default", "fuse", "groups", "horizontal", "into", "orientation", "related", "single", "strip", "together", "toolbar", "true", "vertical"], registryDependencies: ["utils"] }, { name: "button-variants", type: "registry:ui", description: 'Button classes for non-button elements, e.g. `<a className={buttonVariants({ variant: "outline" })}>`.', exports: ["buttonVariants"], keywords: ["button", "buttonvariants", "classes", "classname", "elements", "for", "non", "outline", "variant"], registryDependencies: ["utils"] }, { name: "calendar", type: "registry:ui", title: "Calendar", description: "Month grid on the WAI-ARIA date-grid pattern: single date or range with a hover preview, min/max and custom disabled days, months that slide in from the direction of travel. Arrows, Home/End and PageUp/PageDown (+Shift for years). Hydration-safe: today is only marked in the browser.", exports: ["key", "Calendar"], keywords: ["and", "another", "any", "aria", "arrows", "autofocus", "bcp", "called", "client", "controlled", "current", "custom", "date", "datepicker", "dates", "day", "days", "default", "defaultmonth", "defaultvalue", "direction", "disabled", "displayed", "earliest", "end", "ends", "extra", "first", "focus", "for", "from", "function", "grid", "home", "hover", "into", "isdisabled", "iso", "keyboard", "latest", "like", "local", "locale", "max", "min", "mode", "monday", "month", "months", "mount", "names", "nothing", "null", "off", "one", "only", "onmonthchange", "onvaluechange", "pagedown", "pages", "pageup", "pattern", "pick", "picked", "preview", "range", "render", "rule", "saturday", "selectable", "sets", "shift", "shown", "single", "slide", "sorts", "start", "sunday", "the", "then", "this", "travel", "uncontrolled", "user", "value", "wai", "week", "weekday", "weekends", "weekstartson", "when", "with", "year", "yyyy"], registryDependencies: ["button", "use-controllable-state", "utils"] }, { name: "card", type: "registry:ui", title: "Card", description: "Composable surface: header, title, description, content and footer. Optional glass.", exports: ["Card", "CardHeader", "CardTitle", "CardDescription", "CardContent", "CardFooter"], keywords: ["backgrounds", "colored", "default", "directly", "fits", "follows", "glass", "heading", "level", "not", "outline", "over", "page", "place", "subtle", "textured", "that", "the", "translucency", "under", "when"], registryDependencies: ["utils"] }, { name: "carousel", type: "registry:ui", title: "Carousel", description: "Native scroll-snap row, so touch, trackpad and no-JS scrolling work, with spring-driven arrows, page dots, arrow keys and mouse drag that throws to the next snap point. APG carousel semantics: a labeled region of slides announced as 3 of 8.", exports: ["Carousel", "CarouselContent", "CarouselItem", "CarouselPrevious", "CarouselNext", "CarouselDots"], keywords: ["accessible", "all", "and", "announced", "any", "apg", "are", "arrow", "basis", "buttons", "carousel", "css", "default", "dot", "dots", "drag", "driven", "edge", "end", "fewer", "from", "gap", "hides", "keys", "label", "labeled", "left", "mouse", "name", "native", "not", "one", "overlaid", "page", "pages", "per", "projects", "region", "rem", "right", "row", "scroll", "scrolling", "semantics", "sized", "slide", "slides", "snap", "spring", "start", "the", "there", "total", "touch", "trackpad", "two", "variables", "view", "with", "work"], registryDependencies: ["button", "utils"] }, { name: "changelog", type: "registry:block", title: "Changelog", description: "Release notes on a Timeline: newest release highlighted, changes typed New / Improved / Fixed with Badges, and a Kbd shortcut to try the headline feature. Server-safe.", exports: ["Changelog"], keywords: ["are", "badges", "changes", "first", "highlighted", "how", "kbd", "newest", "notes", "one", "release", "releases", "shows", "the", "timeline", "try", "typed", "with"], registryDependencies: ["badge", "kbd", "timeline", "utils"] }, { name: "chart", type: "registry:ui", title: "Chart", description: "Line, area and bar (grouped or stacked) charts in SVG with a colorblind-validated eight-color palette that keeps each series' color. Crosshair tooltip listing every series, arrow-key readout, legend that hides series, direct end labels, a table view, and spring transitions between data.", exports: ["Chart"], keywords: ["add", "and", "are", "area", "areas", "arrow", "assistive", "axis", "bar", "bars", "bcp", "can", "captions", "categorical", "category", "chart", "charts", "compact", "crosshair", "currency", "data", "direct", "each", "eight", "end", "every", "filled", "fold", "for", "format", "grouping", "height", "hides", "index", "instead", "into", "key", "keys", "label", "labels", "legend", "line", "lines", "listing", "locale", "mark", "most", "names", "notation", "numbers", "object", "one", "only", "other", "pages", "palette", "past", "per", "plot", "plus", "props", "render", "row", "serializable", "series", "server", "split", "stack", "stacked", "style", "svg", "table", "tail", "tech", "that", "the", "them", "ticks", "too", "tooltip", "type", "usd", "value", "view", "with"], registryDependencies: ["button", "table", "use-dismiss", "utils"] }, { name: "checkbox", type: "registry:ui", title: "Checkbox", description: "Native checkbox with a spring-drawn check mark and an indeterminate state for select-all rows. Controlled or uncontrolled; submits with forms.", exports: ["Checkbox"], keywords: ["all", "called", "check", "checkbox", "checked", "controlled", "dash", "defaultchecked", "drawn", "field", "first", "indeterminate", "inside", "mixed", "native", "new", "oncheckedchange", "pair", "render", "rows", "select", "selected", "shows", "some", "spring", "state", "the", "uncontrolled", "with", "works"], registryDependencies: ["field", "form-value", "use-controllable-state", "utils"] }, { name: "clip-card", type: "registry:block", title: "Clip Card", description: "Video tile: AspectRatio thumbnail (16:9 or 9:16), duration Badge, watched Progress along the bottom, and a ContextMenu of actions.", exports: ["ClipCard"], keywords: ["accessible", "actions", "ago", "along", "also", "and", "aspectratio", "badge", "bar", "bottom", "card", "click", "clip", "clips", "context", "contextmenuitem", "days", "duration", "f10", "film", "for", "glyph", "height", "how", "img", "items", "menu", "meta", "much", "name", "one", "opens", "print", "progress", "ratio", "right", "shift", "small", "stands", "the", "them", "thin", "thumbnail", "tile", "title", "under", "vertical", "video", "views", "was", "watched", "width", "without"], registryDependencies: ["aspect-ratio", "badge", "context-menu", "progress", "utils"] }, { name: "code", type: "registry:ui", title: "Code", description: "Shiki-highlighted code that follows light and dark through light-dark() token colors, as a block, an inline one-liner or a tabbed file panel. Line numbers (from any start line), tinted lines with an accent bar, <mark>ed words or column ranges, and per-line notes in a tooltip that keyboard users can tab to. Optional Prettier at display time; copy hands out exactly what is shown.", exports: ["CopyButton", "Code", "CodePanel"], keywords: ["accessible", "active", "add", "all", "and", "animates", "annotations", "bar", "bash", "before", "behind", "block", "body", "build", "button", "clipboard", "code", "collapse", "copied", "copy", "dark", "default", "dom", "each", "every", "excerpts", "file", "files", "first", "focus", "fold", "for", "format", "formatted", "from", "gets", "ghost", "gutter", "header", "highlight", "highlightcode", "highlighted", "highlighting", "hover", "ignored", "inline", "inside", "into", "its", "json", "keep", "label", "language", "least", "left", "light", "line", "linenumbers", "lines", "loads", "longer", "made", "many", "mark", "marks", "name", "never", "note", "notes", "number", "numbers", "one", "options", "own", "page", "paint", "panel", "per", "place", "prettier", "primary", "ranges", "refer", "registry", "remember", "renders", "run", "scrolling", "scrolls", "server", "sets", "shared", "shiki", "show", "source", "stays", "switching", "syntax", "tab", "terminal", "text", "than", "that", "the", "themes", "this", "time", "tint", "tokens", "too", "touch", "tsx", "turns", "variant", "vertically", "wheel", "with", "words", "wrapped", "written"], registryDependencies: ["button", "highlight", "overflow-focus", "tabs", "tooltip", "utils"] }, { name: "collapsible", type: "registry:ui", title: "Collapsible", description: "A single show/hide region with a spring height animation and rotating chevron. Controlled or uncontrolled; closed content stays in the DOM for SEO.", exports: ["Collapsible", "CollapsibleTrigger", "CollapsibleContent"], keywords: ["after", "and", "called", "chevron", "children", "closed", "collapsiblecontent", "collapsibletrigger", "content", "controlled", "crawlable", "default", "defaultopen", "first", "height", "hide", "html", "inert", "label", "mounted", "one", "onopenchange", "open", "pair", "region", "remains", "render", "rotating", "show", "springs", "state", "stays", "that", "the", "toggles", "trigger", "true", "uncontrolled", "when", "while", "with"], registryDependencies: ["use-controllable-state", "utils"] }, { name: "combobox", type: "registry:ui", title: "Combobox", description: "A Select you can type into: the query filters by label and keywords (prefix matches first), arrows move, Enter picks. multiple keeps the list open and shows picks as removable tags. Submits and validates with the form.", exports: ["Combobox"], keywords: ["accessible", "and", "aria", "backspace", "blocks", "called", "can", "choices", "closes", "controlled", "defaultvalue", "describedby", "disabled", "down", "emptytext", "enter", "entry", "error", "esc", "every", "field", "fills", "filtered", "filters", "first", "form", "from", "hint", "ids", "input", "into", "invalid", "isn", "keeps", "label", "last", "list", "matches", "move", "multiple", "name", "needs", "nothing", "one", "onto", "onvaluechange", "open", "opening", "options", "pair", "per", "pick", "picked", "picks", "placeholder", "removable", "removes", "render", "required", "select", "sets", "show", "shown", "shows", "single", "stay", "styling", "submit", "submitted", "tags", "text", "the", "them", "there", "type", "types", "typing", "uncontrolled", "user", "value", "values", "visible", "walks", "when", "with", "you"], registryDependencies: ["field", "form-value", "list", "platform", "tag-input", "use-anchor", "use-controllable-state", "use-dismiss", "utils"] }, { name: "command", type: "registry:ui", title: "Command", description: "⌘K palette: filterable groups, arrow-key highlight, Enter to run, keyword matching. Inline or as CommandDialog with a global shortcut; the first Esc clears, the second closes.", exports: ["Command", "CommandInput", "CommandList", "CommandEmpty", "CommandGroup", "CommandSeparator", "CommandItem", "CommandDialog"], keywords: ["accessible", "after", "against", "and", "arrows", "but", "called", "children", "click", "close", "closes", "collapse", "command", "commanddialog", "commandempty", "commandinput", "commanditem", "commandlist", "content", "controlled", "ctrl", "defaults", "dialog", "disable", "disabled", "driven", "elsewhere", "end", "enter", "esc", "extra", "false", "filterable", "filtering", "for", "global", "group", "groups", "heading", "hidden", "hides", "home", "icon", "inline", "input", "inside", "item", "items", "its", "itself", "keepopen", "keyboard", "keywords", "label", "list", "listbox", "mac", "match", "matched", "matches", "mod", "move", "name", "none", "not", "only", "onopenchange", "onselect", "open", "option", "outside", "own", "pair", "palette", "pass", "passes", "placeholder", "preferences", "query", "results", "scrollable", "search", "selectable", "selecting", "selection", "selects", "separators", "settings", "shortcut", "shown", "shows", "stack", "state", "stay", "terms", "text", "the", "title", "toggle", "toggled", "use", "used", "value", "visible", "visually", "when", "while", "with", "would"], registryDependencies: ["dialog", "list", "search-input", "separator", "use-controllable-state", "use-hotkey", "utils"] }, { name: "confirm", type: "registry:ui", title: "Confirm", description: "Promise-based confirm dialog called like toast: await confirm({ title, description, destructive }) resolves true or false. Built on Dialog's alert mode, Cancel takes the first focus, Esc cancels, one host mounted in the layout.", exports: ["confirm", "Confirmer"], keywords: ["acting", "asks", "await", "before", "cancel", "confirm", "confirmer", "delete", "destructive", "esc", "false", "for", "host", "layout", "mount", "mounted", "needs", "newer", "next", "once", "project", "remove", "resolves", "root", "title", "toaster", "true"], registryDependencies: ["button", "dialog"] }, { name: "context-menu", type: "registry:ui", title: "Context Menu", description: "Right-click menu for an area, opening at the pointer and flipping at screen edges. Shares Dropdown Menu's items, checkbox and radio items, keyboard and typeahead; Menu key or Shift+F10 opens it from the keyboard.", exports: ["ContextMenuContent", "ContextMenuItem", "ContextMenuCheckboxItem", "ContextMenuRadioGroup", "ContextMenuRadioItem", "ContextMenuLabel", "ContextMenuSeparator", "ContextMenu", "ContextMenuTrigger"], keywords: ["actions", "align", "aligned", "and", "area", "but", "called", "center", "check", "checked", "children", "click", "clickable", "closes", "contextmenucontent", "contextmenutrigger", "destructive", "disabled", "dropdownmenuradioitem", "edge", "enter", "equals", "f10", "first", "focus", "focusable", "for", "group", "groups", "hint", "icon", "irreversible", "item", "items", "its", "key", "keyboard", "label", "labels", "leading", "lines", "mark", "menu", "new", "not", "oncheckedchange", "onopenchange", "onselect", "onvaluechange", "opens", "optionally", "picked", "pointer", "press", "red", "right", "selectable", "separators", "shift", "shortcut", "shown", "shows", "state", "the", "then", "trigger", "value", "variant", "when", "with"], registryDependencies: ["dropdown-menu", "utils"] }, { name: "count-to", type: "registry:ui", title: "CountTo", description: "Springs a number up from a start value once it scrolls into view. Prefix, suffix, decimals and locale.", exports: ["CountTo"], keywords: ["after", "and", "bcp", "before", "decimal", "decimals", "digits", "dom", "final", "for", "from", "grouping", "into", "locale", "number", "once", "point", "prefix", "scrolls", "seo", "springs", "starting", "stays", "suffix", "text", "the", "value", "view"], registryDependencies: ["utils"] }, { name: "custom-cursor", type: "registry:ui", title: "Custom Cursor", description: "Optional spring cursor that expands over interactive elements. Off on touch and reduced motion.", exports: ["CustomCursor"], keywords: ["coarse", "cursor", "dot", "elements", "expands", "interactive", "motion", "mount", "nothing", "once", "opt", "over", "pointers", "reduced", "renders", "ring", "spring", "touch", "trailing", "with"], registryDependencies: ["platform", "utils"] }, { name: "data-table", type: "registry:ui", title: "Data Table", description: "Table with columns as data: search across every column, sortable headers, checkbox selection that survives paging, a ⋯ menu per row, bulk actions in the toolbar and pagination. Built from Table, SearchInput, Checkbox, DropdownMenu, Pagination and Empty.", exports: ["DataTable"], keywords: ["accessor", "across", "actions", "all", "alternate", "and", "are", "backgrounds", "box", "bulk", "called", "cell", "checkbox", "client", "column", "columns", "compose", "controlled", "controls", "data", "defaultsort", "definitions", "display", "driven", "dropdownmenuitem", "each", "every", "field", "filters", "first", "for", "friends", "getrowid", "happen", "header", "ids", "items", "its", "jane", "kept", "label", "members", "menu", "name", "names", "onselectionchange", "order", "page", "pages", "pagesize", "pagination", "paging", "pair", "per", "placeholder", "readers", "receives", "render", "right", "row", "rowactions", "rowlabel", "rows", "screen", "search", "searchable", "searchplaceholder", "select", "selectable", "selected", "selection", "server", "show", "side", "sort", "sortable", "stable", "striped", "survives", "table", "that", "the", "toolbar", "with", "yourself"], registryDependencies: ["button", "checkbox", "dropdown-menu", "empty", "pagination", "search-input", "table", "use-controllable-state", "utils"] }, { name: "date-picker", type: "registry:ui", title: "Date Picker", description: "Field-styled button that opens a Calendar in a Popover, focused on the picked day. Single or range, presets such as Last 7 days, closes once the pick is complete, and submits ISO dates with the form. Works inside Field.", exports: ["DatePicker"], keywords: ["accessible", "and", "are", "aria", "bcp", "beside", "blocks", "both", "button", "calendar", "called", "client", "closes", "controlled", "date", "day", "days", "default", "defaultvalue", "describedby", "disabled", "earliest", "end", "ends", "error", "extra", "field", "fill", "fills", "first", "focused", "for", "form", "from", "function", "grid", "hint", "htmlfor", "ids", "invalid", "isdisabled", "isn", "iso", "label", "last", "latest", "let", "locale", "max", "min", "mode", "monday", "name", "needs", "nothing", "null", "off", "once", "one", "only", "onvaluechange", "opening", "opens", "optional", "pair", "pick", "picked", "placeholder", "popover", "presets", "range", "reach", "readers", "ready", "render", "required", "rule", "saturday", "selectable", "set", "sets", "shortcuts", "single", "start", "styled", "styling", "submit", "submits", "submitted", "sunday", "text", "that", "the", "then", "there", "these", "today", "uncontrolled", "value", "visible", "weekends", "weekstartson", "when", "with"], registryDependencies: ["calendar", "field", "form-value", "input", "popover", "use-controllable-state", "utils"] }, { name: "dialog", type: "registry:ui", title: "Dialog", description: "Portal modal with blurred backdrop, spring scale-in, focus trap, Esc and scroll lock.", exports: ["useDialog", "Dialog", "DialogTrigger", "DialogClose", "useModal", "DialogContent", "DialogHeader", "DialogFooter", "DialogTitle", "DialogDescription"], keywords: ["action", "alert", "alertdialog", "also", "and", "aria", "armed", "attach", "backdrop", "before", "behavior", "blur", "blurred", "body", "both", "button", "called", "can", "check", "children", "click", "clicks", "close", "confirm", "confirmlabel", "confirmtimeout", "controlled", "corner", "default", "defaultopen", "defaults", "destructive", "dialog", "dialogclose", "dialogcontent", "dialogs", "dialogtrigger", "does", "done", "drawer", "emphasis", "enter", "esc", "feedback", "field", "fill", "first", "focus", "focusable", "footer", "for", "form", "give", "header", "height", "icon", "ignore", "ignores", "irreversible", "its", "label", "left", "loading", "loadinglabel", "lock", "modal", "mode", "never", "not", "off", "old", "onclick", "one", "ones", "only", "onopenchange", "open", "out", "padding", "page", "pair", "panel", "per", "place", "portal", "primary", "ref", "render", "reserved", "resets", "resize", "restore", "returned", "role", "root", "runs", "scale", "scroll", "second", "shared", "showclose", "shown", "shows", "size", "slides", "spinner", "spins", "spring", "square", "state", "status", "stays", "step", "submit", "success", "successlabel", "swap", "swaps", "tab", "text", "the", "this", "topmost", "trap", "trigger", "twice", "two", "uncontrolled", "variant", "view", "where", "while", "width", "with"], registryDependencies: ["button", "focus", "platform", "use-controllable-state", "utils"] }, { name: "drawer", type: "registry:ui", title: "Drawer", description: "Edge panel (left, right, top, bottom) that springs in and swipes away. Shares Dialog's focus trap, scroll lock and Esc handling. Sidebar uses it below md.", exports: ["Drawer", "DrawerTrigger", "DrawerClose", "DrawerTitle", "DrawerDescription", "DrawerContent", "DrawerHeader", "DrawerBody", "DrawerFooter"], keywords: ["action", "also", "and", "anywhere", "aria", "armed", "backdrop", "before", "blur", "body", "both", "bottom", "button", "called", "can", "check", "children", "click", "clicks", "close", "confirm", "confirmlabel", "confirmtimeout", "content", "controlled", "corner", "default", "defaultopen", "defaults", "destructive", "dialogclose", "dialogcontent", "dialogtrigger", "dismiss", "done", "drag", "drawer", "drawers", "edge", "emphasis", "enter", "esc", "feedback", "fill", "first", "focusable", "footer", "for", "form", "from", "give", "handle", "header", "height", "icon", "ignore", "ignores", "irreversible", "its", "label", "left", "loading", "loadinglabel", "never", "old", "onclick", "one", "ones", "onopenchange", "open", "out", "padding", "pair", "panel", "per", "place", "primary", "render", "reserved", "resets", "resize", "runs", "scroll", "scrolls", "second", "showclose", "shown", "shows", "side", "size", "slides", "spinner", "spins", "spring", "square", "state", "status", "stays", "step", "submit", "success", "successlabel", "swap", "swaps", "swipe", "taller", "text", "than", "that", "the", "their", "this", "top", "toward", "trigger", "twice", "two", "uncontrolled", "variant", "view", "when", "where", "while", "width", "with"], registryDependencies: ["dialog", "platform", "utils"] }, { name: "dropdown-menu", type: "registry:ui", title: "Dropdown Menu", description: "Action menu anchored to a Button: items with icons and shortcuts, checkbox and radio items, labels, separators, destructive variant. Arrow keys, typeahead, focus follows the pointer, Esc returns focus.", exports: ["MenuContext", "DropdownMenu", "DropdownMenuTrigger", "DropdownMenuContent", "DropdownMenuItem", "DropdownMenuCheckboxItem", "DropdownMenuRadioGroup", "DropdownMenuRadioItem", "DropdownMenuLabel", "DropdownMenuSeparator"], keywords: ["action", "actions", "align", "aligned", "also", "anchored", "and", "aria", "armed", "before", "blur", "both", "but", "button", "called", "can", "check", "checked", "children", "choice", "click", "clicks", "closes", "closing", "confirm", "confirmlabel", "confirmtimeout", "content", "controlled", "default", "defaultopen", "defaults", "destructive", "disabled", "done", "down", "dropdownmenuradioitem", "edge", "emphasis", "enter", "equals", "esc", "feedback", "fill", "first", "focusable", "for", "form", "give", "group", "groups", "height", "hint", "icon", "ignore", "ignores", "irreversible", "item", "items", "its", "keeps", "keyboard", "label", "labels", "last", "leading", "left", "lines", "loading", "loadinglabel", "mark", "menu", "never", "new", "not", "old", "oncheckedchange", "onclick", "one", "ones", "onopenchange", "onselect", "onvaluechange", "open", "opens", "optionally", "out", "outside", "padding", "pair", "per", "picked", "picking", "place", "primary", "radiogroup", "red", "render", "reserved", "resets", "resize", "right", "runs", "second", "selectable", "selection", "separators", "shortcut", "shown", "shows", "size", "slides", "space", "spinner", "spins", "square", "state", "status", "stays", "step", "submit", "success", "successlabel", "swap", "swaps", "text", "the", "then", "this", "toggles", "trigger", "twice", "two", "uncontrolled", "value", "variant", "view", "when", "where", "while", "width", "with", "without"], registryDependencies: ["button", "list", "platform", "use-anchor", "use-controllable-state", "use-dismiss", "utils"] }, { name: "empty", type: "registry:ui", title: "Empty", description: "Server-safe empty state: icon tile, title, one helpful sentence and the next action. Bordered variant for empty lists and drop zones.", exports: ["Empty"], keywords: ["about", "action", "and", "app", "area", "bordered", "children", "clips", "dashed", "description", "drop", "empty", "for", "helpful", "icon", "inside", "like", "list", "next", "one", "outline", "page", "rounded", "sentence", "shown", "state", "the", "tile", "title", "what", "yet", "zone"], registryDependencies: ["utils"] }, { name: "field", type: "registry:ui", title: "Field", description: "Label, control, description and error as one unit. Controls inside (Input, Textarea, Select, Checkbox, Switch, RadioGroup) get id, aria-describedby, aria-invalid and required automatically; errors spring in and are announced.", exports: ["useFieldControl", "useFieldLabel", "Field"], keywords: ["and", "aria", "automatically", "beside", "can", "checkbox", "children", "control", "controls", "describedby", "description", "dims", "disabled", "disables", "enclosing", "error", "explicit", "field", "fieldset", "fills", "first", "for", "form", "from", "group", "have", "hint", "horizontal", "ids", "input", "invalid", "its", "label", "makes", "marks", "match", "must", "names", "one", "orientation", "out", "outside", "own", "picks", "props", "radiogroup", "rating", "renders", "required", "returns", "shown", "single", "springs", "state", "switch", "target", "the", "unchanged", "under", "unit", "use", "when", "which", "win", "wired", "with"], registryDependencies: ["label", "utils"] }, { name: "focus", type: "registry:lib", description: "What Tab reaches inside `root`, in order. Skips disabled and hidden fields, tabIndex -1 (a\nFormValue helper) and all but one radio per group, so the first and last are real stops.", exports: ["tabbables"], keywords: ["all", "and", "are", "but", "disabled", "fields", "first", "formvalue", "group", "helper", "hidden", "inside", "last", "one", "order", "per", "radio", "reaches", "real", "root", "skips", "stops", "tab", "tabindex", "the", "what"], registryDependencies: [] }, { name: "form-value", type: "registry:lib", description: "Submits a custom control's value and takes part in native validation, which a hidden input\ncan't: an empty `required` value blocks submit, shows the browser's message and focuses `anchor`.", exports: ["FormValue", "useFormReset"], keywords: ["action", "after", "anchor", "and", "back", "blocks", "browser", "button", "called", "calls", "can", "control", "controlled", "custom", "default", "disabled", "does", "empty", "field", "finds", "focus", "focuses", "form", "gets", "goes", "hidden", "input", "invalid", "its", "leaves", "like", "message", "name", "native", "nothing", "oninvalid", "only", "onreset", "onvaluechange", "out", "parent", "part", "picked", "react", "reports", "required", "reset", "resets", "shows", "skips", "state", "submit", "submits", "succeeds", "takes", "that", "the", "there", "through", "too", "validation", "value", "visible", "when", "which", "without"], registryDependencies: [] }, { name: "highlight", type: "registry:lib", description: "Lines of colored tokens, with Shiki loaded on demand. Runs anywhere: Code calls it in the\nbrowser, and a Server Component or a build can call it ahead of time and pass the result as\n`tokens`, so the browser never loads Shiki. Null for unknown languages.", exports: ["highlightCode", "languageOf"], keywords: ["ahead", "and", "anywhere", "back", "browser", "build", "call", "calls", "can", "code", "colored", "component", "demand", "extension", "falls", "file", "for", "from", "language", "languages", "lines", "loaded", "loads", "name", "never", "null", "pass", "result", "runs", "server", "shiki", "the", "time", "tokens", "tsx", "unknown", "with"], registryDependencies: [] }, { name: "hover-card", type: "registry:ui", title: "Hover Card", description: "Rich preview (profile, product, clip) that opens after a hover delay or keyboard focus. The pointer can move into the card; supplementary only.", exports: ["HoverCard"], keywords: ["acts", "align", "alignment", "avatar", "before", "bio", "can", "card", "children", "clip", "closedelay", "closes", "content", "first", "flips", "focus", "hover", "into", "keyboard", "leaves", "link", "move", "must", "once", "only", "opendelay", "opens", "pointer", "preferred", "preview", "product", "profile", "rich", "room", "second", "side", "stats", "supplementary", "tap", "the", "there", "time", "touch", "trigger", "usually", "when", "with", "without", "work"], registryDependencies: ["platform", "use-anchor", "use-dismiss", "use-hover-open", "utils"] }, { name: "input", type: "registry:ui", title: "Forms", description: "Input, Textarea, Label and Switch: smooth focus rings, native :user-invalid validation (red only after interaction), aria-invalid, auto-growing textarea with an optional character count, and a spring switch.", exports: ["fieldClass", "Input"], keywords: ["and", "aria", "chrome", "field", "filled", "group", "handed", "inputgroup", "inside", "the", "works"], registryDependencies: ["field", "utils"] }, { name: "input-group", type: "registry:ui", title: "Input Group", description: "Server-safe wrapper that fuses an Input with icons, text (https://, USD) or buttons into one field with a shared border and focus ring. Clicking an addon focuses the input.", exports: ["InputGroup", "InputGroupAddon"], keywords: ["addons", "align", "and", "beside", "border", "button", "buttons", "clicking", "control", "data", "draws", "drops", "field", "focus", "focuses", "group", "https", "icon", "icons", "inner", "input", "its", "like", "made", "matched", "one", "own", "plain", "plus", "ring", "side", "sits", "slot", "text", "the"], registryDependencies: ["utils"] }, { name: "input-otp", type: "registry:ui", title: "Input OTP", description: "One-time code in separate slots, backed by one real input: paste and SMS autofill work, screen readers hear one field. Grouped slots, numeric or alphanumeric, characters spring in, onComplete to submit. verified turns each character into a check, then merges them into one mark.", exports: ["InputOTP"], keywords: ["accepted", "again", "allowed", "alphanumeric", "and", "announced", "are", "autocomplete", "autofill", "back", "beside", "between", "called", "change", "character", "characters", "charset", "check", "code", "controlled", "counts", "dash", "defaultvalue", "draws", "dropped", "each", "edit", "every", "false", "filled", "fires", "first", "for", "group", "groups", "input", "into", "label", "laid", "length", "mark", "merge", "merged", "number", "once", "oncomplete", "one", "only", "onvaluechange", "others", "over", "pair", "paste", "pasted", "per", "read", "readers", "real", "render", "screen", "set", "shown", "single", "sits", "slides", "slot", "slots", "sms", "submit", "the", "then", "time", "transparently", "turns", "typed", "uncontrolled", "uppercased", "value", "verified", "verifiedlabel", "via", "visual", "was", "with"], registryDependencies: ["field", "form-value", "use-controllable-state", "utils"] }, { name: "kbd", type: "registry:ui", title: "Kbd", description: "Server-safe key caps for shortcut hints. KbdGroup nests keys per the HTML spec.", exports: ["Kbd", "KbdGroup"], keywords: ["cap", "html", "kbd", "kbdgroup", "key", "keys", "made", "nested", "one", "per", "several", "shortcut", "spec", "the"], registryDependencies: ["utils"] }, { name: "label", type: "registry:ui", title: "Forms", description: "Input, Textarea, Label and Switch: smooth focus rings, native :user-invalid validation (red only after interaction), aria-invalid, auto-growing textarea with an optional character count, and a spring switch.", exports: ["Label"], keywords: [], registryDependencies: ["utils"] }, { name: "list", type: "registry:lib", description: "The item a navigation key moves to in a list, from the item at `index` (-1: none active).\nUndefined when the key doesn't navigate.", exports: ["listKeyTarget", "scrollIntoList"], keywords: ["active", "box", "can", "doesn", "enough", "from", "index", "item", "just", "key", "list", "moves", "moving", "navigate", "navigation", "none", "page", "scrollintoview", "scrolls", "show", "the", "undefined", "way", "when", "without"], registryDependencies: ["utils"] }, { name: "marker", type: "registry:ui", title: "Marker", description: "Highlighter-pen <mark> in four tints that wraps cleanly across lines and can draw itself on, left to right, when it scrolls into view. Highlight marks every match of a search in a string, for transcripts and search results.", exports: ["Marker", "Highlight"], keywords: ["across", "before", "case", "cleanly", "color", "delay", "draw", "drawing", "draws", "every", "first", "full", "fully", "highlight", "highlighter", "insensitive", "inside", "instant", "into", "left", "lines", "mark", "markers", "marks", "match", "motion", "one", "paragraph", "pen", "query", "reduced", "right", "scrolls", "search", "seconds", "several", "shown", "stagger", "sweeps", "term", "terms", "text", "the", "time", "view", "with", "without", "wraps"], registryDependencies: ["utils"] }, { name: "menubar", type: "registry:ui", title: "Menubar", description: "Desktop-style menu bar on the WAI-ARIA menubar pattern: one menu open at a time, hovering the next title while one is open switches to it, ← → walk the bar with or without a menu open, one Tab stop. A single highlight springs between titles; menus reuse every DropdownMenu item.", exports: ["MenubarContent", "MenubarItem", "MenubarCheckboxItem", "MenubarRadioGroup", "MenubarRadioItem", "MenubarLabel", "MenubarSeparator", "Menubar", "MenubarMenu", "MenubarTrigger"], keywords: ["actions", "align", "aligned", "analytics", "and", "another", "apart", "bar", "between", "but", "called", "check", "checked", "children", "click", "closes", "desktop", "destructive", "disabled", "down", "dropdownmenuradioitem", "edge", "enter", "equals", "first", "for", "generated", "give", "group", "groups", "highlight", "hint", "hovering", "icon", "irreversible", "item", "items", "its", "label", "labels", "last", "leading", "left", "lines", "mark", "menu", "menubarcontent", "menubartrigger", "menus", "move", "new", "not", "omitted", "oncheckedchange", "one", "onselect", "onvaluechange", "open", "opens", "optional", "optionally", "picked", "red", "right", "selectable", "separators", "shortcut", "shown", "shows", "space", "springs", "stable", "state", "style", "switches", "tell", "tests", "the", "then", "time", "title", "titles", "toggles", "trigger", "value", "variant", "when", "while", "with"], registryDependencies: ["dropdown-menu", "list", "use-indicator", "utils"] }, { name: "message-bubble", type: "registry:ui", title: "Message Bubble", description: "User/assistant chat bubbles with avatar slot, streaming dots and copyable code blocks.", exports: ["MessageBubble"], keywords: ["above", "accent", "after", "and", "animated", "are", "assistant", "attachments", "avatar", "bubble", "chat", "children", "code", "content", "dots", "from", "inline", "left", "lists", "message", "name", "next", "rich", "right", "sender", "shown", "shows", "side", "size", "slot", "streaming", "surface", "text", "the", "time", "typing", "user"], registryDependencies: ["utils"] }, { name: "meter", type: "registry:ui", title: "Meter", description: "Server-safe gauge for a value in a known range (storage, quota, server load, password strength). Colors by <meter> thresholds, or stacked segments with a legend, like a storage bar.", exports: ["Meter"], keywords: ["above", "accessible", "also", "and", "away", "bar", "below", "between", "bound", "color", "colors", "decides", "defaults", "disk", "follow", "good", "green", "high", "known", "label", "legend", "like", "load", "low", "lower", "max", "measurement", "meter", "min", "name", "not", "one", "optimum", "orange", "overrides", "parts", "password", "progress", "quota", "range", "red", "region", "right", "segments", "server", "stacked", "storage", "strength", "sum", "task", "text", "the", "their", "this", "threshold", "thresholds", "total", "two", "upper", "use", "used", "value", "valuetext", "visible", "where", "with", "within"], registryDependencies: ["utils"] }, { name: "motion-provider", type: "registry:component", exports: ["MotionProvider"], keywords: [], registryDependencies: ["utils"] }, { name: "native-select", type: "registry:ui", title: "Native Select", description: "A real <select> with option groups: native picker on phones, no-JS forms, native required validation. Where customizable select is supported (appearance: base-select), the open list gets the menu look.", exports: ["NativeSelect", "NativeSelectGroup", "NativeSelectOption"], keywords: ["appearance", "base", "browser", "custom", "customizable", "elsewhere", "forms", "gets", "group", "heading", "icons", "instead", "invalid", "label", "labeled", "list", "look", "menu", "native", "need", "open", "optgroup", "options", "phones", "picker", "real", "rendering", "required", "select", "stays", "supports", "the", "use", "user", "validation", "when", "where", "without", "works"], registryDependencies: ["field", "input", "utils"] }, { name: "navigation-menu", type: "registry:ui", title: "Navigation Menu", description: "Website top navigation on the APG disclosure pattern: plain links and panels of rich links. Hover opens after a beat, then sliding along the bar switches panels at once; every panel shares one viewport that springs to the next panel's size and position while the content slides in from the side you moved toward. Click, Enter, ↓, ← → and Esc work; closed panels keep their links in the HTML for crawlers.", exports: ["NavigationMenu", "NavigationMenuList", "NavigationMenuItem", "NavigationMenuTrigger", "NavigationMenuContent", "NavigationMenuLink"], keywords: ["active", "after", "all", "along", "and", "aria", "bar", "beat", "bold", "called", "children", "classname", "click", "closed", "closes", "content", "controlled", "crawlers", "current", "defaultvalue", "description", "down", "enter", "entry", "esc", "find", "first", "focuses", "following", "for", "generated", "hidden", "hover", "html", "icon", "inset", "into", "item", "items", "its", "left", "level", "line", "link", "links", "look", "matched", "menu", "mouse", "muted", "navigation", "omitted", "once", "one", "onvaluechange", "open", "opened", "opens", "optional", "own", "padding", "page", "pair", "panel", "panels", "pill", "plain", "readers", "rem", "render", "rich", "right", "row", "selected", "single", "size", "sliding", "space", "springs", "stable", "stay", "still", "switches", "the", "them", "then", "tile", "title", "toggle", "top", "trigger", "uncontrolled", "under", "value", "viewport", "walk", "website", "while", "with", "your"], registryDependencies: ["focus", "use-controllable-state", "use-dismiss", "use-indicator", "utils"] }, { name: "newsletter", type: "registry:block", title: "Newsletter", description: "Email signup: InputGroup with the submit Button inside, native email validation, a loading → success toast and an in-place success state on the button.", exports: ["Newsletter"], keywords: ["above", "address", "after", "and", "button", "check", "confirm", "confirms", "description", "email", "field", "for", "form", "heading", "inbox", "inputgroup", "inside", "into", "its", "line", "loading", "message", "native", "next", "onsubscribe", "place", "placeholder", "rejection", "resets", "resolves", "saved", "shows", "signup", "submit", "submitlabel", "success", "successmessage", "supporting", "text", "that", "the", "then", "title", "toast", "turns", "under", "validation", "when", "with", "your"], registryDependencies: ["button", "input", "input-group", "toast", "utils"] }, { name: "number-input", type: "registry:ui", title: "Number Input", description: "Native number field with −/+ buttons: arrow keys, min/max/step validation and form submit from the platform; clamps on blur, rounds to the step's precision.", exports: ["NumberInput"], keywords: ["amount", "and", "are", "arrows", "arrowup", "buttons", "called", "cart", "change", "controlled", "defaultvalue", "down", "field", "first", "form", "helpers", "highest", "keyboard", "lowest", "max", "min", "native", "new", "number", "onvaluechange", "pair", "pointer", "quantity", "render", "step", "submit", "tabindex", "the", "uncontrolled", "use", "users", "validation", "value", "with"], registryDependencies: ["form-value", "input", "input-group", "use-controllable-state", "utils"] }, { name: "overflow-focus", type: "registry:lib", description: `Drop inside a scroll container: while the container overflows it becomes keyboard-focusable
|
|
17856
18045
|
(tabIndex 0) so arrow keys can scroll it, and it leaves the tab order again once it fits.
|
|
17857
|
-
Renders a hidden span, so server components (Table) can use it as a child.`, exports: ["OverflowFocus"], keywords: ["again", "and", "arrow", "becomes", "can", "child", "components", "container", "drop", "fits", "focusable", "hidden", "inside", "keyboard", "keys", "leaves", "once", "order", "overflows", "renders", "scroll", "server", "span", "tab", "tabindex", "table", "the", "use", "while"], registryDependencies: [] }, { name: "pagination", type: "registry:ui", title: "Pagination", description: "Server-safe page links from page and total: previous/next, ellipsis gaps and a sliding active pill, with numbers that swipe the way you moved. Links by default; buttons with onPageChange. The row keeps a fixed width so it never jumps.", exports: ["Pagination", "pageRange"], keywords: ["active", "always", "and", "arrows", "bar", "become", "between", "buttons", "called", "changes", "client", "controls", "current", "default", "each", "ellipsis", "first", "for", "from", "gaps", "href", "index", "instead", "items", "its", "keeps", "kept", "key", "labels", "last", "length", "links", "mounted", "moved", "never", "next", "number", "numbers", "one", "only", "onpagechange", "page", "pagerange", "pages", "parents", "phones", "picked", "pill", "previous", "remounting", "render", "returns", "row", "same", "show", "siblings", "side", "slides", "slot", "stays", "swipe", "swipes", "text", "that", "the", "total", "unused", "url", "way", "width", "with", "without", "work", "you"], registryDependencies: ["button-variants", "pagination-motion", "utils"] }, { name: "pagination-motion", type: "registry:ui", description: "Scopes the active pill's `layoutId` so several paginations on a page don't share it.", exports: ["PaginationMotion", "PaginationActivePill", "PaginationNumber"], keywords: ["active", "behind", "changes", "children", "direction", "don", "from", "items", "its", "layoutid", "link", "moved", "next", "number", "outline", "page", "pagination", "paginations", "pill", "scopes", "several", "share", "side", "slot", "springs", "swipes", "that", "the", "toward", "travel", "value", "when"], registryDependencies: ["utils"] }, { name: "platform", type: "registry:lib", description: "False on the server and during hydration, true after: gates portals and other client-only output.", exports: ["useMounted", "useIsMac", "useMediaQuery"], keywords: ["after", "and", "assumes", "client", "corrects", "ctrl", "during", "false", "gates", "hydration", "live", "match", "media", "not", "only", "other", "output", "portals", "query", "render", "server", "shortcuts", "ssr", "the", "then", "true", "use", "where"], registryDependencies: [] }, { name: "popover", type: "registry:ui", title: "Popover", description: "Non-modal panel anchored to a Button for filters, pickers and quick edits. Focus moves in, Esc returns it, outside clicks close; Selects and menus opened inside don't count as outside.", exports: ["Popover", "PopoverTrigger", "PopoverContent"], keywords: ["above", "action", "align", "alignment", "also", "anchored", "and", "any", "aria", "armed", "before", "blur", "both", "button", "called", "can", "check", "children", "click", "clicks", "closed", "closes", "confirm", "confirmlabel", "confirmtimeout", "content", "controlled", "default", "defaultopen", "defaults", "destructive", "done", "edits", "emphasis", "end", "enter", "esc", "feedback", "field", "fill", "filters", "first", "flips", "focus", "focusable", "for", "form", "from", "give", "height", "icon", "ignore", "ignores", "irreversible", "its", "keep", "label", "left", "like", "loading", "loadinglabel", "modal", "moves", "never", "non", "not", "old", "onclick", "one", "ones", "onopenchange", "open", "out", "outside", "padding", "pair", "panel", "past", "per", "pickers", "place", "popovercontent", "popovertrigger", "popup", "preferred", "primary", "quick", "render", "reserved", "resets", "resize", "returns", "room", "runs", "second", "shift", "shown", "shows", "side", "size", "slides", "spinner", "spins", "square", "state", "status", "stays", "step", "submit", "success", "successlabel", "swap", "swaps", "tab", "text", "the", "there", "this", "trap", "trigger", "twice", "two", "uncontrolled", "unmount", "values", "variant", "view", "when", "where", "while", "width", "with"], registryDependencies: ["button", "focus", "platform", "use-anchor", "use-controllable-state", "use-dismiss", "utils"] }, { name: "pricing", type: "registry:block", title: "Pricing", description: "Plan cards with a monthly / yearly segmented switch. Prices roll on a spring in the direction they moved; the featured plan is outlined in the accent. Card, Badge, ToggleGroup, Button.", exports: ["Pricing"], keywords: ["and", "bcp", "billing", "cards", "code", "currency", "defaultperiod", "display", "down", "drop", "eur", "first", "for", "formatting", "iso", "locale", "monthly", "next", "number", "order", "period", "plan", "plans", "price", "prices", "render", "rise", "rolling", "segmented", "selected", "spring", "swap", "switch", "the", "they", "togglegroup", "when", "with", "yearly", "yearlynote"], registryDependencies: ["badge", "card", "toggle-group", "utils"] }, { name: "product-card", type: "registry:block", title: "Product Card", description: "Shop tile: photos in an AspectRatio (a swipeable Carousel with dots when there are several), sale price, Badge and add-to-cart Button.", exports: ["ProductCard"], keywords: ["add", "addlabel", "are", "aspectratio", "badge", "bcp", "become", "button", "called", "card", "carousel", "code", "compareat", "currency", "current", "dots", "for", "formatting", "heading", "height", "href", "img", "iso", "label", "link", "locale", "major", "media", "name", "new", "number", "onaddtocart", "one", "original", "over", "page", "per", "photos", "pressed", "price", "product", "ratio", "sale", "several", "shop", "shows", "slide", "small", "square", "struck", "swipeable", "text", "the", "there", "through", "tile", "units", "when", "width", "with"], registryDependencies: ["aspect-ratio", "badge", "button", "carousel", "utils"] }, { name: "progress", type: "registry:ui", title: "Progress", description: "Accent bar that springs to each new value, with an optional label and percentage. Leave out value for an indeterminate sweep (static under reduced motion).", exports: ["Progress"], keywords: ["above", "accent", "accessible", "across", "allowed", "also", "aria", "bar", "colored", "duration", "each", "for", "frame", "indeterminate", "infinite", "its", "label", "layout", "max", "motion", "name", "new", "next", "omit", "one", "per", "percentage", "reduced", "scalex", "segment", "show", "showvalue", "size", "springs", "static", "sweeps", "that", "the", "there", "thickness", "tween", "under", "unknown", "value", "visible", "when", "work"], registryDependencies: ["utils"] }, { name: "quote", type: "registry:ui", title: "Quote", description: "Semantic figure, blockquote and figcaption. Plain, card and large variants, with author, subtitle, avatar, lang, typewriter and textSwap.", exports: ["Quote"], keywords: ["accent", "alt", "and", "array", "author", "avatar", "blockquote", "card", "children", "correct", "custom", "cycles", "delay", "display", "doe", "effect", "enables", "fades", "figcaption", "figure", "for", "into", "jane", "job", "lang", "language", "large", "line", "mark", "options", "pass", "plain", "pronunciation", "quote", "quoted", "quotes", "reader", "said", "screen", "scrolls", "secondary", "semantic", "seo", "size", "slot", "speed", "spring", "string", "strings", "subtitle", "supports", "surface", "swap", "text", "textswap", "the", "through", "title", "transitions", "typewriter", "under", "variant", "view", "when", "who", "with", "work", "wrote"], registryDependencies: ["text-swap", "typewriter", "utils"] }, { name: "radio-group", type: "registry:ui", title: "Radio Group", description: "Native radios in a fieldset with a legend: browser arrow keys and form submit, a dot that springs in, optional per-option descriptions, vertical or horizontal.", exports: ["RadioGroup", "RadioGroupItem"], keywords: ["adds", "and", "application", "aria", "arrow", "browser", "but", "called", "children", "conform", "controlled", "defaultvalue", "description", "disabled", "does", "enclosing", "entered", "expected", "field", "fieldset", "first", "form", "format", "generated", "group", "indicates", "invalid", "item", "items", "its", "keys", "label", "labels", "lay", "legend", "line", "muted", "name", "names", "native", "needs", "not", "omitted", "one", "onvaluechange", "option", "orientation", "pair", "picked", "radios", "render", "rendered", "required", "row", "selectable", "shown", "stack", "submit", "submitted", "the", "them", "uncontrolled", "under", "value", "visible", "when", "with", "without", "work"], registryDependencies: ["field", "form-value", "use-controllable-state", "utils"] }, { name: "rating", type: "registry:ui", title: "Rating", description: "Star rating on native radios (arrow keys, forms) with hover preview, or readOnly display with fractional stars for averages.", exports: ["Rating"], keywords: ["and", "aria", "arrow", "called", "can", "chosen", "controlled", "defaults", "defaultvalue", "describedby", "disabled", "display", "else", "enclosing", "error", "field", "filled", "fills", "first", "for", "form", "forms", "fractions", "from", "group", "hidden", "hint", "hover", "ids", "image", "included", "inputs", "interactive", "invalid", "isn", "keys", "label", "lift", "max", "name", "native", "needs", "number", "only", "onvaluechange", "pair", "partially", "picked", "press", "preview", "radios", "rating", "readers", "readonly", "render", "renders", "required", "score", "screen", "sets", "size", "spring", "star", "stars", "styling", "submit", "submitted", "text", "the", "uncontrolled", "value", "visually", "with"], registryDependencies: ["field", "form-value", "use-controllable-state", "utils"] }, { name: "relative-time", type: "registry:ui", title: "Relative Time", description: "Self-updating “5 minutes ago” in a semantic <time> with the full date as its title. Server renders the absolute date (no hydration mismatch); one shared ticker for the whole page.", exports: ["RelativeTime"], keywords: ["absolute", "ago", "and", "both", "client", "crawlers", "current", "date", "default", "differ", "full", "get", "hydration", "its", "itself", "keeps", "locale", "minutes", "mismatch", "not", "now", "one", "page", "pass", "real", "relative", "render", "renders", "semantic", "server", "shared", "stored", "text", "that", "the", "there", "ticker", "time", "timestamp", "title", "with", "would"], registryDependencies: ["utils"] }, { name: "resizable", type: "registry:ui", title: "Resizable", description: "Split panes for admin layouts, docs and editors. Hairline dividers with a wide grab area follow the pointer; collapsible panels snap shut past half their minimum. Dividers are APG window splitters: arrows step, Home/End jump to the limits, Enter or double-click collapses with a spring. Groups nest.", exports: ["ResizablePanelGroup", "ResizablePanel", "ResizableHandle"], keywords: ["adds", "admin", "after", "already", "and", "apg", "are", "arrow", "arrows", "basis", "between", "click", "collapsed", "collapsedsize", "collapses", "collapsible", "component", "cookie", "css", "default", "defaultsize", "direction", "divider", "docs", "dom", "double", "drag", "dragging", "each", "editors", "end", "enter", "evenly", "final", "find", "flex", "follows", "for", "grip", "group", "half", "handle", "handles", "home", "horizontal", "hydration", "icon", "jump", "key", "keyboardstep", "keys", "largest", "layout", "layouts", "left", "limits", "maxsize", "middle", "minimum", "minsize", "neighbor", "one", "onlayout", "order", "others", "panels", "panes", "parents", "past", "per", "plain", "pointer", "press", "rail", "rest", "save", "server", "share", "shows", "shut", "side", "size", "sizes", "smallest", "snaps", "split", "splitters", "spring", "ssr", "stacked", "starting", "step", "the", "their", "them", "toggle", "toggles", "two", "until", "vertical", "visible", "what", "when", "window", "withhandle", "without", "work", "wrappers"], registryDependencies: ["utils"] }, { name: "reveal", type: "registry:ui", title: "Reveal", description: "Scroll reveal: content fades and rises on a spring the first time it enters the viewport.", exports: ["Reveal"], keywords: ["after", "and", "content", "delay", "entering", "fades", "first", "into", "offset", "place", "rises", "scrolls", "seconds", "spring", "start", "stays", "the", "time", "view", "visible", "wait"], registryDependencies: ["utils"] }, { name: "scroll-area", type: "registry:ui", title: "Scroll Area", description: "Overlay scrollbars: thin thumbs that fade in while scrolling or on hover, widen under the pointer and drag. Scrolling stays native; the labeled region is keyboard-focusable.", exports: ["ScrollArea"], keywords: ["also", "and", "aria", "axes", "can", "classes", "drag", "fade", "focus", "for", "get", "give", "goes", "height", "here", "horizontal", "hover", "itself", "keyboard", "label", "mouse", "names", "native", "one", "orientation", "overlay", "padding", "region", "scroll", "scrollable", "scrollbars", "scrolling", "stays", "takes", "that", "the", "thin", "thumb", "thumbs", "touch", "users", "viewport", "viewportclassname", "wheel", "while", "widen"], registryDependencies: ["utils"] }, { name: "search-input", type: "registry:ui", title: "Search Input", description: 'Search field built on Input: leading icon, spring-in clear button, Esc to clear, and an optional focus shortcut ("/" or mod+k) with a Kbd hint.', exports: ["SearchInput"], keywords: ["accessible", "already", "and", "anywhere", "aside", "border", "browser", "button", "called", "change", "clear", "cleared", "clears", "command", "controlled", "ctrl", "default", "defaultvalue", "elsewhere", "embedded", "esc", "every", "field", "find", "first", "focus", "focused", "focuses", "for", "from", "ghost", "hidden", "hint", "htmlfor", "icon", "inputs", "kbd", "key", "label", "mac", "mod", "name", "onvaluechange", "opens", "optional", "own", "pair", "palette", "pass", "query", "render", "ring", "search", "second", "shadow", "shortcut", "single", "skipped", "spring", "steps", "still", "surface", "the", "uncontrolled", "value", "variant", "when", "while", "with", "you"], registryDependencies: ["input", "kbd", "platform", "use-controllable-state", "use-hotkey", "utils"] }, { name: "segmented-tabs", type: "registry:ui", title: "AppWindow", description: "Window container with close / minimize / zoom controls, title, custom header slot and actions. SegmentedTabs is the compact view switcher for its title bar.", exports: ["SegmentedTabs"], keywords: ["accessible", "active", "also", "always", "aria", "behind", "called", "current", "for", "indicator", "item", "items", "label", "labels", "list", "marked", "name", "onchange", "one", "pressed", "segmented", "slides", "spring", "tab", "tabs", "the", "under", "used", "value", "values", "with"], registryDependencies: ["overflow-focus", "use-indicator", "utils"] }, { name: "select", type: "registry:ui", title: "Select", description: "Single-choice listbox styled like Input, with groups, icons and disabled options. Native-select keyboard (arrows, typeahead even while closed), flips above the trigger near the viewport edge, submits and validates with the form.", exports: ["Select"], keywords: ["accessible", "also", "aria", "blocks", "called", "cancel", "choice", "closed", "controlled", "defaultvalue", "describedby", "disabled", "down", "end", "enter", "error", "esc", "field", "fill", "fills", "first", "flat", "for", "form", "from", "groups", "hint", "home", "htmlfor", "ids", "input", "invalid", "isn", "keyboard", "label", "labeled", "let", "like", "listbox", "matches", "name", "native", "needs", "nothing", "onvaluechange", "opening", "options", "pair", "pick", "picked", "placeholder", "render", "required", "select", "sets", "single", "space", "styled", "styling", "submit", "submitted", "text", "the", "there", "trigger", "typeahead", "uncontrolled", "value", "visible", "when", "while", "with"], registryDependencies: ["field", "form-value", "input", "list", "platform", "use-anchor", "use-controllable-state", "use-dismiss", "utils"] }, { name: "separator", type: "registry:ui", title: "Separator", description: 'Server-safe hairline, horizontal or vertical, decorative by default, with an optional centered label like "or".', exports: ["Separator"], keywords: ["assistive", "centered", "content", "decorative", "default", "direction", "false", "for", "horizontal", "label", "line", "only", "orientation", "purely", "separates", "set", "tech", "text", "the", "visual", "when"], registryDependencies: ["utils"] }, { name: "sidebar", type: "registry:ui", title: "Sidebar", description: "App navigation from data: header, groups (optionally foldable), badges and pinned footer entries. Collapses to an icon rail on a spring (⌘B / Ctrl+B) with labels kept for assistive tech; below md it is a left drawer.", exports: ["SidebarLayout", "Sidebar", "SidebarTrigger"], keywords: ["a11y", "accessible", "and", "aria", "below", "bottom", "but", "called", "client", "clip", "collapsed", "collapses", "controlled", "ctrl", "current", "data", "default", "defaultcollapsed", "drawer", "entries", "field", "footer", "for", "from", "groups", "header", "highlighted", "holds", "icon", "ignored", "item", "label", "labels", "landmark", "lays", "left", "links", "name", "navigation", "oncollapsedchange", "onselect", "page", "pair", "picked", "pinned", "rail", "routing", "settings", "shortcut", "side", "sidebar", "slot", "spring", "start", "state", "stay", "the", "toggle", "too", "top", "tree", "trigger", "true", "typing", "uncontrolled", "when", "while", "with", "workspace"], registryDependencies: ["button", "collapsible", "drawer", "platform", "use-controllable-state", "use-hotkey", "utils"] }, { name: "site-header", type: "registry:block", title: "Site Header", description: "Sticky, translucent site header from one nav array: brand, NavigationMenu panels from md up, a right Drawer with the same links below it, and your actions on the right. The hairline underneath appears only once content scrolls beneath it.", exports: ["SiteHeader"], keywords: ["accessible", "acme", "actions", "alike", "and", "appears", "aria", "below", "brand", "client", "close", "controls", "current", "demo", "desktop", "drawer", "every", "for", "from", "hairline", "hand", "header", "href", "label", "left", "level", "link", "links", "logo", "marks", "matching", "name", "nav", "navigation", "navigationmenu", "once", "onclick", "only", "onnavigate", "over", "page", "panels", "right", "routing", "same", "scrolls", "search", "shown", "sign", "site", "sticky", "stub", "the", "theme", "top", "under", "width", "with"], registryDependencies: ["button", "drawer", "navigation-menu", "use-disclosure", "utils"] }, { name: "skeleton", type: "registry:ui", title: "Skeleton", description: 'Server-safe loading placeholders that pulse (static under reduced motion). SkeletonGroup announces one "Loading" to screen readers.', exports: ["Skeleton", "SkeletonGroup"], keywords: ["announced", "block", "classes", "classname", "hear", "instead", "label", "loading", "nothing", "one", "placeholder", "readers", "screen", "shown", "size", "skeleton", "skeletons", "this", "while", "with", "wrap"], registryDependencies: ["utils"] }, { name: "slider", type: "registry:ui", title: "Slider", description: "Slider over native range inputs: thin accent track, white knob that lifts while held. One value or two for a range; keyboard, forms, touch and Field wiring come from the platform.", exports: ["Slider"], keywords: ["accent", "accessible", "and", "aren", "aria", "arrow", "blocks", "browser", "called", "defaults", "defaultvalue", "describedby", "disabled", "does", "drag", "dragging", "error", "every", "field", "fills", "filter", "first", "for", "forms", "from", "held", "highest", "hint", "ids", "increment", "inputs", "invalid", "keyboard", "keys", "knob", "labels", "lifts", "lowest", "max", "maximum", "min", "minimum", "name", "names", "native", "one", "onvaluechange", "over", "per", "price", "range", "readers", "render", "screen", "sets", "slider", "step", "styling", "submitted", "text", "that", "the", "thin", "thumb", "touch", "track", "twice", "two", "uncontrolled", "value", "values", "while", "white", "with", "work"], registryDependencies: ["field", "form-value", "use-controllable-state", "utils"] }, { name: "spinner", type: "registry:ui", title: "Spinner", description: "Server-safe activity indicator: eight spokes in currentColor, three sizes, its own status label (or silent inside a parent that announces). Holds still under reduced motion.", exports: ["Spinner"], keywords: ["accessible", "activity", "already", "announces", "busy", "currentcolor", "diameter", "eight", "holds", "indicator", "label", "motion", "name", "parent", "pass", "reduced", "size", "spokes", "state", "still", "the", "under", "when"], registryDependencies: ["utils"] }, { name: "stat-tile", type: "registry:block", title: "Stat Tile", description: "Admin dashboard metric: value, a delta Badge that is green when the change is good news (invert for errors and latency), and an area Chart of the trend. Server-safe.", exports: ["StatTile"], keywords: ["admin", "and", "area", "axis", "badge", "below", "change", "chart", "dashboard", "data", "datakey", "delta", "down", "each", "errors", "for", "format", "formatted", "good", "green", "headline", "index", "intl", "invert", "key", "label", "last", "latency", "metric", "metrics", "month", "name", "news", "number", "period", "positive", "revenue", "row", "rows", "the", "tile", "tooltip", "trend", "unless", "value", "when", "where"], registryDependencies: ["badge", "card", "chart", "utils"] }, { name: "stats-strip", type: "registry:block", title: "Stats Strip", description: "Headline numbers, each card a <dl>: cards rise in turn with Reveal, then each number springs up with CountTo. Final values are in the HTML.", exports: ["StatsStrip"], keywords: ["card", "display", "each", "headline", "its", "number", "numbers", "order", "rises", "row", "springs", "stats", "then", "turn"], registryDependencies: ["card", "count-to", "reveal", "utils"] }, { name: "stepper", type: "registry:ui", title: "Stepper", description: "Multi-step form (questionnaire, checkout, setup wizard). Steps stay mounted so values survive Back and submit together; Continue checks only the current step's native constraints, plus an optional async onNext guard with a spinner. Progress header with springing connectors; works with onSubmit or a Server Action.", exports: ["Stepper", "StepperList", "StepperStep", "StepperFooter"], keywords: ["action", "after", "and", "are", "back", "backlabel", "button", "called", "check", "checks", "children", "connector", "constraints", "continue", "controlled", "current", "defaultvalue", "description", "every", "false", "fields", "fills", "finished", "first", "for", "form", "header", "hidden", "inactive", "its", "last", "links", "matches", "mounted", "multi", "native", "new", "nextlabel", "not", "one", "only", "onnext", "onsubmit", "onvaluechange", "optional", "order", "pass", "progress", "render", "resolve", "return", "runs", "server", "shown", "shows", "spinner", "spring", "start", "stay", "stays", "step", "steps", "submit", "submitlabel", "submits", "survive", "text", "the", "through", "title", "together", "uncontrolled", "unmounted", "value", "values", "while", "with"], registryDependencies: ["button", "focus", "use-controllable-state", "utils"] }, { name: "styles", type: "registry:file", exports: [], keywords: [], registryDependencies: [] }, { name: "switch", type: "registry:ui", title: "Forms", description: "Input, Textarea, Label and Switch: smooth focus rings, native :user-invalid validation (red only after interaction), aria-invalid, auto-growing textarea with an optional character count, and a spring switch.", exports: ["Switch"], keywords: ["called", "checkbox", "checked", "controlled", "defaultchecked", "first", "knob", "native", "new", "oncheckedchange", "pair", "render", "role", "spring", "state", "switch", "the", "uncontrolled", "with"], registryDependencies: ["field", "form-value", "use-controllable-state", "utils"] }, { name: "table", type: "registry:ui", title: "Table", description: "Server-safe semantic <table> in a scrolling card: header, body, footer and caption, striped rows, tinted selected rows, and sortable headers that set aria-sort and flip their arrow.", exports: ["Table", "TableHeader", "TableBody", "TableFooter", "TableRow", "TableHead", "TableCell", "TableCaption"], keywords: ["alternating", "and", "aria", "around", "arrow", "button", "called", "card", "classes", "column", "containerclassname", "data", "direction", "for", "header", "keyboard", "makes", "narrow", "onsort", "pass", "pressed", "real", "requires", "row", "screens", "scrollable", "scrolling", "scrolls", "selected", "sets", "shading", "shows", "sideways", "sort", "sortable", "state", "striped", "table", "that", "the", "then", "tint", "when"], registryDependencies: ["overflow-focus", "utils"] }, { name: "tabs", type: "registry:ui", title: "Tabs", description: "WAI-ARIA tabs with arrow, Home and End keys. One indicator springs between tabs, segmented or underline. Inactive panels stay in the DOM for SEO; the active one fades in.", exports: ["Tabs", "TabsList", "TabsTrigger", "TabsContent"], keywords: ["activated", "active", "always", "and", "aria", "arrows", "bar", "behind", "called", "children", "content", "controlled", "crawlable", "defaultvalue", "dom", "end", "first", "focus", "hidden", "home", "html", "inactive", "indicator", "labels", "matches", "move", "muted", "one", "onvaluechange", "pair", "panel", "panels", "pill", "remains", "render", "segmented", "springs", "stay", "stays", "tab", "tabs", "tabscontent", "tabstrigger", "the", "their", "track", "uncontrolled", "under", "underline", "value", "variant", "wai", "while", "with"], registryDependencies: ["list", "use-controllable-state", "use-indicator", "utils"] }, { name: "tag-input", type: "registry:ui", title: "Tag Input", description: "Free-form tags: Enter or a separator adds, Backspace removes the last, ← walks onto the tags and Delete removes one, pasting a list adds them all. Duplicates, a max and a validate function keep bad tags in the input. Submits one entry per tag; required needs one.", exports: ["tagFieldClass", "Tag", "useTagKeys", "TagInput"], keywords: ["accessible", "add", "after", "allowed", "and", "animatepresence", "backspace", "besides", "button", "call", "called", "children", "chip", "commit", "controlled", "defaultvalue", "delete", "disabled", "each", "empty", "enter", "esc", "every", "false", "field", "first", "focused", "for", "form", "free", "from", "frominput", "hides", "input", "inside", "its", "keyboard", "keydown", "keys", "last", "least", "limit", "list", "many", "max", "mode", "most", "move", "name", "needs", "one", "onkeydown", "onremove", "onto", "onvaluechange", "order", "out", "pair", "paste", "pasted", "per", "poplayout", "press", "put", "reach", "reject", "removable", "remove", "removeref", "removes", "render", "rendered", "required", "return", "returns", "runs", "separator", "separators", "shows", "space", "split", "springs", "stay", "stays", "stops", "submits", "tab", "tag", "tagref", "tags", "text", "that", "the", "them", "these", "too", "trimming", "type", "typed", "typing", "uncontrolled", "usetagkeys", "validate", "value", "walks", "when", "which", "with"], registryDependencies: ["field", "form-value", "use-controllable-state", "utils"] }, { name: "terminal", type: "registry:ui", title: "Terminal", description: "Terminal window. Snippet mode types install commands once scrolled into view, with the full transcript kept in the DOM for SEO and no-JS; log mode streams lines into a live region that follows the bottom unless you scroll up, then offers a jump back. Status lines carry glyphs, copy grabs the commands.", exports: ["Terminal"], keywords: ["and", "append", "arrives", "back", "before", "body", "bodyclassname", "bottom", "character", "clipboard", "commands", "copy", "copytext", "defaults", "dom", "false", "follows", "for", "full", "hides", "install", "jump", "lines", "live", "log", "mode", "new", "offering", "once", "order", "output", "per", "prompt", "puts", "read", "reveals", "role", "scroll", "scrolling", "seo", "size", "snippet", "snippets", "speed", "stays", "sticks", "stream", "streams", "symbol", "terminal", "text", "the", "title", "typed", "types", "typing", "unless", "view", "what", "when", "window", "you"], registryDependencies: ["app-window", "code", "platform", "utils"] }, { name: "testimonial", type: "registry:block", title: "Testimonial", description: "A large Quote with its author, and everyone else who agrees as an overlapping AvatarGroup. Server-safe.", exports: ["Testimonial"], keywords: ["agrees", "and", "author", "avatar", "avatargroup", "behind", "beside", "carried", "children", "company", "crowd", "else", "everyone", "few", "first", "fold", "initials", "into", "its", "lang", "language", "large", "line", "one", "others", "otherslabel", "photo", "quote", "rest", "role", "said", "show", "stack", "streamers", "subtitle", "the", "trusted", "under", "url", "who", "with", "without"], registryDependencies: ["avatar", "avatar-group", "quote", "utils"] }, { name: "text-swap", type: "registry:ui", title: "TextSwap", description: "Spring-powered text morphing and ticker. Auto-cycles phrases or animates whenever children change with blur and slide transitions.", exports: ["TextSwap"], keywords: ["active", "array", "automatically", "between", "changing", "child", "children", "controlling", "cycle", "cycling", "default", "direction", "externally", "hover", "index", "interval", "items", "milliseconds", "mouse", "new", "pass", "pause", "pauseonhover", "single", "string", "swap", "swaps", "text", "the", "through", "true", "when"], registryDependencies: ["utils"] }, { name: "textarea", type: "registry:ui", title: "Forms", description: "Input, Textarea, Label and Switch: smooth focus rings, native :user-invalid validation (red only after interaction), aria-invalid, auto-growing textarea with an optional character count, and a spring switch.", exports: ["Textarea"], keywords: ["auto", "cap", "content", "corner", "end", "field", "firefox", "grows", "max", "maxlength", "near", "needs", "red", "resizable", "show", "showcount", "sizing", "the", "then", "turns", "unsupported", "used", "warning", "where", "with"], registryDependencies: ["field", "form-value", "input", "utils"] }, { name: "theme", type: "registry:lib", description: "Retints primary, focus rings, selection and switches app-wide. Persists like the theme.", exports: ["isDark", "setTheme", "subscribeTheme", "accents", "getAccent", "setAccent", "themeScript"], keywords: ["and", "app", "focus", "like", "persists", "primary", "retints", "rings", "selection", "switches", "the", "theme", "wide"], registryDependencies: [] }, { name: "theme-toggle", type: "registry:ui", title: "Theme Toggle", description: "Light/dark icon switch with spring rotate-swap; follows the OS until first use.", exports: ["ThemeToggle"], keywords: ["button", "click", "first", "follows", "icon", "moon", "rotate", "spring", "sun", "swap", "until"], registryDependencies: ["button", "theme", "utils"] }, { name: "timeline", type: "registry:ui", title: "Timeline", description: "Server-safe ordered list of events on a rail: icon or dot markers with status colors, machine-readable <time>, title and an optional body. For changelogs, experience and mod logs.", exports: ["Timeline", "TimelineItem"], keywords: ["above", "and", "body", "children", "color", "date", "datetime", "dot", "event", "events", "first", "for", "headline", "icon", "inside", "list", "machine", "marker", "newest", "oldest", "one", "optional", "order", "ordered", "pair", "rail", "readable", "render", "shown", "small", "the", "then", "time", "title", "variant", "with", "without", "yours"], registryDependencies: ["utils"] }, { name: "toast", type: "registry:ui", title: "Toast", description: "Stacked notifications. Hover to expand, swipe to dismiss, timers pause on hover or focus, and F8 jumps to the newest toast from the keyboard.", exports: ["toast", "Toaster"], keywords: ["action", "back", "buttons", "description", "dismiss", "dismisses", "duration", "error", "esc", "expands", "focus", "goes", "hover", "jumps", "layout", "loading", "mount", "newest", "once", "pause", "promise", "root", "saved", "stacked", "success", "swipe", "tap", "the", "there", "timers", "title", "toast", "toasts", "while"], registryDependencies: ["focus", "use-dismiss", "use-hotkey", "utils"] }, { name: "toggle-group", type: "registry:ui", title: "Toggle Group", description: "Segmented control of toggle buttons. Single mode slides one pill to the pressed item (view mode, billing period); multiple mode toggles items independently (bold, italic). Arrow keys move between items.", exports: ["ToggleGroup", "ToggleGroupItem"], keywords: ["always", "and", "aria", "arrow", "behind", "between", "billing", "bold", "buttons", "called", "choice", "control", "controlled", "defaultvalue", "end", "every", "filters", "first", "height", "home", "icon", "independent", "italic", "item", "items", "keys", "label", "labels", "mode", "move", "multiple", "need", "one", "only", "onvaluechange", "pair", "period", "pill", "pressed", "render", "reported", "segment", "segmented", "set", "single", "size", "slides", "springs", "the", "time", "toggle", "toggles", "type", "uncontrolled", "value", "values", "view", "when", "with"], registryDependencies: ["list", "use-controllable-state", "use-indicator", "utils"] }, { name: "tooltip", type: "registry:ui", title: "Tooltip", description: "Short label on hover or keyboard focus, also wired as the trigger's aria-describedby. Warm-up: once one opens, neighbors open instantly. Hoverable, Esc dismisses, ignores touch.", exports: ["Tooltip"], keywords: ["accessible", "action", "align", "alignment", "also", "and", "aria", "becomes", "before", "button", "children", "content", "control", "default", "delay", "description", "dismisses", "element", "esc", "first", "flips", "focus", "focusable", "for", "hold", "hover", "how", "icon", "keyboard", "label", "one", "open", "opens", "own", "pointer", "preferred", "press", "reaches", "room", "short", "shows", "side", "stays", "still", "tap", "text", "the", "their", "there", "toggles", "tooltip", "touch", "trigger", "triggers", "wcag", "when", "while", "with"], registryDependencies: ["platform", "use-anchor", "use-dismiss", "use-hover-open", "utils"] }, { name: "typewriter", type: "registry:ui", title: "Typewriter", description: "Types out text character-by-character with optional phrase cycling and pulsing cursor. Full copy stays in the DOM for SEO.", exports: ["Typewriter"], keywords: ["and", "array", "before", "blinking", "called", "character", "classes", "cursor", "cursorchar", "cursorclassname", "custom", "cycle", "default", "delay", "deletespeed", "deleting", "dom", "effect", "for", "full", "fully", "headers", "hero", "loop", "milliseconds", "once", "oncomplete", "pause", "pauseduration", "per", "phrase", "phrases", "quotes", "seo", "show", "speed", "starting", "stays", "text", "the", "through", "true", "type", "typed", "typewriter", "typing", "wait", "when", "whether", "while"], registryDependencies: ["platform", "utils"] }, { name: "use-anchor", type: "registry:hook", description: `Fixed-position coordinates for a floating panel next to its anchor: on the preferred side,
|
|
18046
|
+
Renders a hidden span, so server components (Table) can use it as a child.`, exports: ["OverflowFocus"], keywords: ["again", "and", "arrow", "becomes", "can", "child", "components", "container", "drop", "fits", "focusable", "hidden", "inside", "keyboard", "keys", "leaves", "once", "order", "overflows", "renders", "scroll", "server", "span", "tab", "tabindex", "table", "the", "use", "while"], registryDependencies: [] }, { name: "pagination", type: "registry:ui", title: "Pagination", description: "Server-safe page links from page and total: previous/next, ellipsis gaps and a sliding active pill, with numbers that swipe the way you moved. Links by default; buttons with onPageChange. The row keeps a fixed width so it never jumps.", exports: ["Pagination", "pageRange"], keywords: ["active", "always", "and", "arrows", "bar", "become", "between", "buttons", "called", "changes", "client", "controls", "current", "default", "each", "ellipsis", "first", "for", "from", "gaps", "href", "index", "instead", "items", "its", "keeps", "kept", "key", "labels", "last", "length", "links", "mounted", "moved", "never", "next", "number", "numbers", "one", "only", "onpagechange", "page", "pagerange", "pages", "parents", "phones", "picked", "pill", "previous", "remounting", "render", "returns", "row", "same", "show", "siblings", "side", "slides", "slot", "stays", "swipe", "swipes", "text", "that", "the", "total", "unused", "url", "way", "width", "with", "without", "work", "you"], registryDependencies: ["button-variants", "pagination-motion", "utils"] }, { name: "pagination-motion", type: "registry:ui", description: "Scopes the active pill's `layoutId` so several paginations on a page don't share it.", exports: ["PaginationMotion", "PaginationActivePill", "PaginationNumber"], keywords: ["active", "behind", "changes", "children", "direction", "don", "from", "items", "its", "layoutid", "link", "moved", "next", "number", "outline", "page", "pagination", "paginations", "pill", "scopes", "several", "share", "side", "slot", "springs", "swipes", "that", "the", "toward", "travel", "value", "when"], registryDependencies: ["utils"] }, { name: "platform", type: "registry:lib", description: "False on the server and during hydration, true after: gates portals and other client-only output.", exports: ["useMounted", "useIsMac", "useMediaQuery"], keywords: ["after", "and", "assumes", "client", "corrects", "ctrl", "during", "false", "gates", "hydration", "live", "match", "media", "not", "only", "other", "output", "portals", "query", "render", "server", "shortcuts", "ssr", "the", "then", "true", "use", "where"], registryDependencies: [] }, { name: "popover", type: "registry:ui", title: "Popover", description: "Non-modal panel anchored to a Button for filters, pickers and quick edits. Focus moves in, Esc returns it, outside clicks close; Selects and menus opened inside don't count as outside.", exports: ["Popover", "PopoverTrigger", "PopoverContent"], keywords: ["above", "action", "align", "alignment", "also", "anchored", "and", "any", "aria", "armed", "before", "blur", "both", "button", "called", "can", "check", "children", "click", "clicks", "closed", "closes", "confirm", "confirmlabel", "confirmtimeout", "content", "controlled", "default", "defaultopen", "defaults", "destructive", "done", "edits", "emphasis", "end", "enter", "esc", "feedback", "field", "fill", "filters", "first", "flips", "focus", "focusable", "for", "form", "from", "give", "height", "icon", "ignore", "ignores", "irreversible", "its", "keep", "label", "left", "like", "loading", "loadinglabel", "modal", "moves", "never", "non", "not", "old", "onclick", "one", "ones", "onopenchange", "open", "out", "outside", "padding", "pair", "panel", "past", "per", "pickers", "place", "popovercontent", "popovertrigger", "popup", "preferred", "primary", "quick", "render", "reserved", "resets", "resize", "returns", "room", "runs", "second", "shift", "shown", "shows", "side", "size", "slides", "spinner", "spins", "square", "state", "status", "stays", "step", "submit", "success", "successlabel", "swap", "swaps", "tab", "text", "the", "there", "this", "trap", "trigger", "twice", "two", "uncontrolled", "unmount", "values", "variant", "view", "when", "where", "while", "width", "with"], registryDependencies: ["button", "focus", "platform", "use-anchor", "use-controllable-state", "use-dismiss", "utils"] }, { name: "pricing", type: "registry:block", title: "Pricing", description: "Plan cards with a monthly / yearly segmented switch. Prices roll on a spring in the direction they moved; the featured plan is outlined in the accent. Card, Badge, ToggleGroup, Button.", exports: ["Pricing"], keywords: ["and", "bcp", "billing", "cards", "code", "currency", "defaultperiod", "display", "down", "drop", "eur", "first", "for", "formatting", "iso", "locale", "monthly", "next", "number", "order", "period", "plan", "plans", "price", "prices", "render", "rise", "rolling", "segmented", "selected", "spring", "swap", "switch", "the", "they", "togglegroup", "when", "with", "yearly", "yearlynote"], registryDependencies: ["badge", "card", "toggle-group", "utils"] }, { name: "product-card", type: "registry:block", title: "Product Card", description: "Shop tile: photos in an AspectRatio (a swipeable Carousel with dots when there are several), sale price, Badge and add-to-cart Button.", exports: ["ProductCard"], keywords: ["add", "addlabel", "are", "aspectratio", "badge", "bcp", "become", "button", "called", "card", "carousel", "code", "compareat", "currency", "current", "dots", "for", "formatting", "heading", "height", "href", "img", "iso", "label", "link", "locale", "major", "media", "name", "new", "number", "onaddtocart", "one", "original", "over", "page", "per", "photos", "pressed", "price", "product", "ratio", "sale", "several", "shop", "shows", "slide", "small", "square", "struck", "swipeable", "text", "the", "there", "through", "tile", "units", "when", "width", "with"], registryDependencies: ["aspect-ratio", "badge", "button", "carousel", "utils"] }, { name: "progress", type: "registry:ui", title: "Progress", description: "Accent bar that springs to each new value, with an optional label and percentage. Leave out value for an indeterminate sweep (static under reduced motion).", exports: ["Progress"], keywords: ["above", "accent", "accessible", "across", "allowed", "also", "aria", "bar", "colored", "duration", "each", "for", "frame", "indeterminate", "infinite", "its", "label", "layout", "max", "motion", "name", "new", "next", "omit", "one", "per", "percentage", "reduced", "scalex", "segment", "show", "showvalue", "size", "springs", "static", "sweeps", "that", "the", "there", "thickness", "tween", "under", "unknown", "value", "visible", "when", "work"], registryDependencies: ["utils"] }, { name: "quote", type: "registry:ui", title: "Quote", description: "Semantic figure, blockquote and figcaption. Plain, card and large variants, with author, subtitle, avatar, lang, typewriter and textSwap.", exports: ["Quote"], keywords: ["accent", "alt", "and", "array", "author", "avatar", "blockquote", "card", "children", "correct", "custom", "cycles", "delay", "display", "doe", "effect", "enables", "fades", "figcaption", "figure", "for", "into", "jane", "job", "lang", "language", "large", "line", "mark", "options", "pass", "plain", "pronunciation", "quote", "quoted", "quotes", "reader", "said", "screen", "scrolls", "secondary", "semantic", "seo", "size", "slot", "speed", "spring", "string", "strings", "subtitle", "supports", "surface", "swap", "text", "textswap", "the", "through", "title", "transitions", "typewriter", "under", "variant", "view", "when", "who", "with", "work", "wrote"], registryDependencies: ["text-swap", "typewriter", "utils"] }, { name: "radio-group", type: "registry:ui", title: "Radio Group", description: "Native radios in a fieldset with a legend: browser arrow keys and form submit, a dot that springs in, optional per-option descriptions, vertical or horizontal.", exports: ["RadioGroup", "RadioGroupItem"], keywords: ["adds", "and", "application", "aria", "arrow", "browser", "but", "called", "children", "conform", "controlled", "defaultvalue", "description", "disabled", "does", "enclosing", "entered", "expected", "field", "fieldset", "first", "form", "format", "generated", "group", "indicates", "invalid", "item", "items", "its", "keys", "label", "labels", "lay", "legend", "line", "muted", "name", "names", "native", "needs", "not", "omitted", "one", "onvaluechange", "option", "orientation", "pair", "picked", "radios", "render", "rendered", "required", "row", "selectable", "shown", "stack", "submit", "submitted", "the", "them", "uncontrolled", "under", "value", "visible", "when", "with", "without", "work"], registryDependencies: ["field", "form-value", "use-controllable-state", "utils"] }, { name: "rating", type: "registry:ui", title: "Rating", description: "Star rating on native radios (arrow keys, forms) with hover preview, or readOnly display with fractional stars for averages.", exports: ["Rating"], keywords: ["and", "aria", "arrow", "called", "can", "chosen", "controlled", "defaults", "defaultvalue", "describedby", "disabled", "display", "else", "enclosing", "error", "field", "filled", "fills", "first", "for", "form", "forms", "fractions", "from", "group", "hidden", "hint", "hover", "ids", "image", "included", "inputs", "interactive", "invalid", "isn", "keys", "label", "lift", "max", "name", "native", "needs", "number", "only", "onvaluechange", "pair", "partially", "picked", "press", "preview", "radios", "rating", "readers", "readonly", "render", "renders", "required", "score", "screen", "sets", "size", "spring", "star", "stars", "styling", "submit", "submitted", "text", "the", "uncontrolled", "value", "visually", "with"], registryDependencies: ["field", "form-value", "use-controllable-state", "utils"] }, { name: "relative-time", type: "registry:ui", title: "Relative Time", description: "Self-updating “5 minutes ago” in a semantic <time> with the full date as its title. Server renders the absolute date (no hydration mismatch); one shared ticker for the whole page.", exports: ["RelativeTime"], keywords: ["absolute", "ago", "and", "both", "client", "crawlers", "current", "date", "default", "differ", "full", "get", "hydration", "its", "itself", "keeps", "locale", "minutes", "mismatch", "not", "now", "one", "page", "pass", "real", "relative", "render", "renders", "semantic", "server", "shared", "stored", "text", "that", "the", "there", "ticker", "time", "timestamp", "title", "with", "would"], registryDependencies: ["utils"] }, { name: "resizable", type: "registry:ui", title: "Resizable", description: "Split panes for admin layouts, docs and editors. Hairline dividers with a wide grab area follow the pointer; collapsible panels snap shut past half their minimum. Dividers are APG window splitters: arrows step, Home/End jump to the limits, Enter or double-click collapses with a spring. Groups nest.", exports: ["ResizablePanelGroup", "ResizablePanel", "ResizableHandle"], keywords: ["adds", "admin", "after", "already", "and", "apg", "are", "arrow", "arrows", "basis", "between", "click", "collapsed", "collapsedsize", "collapses", "collapsible", "component", "cookie", "css", "default", "defaultsize", "direction", "divider", "docs", "dom", "double", "drag", "dragging", "each", "editors", "end", "enter", "evenly", "final", "find", "flex", "follows", "for", "grip", "group", "half", "handle", "handles", "home", "horizontal", "hydration", "icon", "jump", "key", "keyboardstep", "keys", "largest", "layout", "layouts", "left", "limits", "maxsize", "middle", "minimum", "minsize", "neighbor", "one", "onlayout", "order", "others", "panels", "panes", "parents", "past", "per", "plain", "pointer", "press", "rail", "rest", "save", "server", "share", "shows", "shut", "side", "size", "sizes", "smallest", "snaps", "split", "splitters", "spring", "ssr", "stacked", "starting", "step", "the", "their", "them", "toggle", "toggles", "two", "until", "vertical", "visible", "what", "when", "window", "withhandle", "without", "work", "wrappers"], registryDependencies: ["utils"] }, { name: "reveal", type: "registry:ui", title: "Reveal", description: "Scroll reveal: content fades and rises on a spring the first time it enters the viewport.", exports: ["Reveal"], keywords: ["after", "and", "content", "delay", "entering", "fades", "first", "into", "offset", "place", "rises", "scrolls", "seconds", "spring", "start", "stays", "the", "time", "view", "visible", "wait"], registryDependencies: ["utils"] }, { name: "scroll-area", type: "registry:ui", title: "Scroll Area", description: "Overlay scrollbars: thin thumbs that fade in while scrolling or on hover, widen under the pointer and drag. Scrolling stays native; the labeled region is keyboard-focusable.", exports: ["ScrollArea"], keywords: ["also", "and", "aria", "axes", "can", "classes", "drag", "fade", "focus", "for", "get", "give", "goes", "height", "here", "horizontal", "hover", "itself", "keyboard", "label", "mouse", "names", "native", "one", "orientation", "overlay", "padding", "region", "scroll", "scrollable", "scrollbars", "scrolling", "stays", "takes", "that", "the", "thin", "thumb", "thumbs", "touch", "users", "viewport", "viewportclassname", "wheel", "while", "widen"], registryDependencies: ["utils"] }, { name: "search-input", type: "registry:ui", title: "Search Input", description: 'Search field built on Input: leading icon, spring-in clear button, Esc to clear, and an optional focus shortcut ("/" or mod+k) with a Kbd hint.', exports: ["SearchInput"], keywords: ["accessible", "already", "and", "anywhere", "aside", "border", "browser", "button", "called", "change", "clear", "cleared", "clears", "command", "controlled", "ctrl", "default", "defaultvalue", "elsewhere", "embedded", "esc", "every", "field", "find", "first", "focus", "focused", "focuses", "for", "from", "ghost", "hidden", "hint", "htmlfor", "icon", "inputs", "kbd", "key", "label", "mac", "mod", "name", "onvaluechange", "opens", "optional", "own", "pair", "palette", "pass", "query", "render", "ring", "search", "second", "shadow", "shortcut", "single", "skipped", "spring", "steps", "still", "surface", "the", "uncontrolled", "value", "variant", "when", "while", "with", "you"], registryDependencies: ["input", "kbd", "platform", "use-controllable-state", "use-hotkey", "utils"] }, { name: "segmented-tabs", type: "registry:ui", title: "AppWindow", description: "Window container with close / minimize / zoom controls, title, custom header slot and actions. SegmentedTabs is the compact view switcher for its title bar.", exports: ["SegmentedTabs"], keywords: ["accessible", "active", "also", "always", "aria", "behind", "called", "current", "for", "indicator", "item", "items", "label", "labels", "list", "marked", "name", "onchange", "one", "pressed", "segmented", "slides", "spring", "tab", "tabs", "the", "under", "used", "value", "values", "with"], registryDependencies: ["overflow-focus", "use-indicator", "utils"] }, { name: "select", type: "registry:ui", title: "Select", description: "Single-choice listbox styled like Input, with groups, icons and disabled options. Native-select keyboard (arrows, typeahead even while closed), flips above the trigger near the viewport edge, submits and validates with the form.", exports: ["Select"], keywords: ["accessible", "also", "aria", "blocks", "called", "cancel", "choice", "closed", "controlled", "defaultvalue", "describedby", "disabled", "down", "end", "enter", "error", "esc", "field", "fill", "fills", "first", "flat", "for", "form", "from", "groups", "hint", "home", "htmlfor", "ids", "input", "invalid", "isn", "keyboard", "label", "labeled", "let", "like", "listbox", "matches", "name", "native", "needs", "nothing", "onvaluechange", "opening", "options", "pair", "pick", "picked", "placeholder", "render", "required", "select", "sets", "single", "space", "styled", "styling", "submit", "submitted", "text", "the", "there", "trigger", "typeahead", "uncontrolled", "value", "visible", "when", "while", "with"], registryDependencies: ["field", "form-value", "input", "list", "platform", "use-anchor", "use-controllable-state", "use-dismiss", "utils"] }, { name: "separator", type: "registry:ui", title: "Separator", description: 'Server-safe hairline, horizontal or vertical, decorative by default, with an optional centered label like "or".', exports: ["Separator"], keywords: ["assistive", "centered", "content", "decorative", "default", "direction", "false", "for", "horizontal", "label", "line", "only", "orientation", "purely", "separates", "set", "tech", "text", "the", "visual", "when"], registryDependencies: ["utils"] }, { name: "sidebar", type: "registry:ui", title: "Sidebar", description: "App navigation from data: header, groups (optionally foldable), badges and pinned footer entries. Collapses to an icon rail on a spring (⌘B / Ctrl+B) with labels kept for assistive tech; below md it is a left drawer.", exports: ["SidebarLayout", "Sidebar", "SidebarTrigger"], keywords: ["a11y", "accessible", "and", "aria", "below", "bottom", "but", "called", "client", "clip", "collapsed", "collapses", "controlled", "ctrl", "current", "data", "default", "defaultcollapsed", "drawer", "entries", "field", "footer", "for", "from", "groups", "header", "highlighted", "holds", "icon", "ignored", "item", "label", "labels", "landmark", "lays", "left", "links", "name", "navigation", "oncollapsedchange", "onselect", "page", "pair", "picked", "pinned", "rail", "routing", "settings", "shortcut", "side", "sidebar", "slot", "spring", "start", "state", "stay", "the", "toggle", "too", "top", "tree", "trigger", "true", "typing", "uncontrolled", "when", "while", "with", "workspace"], registryDependencies: ["button", "collapsible", "drawer", "kbd", "platform", "tooltip", "use-controllable-state", "use-hotkey", "utils"] }, { name: "site-header", type: "registry:block", title: "Site Header", description: "Sticky, translucent site header from one nav array: brand, NavigationMenu panels from md up, a right Drawer with the same links below it, and your actions on the right. The hairline underneath appears only once content scrolls beneath it.", exports: ["SiteHeader"], keywords: ["accessible", "acme", "actions", "alike", "and", "appears", "aria", "below", "brand", "client", "close", "controls", "current", "demo", "desktop", "drawer", "every", "for", "from", "hairline", "hand", "header", "href", "label", "left", "level", "link", "links", "logo", "marks", "matching", "name", "nav", "navigation", "navigationmenu", "once", "onclick", "only", "onnavigate", "over", "page", "panels", "right", "routing", "same", "scrolls", "search", "shown", "sign", "site", "sticky", "stub", "the", "theme", "top", "under", "width", "with"], registryDependencies: ["button", "drawer", "navigation-menu", "use-disclosure", "utils"] }, { name: "skeleton", type: "registry:ui", title: "Skeleton", description: 'Server-safe loading placeholders that pulse (static under reduced motion). SkeletonGroup announces one "Loading" to screen readers.', exports: ["Skeleton", "SkeletonGroup"], keywords: ["announced", "block", "classes", "classname", "hear", "instead", "label", "loading", "nothing", "one", "placeholder", "readers", "screen", "shown", "size", "skeleton", "skeletons", "this", "while", "with", "wrap"], registryDependencies: ["utils"] }, { name: "slider", type: "registry:ui", title: "Slider", description: "Slider over native range inputs: thin accent track, white knob that lifts while held. One value or two for a range; keyboard, forms, touch and Field wiring come from the platform.", exports: ["Slider"], keywords: ["accent", "accessible", "and", "aren", "aria", "arrow", "blocks", "browser", "called", "defaults", "defaultvalue", "describedby", "disabled", "does", "drag", "dragging", "error", "every", "field", "fills", "filter", "first", "for", "forms", "from", "held", "highest", "hint", "ids", "increment", "inputs", "invalid", "keyboard", "keys", "knob", "labels", "lifts", "lowest", "max", "maximum", "min", "minimum", "name", "names", "native", "one", "onvaluechange", "over", "per", "price", "range", "readers", "render", "screen", "sets", "slider", "step", "styling", "submitted", "text", "that", "the", "thin", "thumb", "touch", "track", "twice", "two", "uncontrolled", "value", "values", "while", "white", "with", "work"], registryDependencies: ["field", "form-value", "use-controllable-state", "utils"] }, { name: "spinner", type: "registry:ui", title: "Spinner", description: "Server-safe activity indicator: eight spokes in currentColor, three sizes, its own status label (or silent inside a parent that announces). Holds still under reduced motion.", exports: ["Spinner"], keywords: ["accessible", "activity", "already", "announces", "busy", "currentcolor", "diameter", "eight", "holds", "indicator", "label", "motion", "name", "parent", "pass", "reduced", "size", "spokes", "state", "still", "the", "under", "when"], registryDependencies: ["utils"] }, { name: "stat-tile", type: "registry:block", title: "Stat Tile", description: "Admin dashboard metric: value, a delta Badge that is green when the change is good news (invert for errors and latency), and an area Chart of the trend. Server-safe.", exports: ["StatTile"], keywords: ["admin", "and", "area", "axis", "badge", "below", "change", "chart", "dashboard", "data", "datakey", "delta", "down", "each", "errors", "for", "format", "formatted", "good", "green", "headline", "index", "intl", "invert", "key", "label", "last", "latency", "metric", "metrics", "month", "name", "news", "number", "period", "positive", "revenue", "row", "rows", "the", "tile", "tooltip", "trend", "unless", "value", "when", "where"], registryDependencies: ["badge", "card", "chart", "utils"] }, { name: "stats-strip", type: "registry:block", title: "Stats Strip", description: "Headline numbers, each card a <dl>: cards rise in turn with Reveal, then each number springs up with CountTo. Final values are in the HTML.", exports: ["StatsStrip"], keywords: ["card", "display", "each", "headline", "its", "number", "numbers", "order", "rises", "row", "springs", "stats", "then", "turn"], registryDependencies: ["card", "count-to", "reveal", "utils"] }, { name: "stepper", type: "registry:ui", title: "Stepper", description: "Multi-step form (questionnaire, checkout, setup wizard). Steps stay mounted so values survive Back and submit together; Continue checks only the current step's native constraints, plus an optional async onNext guard with a spinner. Progress header with springing connectors; works with onSubmit or a Server Action.", exports: ["Stepper", "StepperList", "StepperStep", "StepperFooter"], keywords: ["action", "after", "and", "are", "back", "backlabel", "button", "called", "check", "checks", "children", "connector", "constraints", "continue", "controlled", "current", "defaultvalue", "description", "every", "false", "fields", "fills", "finished", "first", "for", "form", "header", "hidden", "inactive", "its", "last", "links", "matches", "mounted", "multi", "native", "new", "nextlabel", "not", "one", "only", "onnext", "onsubmit", "onvaluechange", "optional", "order", "pass", "progress", "render", "resolve", "return", "runs", "server", "shown", "shows", "spinner", "spring", "start", "stay", "stays", "step", "steps", "submit", "submitlabel", "submits", "survive", "text", "the", "through", "title", "together", "uncontrolled", "unmounted", "value", "values", "while", "with"], registryDependencies: ["button", "focus", "use-controllable-state", "utils"] }, { name: "styles", type: "registry:file", exports: [], keywords: [], registryDependencies: [] }, { name: "switch", type: "registry:ui", title: "Forms", description: "Input, Textarea, Label and Switch: smooth focus rings, native :user-invalid validation (red only after interaction), aria-invalid, auto-growing textarea with an optional character count, and a spring switch.", exports: ["Switch"], keywords: ["called", "checkbox", "checked", "controlled", "defaultchecked", "first", "knob", "native", "new", "oncheckedchange", "pair", "render", "role", "spring", "state", "switch", "the", "uncontrolled", "with"], registryDependencies: ["field", "form-value", "use-controllable-state", "utils"] }, { name: "table", type: "registry:ui", title: "Table", description: "Server-safe semantic <table> in a scrolling card: header, body, footer and caption, striped rows, tinted selected rows, and sortable headers that set aria-sort and flip their arrow.", exports: ["Table", "TableHeader", "TableBody", "TableFooter", "TableRow", "TableHead", "TableCell", "TableCaption"], keywords: ["alternating", "and", "aria", "around", "arrow", "button", "called", "card", "classes", "column", "containerclassname", "data", "direction", "for", "header", "keyboard", "makes", "narrow", "onsort", "pass", "pressed", "real", "requires", "row", "screens", "scrollable", "scrolling", "scrolls", "selected", "sets", "shading", "shows", "sideways", "sort", "sortable", "state", "striped", "table", "that", "the", "then", "tint", "when"], registryDependencies: ["overflow-focus", "utils"] }, { name: "tabs", type: "registry:ui", title: "Tabs", description: "WAI-ARIA tabs with arrow, Home and End keys. One indicator springs between tabs, segmented or underline. Inactive panels stay in the DOM for SEO; the active one fades in.", exports: ["Tabs", "TabsList", "TabsTrigger", "TabsContent"], keywords: ["activated", "active", "always", "and", "aria", "arrows", "bar", "behind", "called", "children", "content", "controlled", "crawlable", "defaultvalue", "dom", "end", "first", "focus", "hidden", "home", "html", "inactive", "indicator", "labels", "matches", "move", "muted", "one", "onvaluechange", "pair", "panel", "panels", "pill", "remains", "render", "segmented", "springs", "stay", "stays", "tab", "tabs", "tabscontent", "tabstrigger", "the", "their", "track", "uncontrolled", "under", "underline", "value", "variant", "wai", "while", "with"], registryDependencies: ["list", "use-controllable-state", "use-indicator", "utils"] }, { name: "tag-input", type: "registry:ui", title: "Tag Input", description: "Free-form tags: Enter or a separator adds, Backspace removes the last, ← walks onto the tags and Delete removes one, pasting a list adds them all. Duplicates, a max and a validate function keep bad tags in the input. Submits one entry per tag; required needs one.", exports: ["tagFieldClass", "Tag", "useTagKeys", "TagInput"], keywords: ["accessible", "add", "after", "allowed", "and", "animatepresence", "backspace", "besides", "button", "call", "called", "children", "chip", "commit", "controlled", "defaultvalue", "delete", "disabled", "each", "empty", "enter", "esc", "every", "false", "field", "first", "focused", "for", "form", "free", "from", "frominput", "hides", "input", "inside", "its", "keyboard", "keydown", "keys", "last", "least", "limit", "list", "many", "max", "mode", "most", "move", "name", "needs", "one", "onkeydown", "onremove", "onto", "onvaluechange", "order", "out", "pair", "paste", "pasted", "per", "poplayout", "press", "put", "reach", "reject", "removable", "remove", "removeref", "removes", "render", "rendered", "required", "return", "returns", "runs", "separator", "separators", "shows", "space", "split", "springs", "stay", "stays", "stops", "submits", "tab", "tag", "tagref", "tags", "text", "that", "the", "them", "these", "too", "trimming", "type", "typed", "typing", "uncontrolled", "usetagkeys", "validate", "value", "walks", "when", "which", "with"], registryDependencies: ["field", "form-value", "use-controllable-state", "utils"] }, { name: "terminal", type: "registry:ui", title: "Terminal", description: "Terminal window. Snippet mode types install commands once scrolled into view, with the full transcript kept in the DOM for SEO and no-JS; log mode streams lines into a live region that follows the bottom unless you scroll up, then offers a jump back. Status lines carry glyphs, copy grabs the commands.", exports: ["Terminal"], keywords: ["and", "append", "arrives", "back", "before", "body", "bodyclassname", "bottom", "character", "clipboard", "commands", "copy", "copytext", "defaults", "dom", "false", "follows", "for", "full", "hides", "install", "jump", "lines", "live", "log", "mode", "new", "offering", "once", "order", "output", "per", "prompt", "puts", "read", "reveals", "role", "scroll", "scrolling", "seo", "size", "snippet", "snippets", "speed", "stays", "sticks", "stream", "streams", "symbol", "terminal", "text", "the", "title", "typed", "types", "typing", "unless", "view", "what", "when", "window", "you"], registryDependencies: ["app-window", "code", "platform", "utils"] }, { name: "testimonial", type: "registry:block", title: "Testimonial", description: "A large Quote with its author, and everyone else who agrees as an overlapping AvatarGroup. Server-safe.", exports: ["Testimonial"], keywords: ["agrees", "and", "author", "avatar", "avatargroup", "behind", "beside", "carried", "children", "company", "crowd", "else", "everyone", "few", "first", "fold", "initials", "into", "its", "lang", "language", "large", "line", "one", "others", "otherslabel", "photo", "quote", "rest", "role", "said", "show", "stack", "streamers", "subtitle", "the", "trusted", "under", "url", "who", "with", "without"], registryDependencies: ["avatar", "avatar-group", "quote", "utils"] }, { name: "text-swap", type: "registry:ui", title: "TextSwap", description: "Spring-powered text morphing and ticker. Auto-cycles phrases or animates whenever children change with blur and slide transitions.", exports: ["TextSwap"], keywords: ["accessible", "active", "after", "and", "animation", "array", "automatically", "between", "button", "can", "changing", "child", "children", "content", "controlling", "controls", "cycle", "cycles", "cycling", "default", "direction", "doesn", "externally", "focus", "hidden", "hover", "index", "interval", "items", "keyboard", "milliseconds", "motion", "mouse", "name", "new", "next", "other", "pass", "pause", "pauselabel", "pauseonhover", "reduced", "show", "shows", "single", "stop", "string", "swap", "swaps", "text", "the", "through", "too", "touch", "true", "use", "users", "wcag", "when", "with"], registryDependencies: ["button-variants", "platform", "utils"] }, { name: "textarea", type: "registry:ui", title: "Forms", description: "Input, Textarea, Label and Switch: smooth focus rings, native :user-invalid validation (red only after interaction), aria-invalid, auto-growing textarea with an optional character count, and a spring switch.", exports: ["Textarea"], keywords: ["auto", "cap", "content", "corner", "end", "field", "firefox", "grows", "max", "maxlength", "near", "needs", "red", "resizable", "show", "showcount", "sizing", "the", "then", "turns", "unsupported", "used", "warning", "where", "with"], registryDependencies: ["field", "form-value", "input", "utils"] }, { name: "theme", type: "registry:lib", description: "Retints primary, focus rings, selection and switches app-wide. Persists like the theme.", exports: ["isDark", "setTheme", "subscribeTheme", "accents", "getAccent", "setAccent", "themeScript"], keywords: ["and", "app", "focus", "like", "persists", "primary", "retints", "rings", "selection", "switches", "the", "theme", "wide"], registryDependencies: [] }, { name: "theme-toggle", type: "registry:ui", title: "Theme Toggle", description: "Light/dark icon switch with spring rotate-swap; follows the OS until first use.", exports: ["ThemeToggle"], keywords: ["button", "click", "first", "follows", "icon", "moon", "rotate", "spring", "sun", "swap", "until"], registryDependencies: ["button", "theme", "utils"] }, { name: "timeline", type: "registry:ui", title: "Timeline", description: "Server-safe ordered list of events on a rail: icon or dot markers with status colors, machine-readable <time>, title and an optional body. For changelogs, experience and mod logs.", exports: ["Timeline", "TimelineItem"], keywords: ["above", "and", "body", "children", "color", "date", "datetime", "dot", "event", "events", "first", "for", "headline", "icon", "inside", "list", "machine", "marker", "newest", "oldest", "one", "optional", "order", "ordered", "pair", "rail", "readable", "render", "shown", "small", "the", "then", "time", "title", "variant", "with", "without", "yours"], registryDependencies: ["utils"] }, { name: "toast", type: "registry:ui", title: "Toast", description: "Stacked notifications. Hover to expand, swipe to dismiss, timers pause on hover or focus, a toast with an action stays until it's used or dismissed, and F8 jumps to the newest toast with an action (else the newest) from the keyboard.", exports: ["toast", "Toaster"], keywords: ["action", "actionduration", "after", "back", "before", "closes", "default", "description", "dismiss", "dismissed", "dismisses", "duration", "else", "error", "esc", "expands", "focus", "hover", "infinity", "jumps", "key", "layout", "loading", "mount", "newest", "once", "own", "pause", "press", "promise", "root", "saved", "sends", "shortcut", "shows", "stacked", "stays", "still", "success", "swipe", "tap", "that", "the", "there", "timers", "title", "toast", "toasts", "until", "used", "while", "wins", "with", "without"], registryDependencies: ["focus", "kbd", "platform", "use-dismiss", "use-hotkey", "utils"] }, { name: "toggle-group", type: "registry:ui", title: "Toggle Group", description: "Segmented control of toggle buttons. Single mode slides one pill to the pressed item (view mode, billing period); multiple mode toggles items independently (bold, italic). Arrow keys move between items.", exports: ["ToggleGroup", "ToggleGroupItem"], keywords: ["always", "and", "aria", "arrow", "behind", "between", "billing", "bold", "buttons", "called", "choice", "control", "controlled", "defaultvalue", "end", "every", "filters", "first", "height", "home", "icon", "independent", "italic", "item", "items", "keys", "label", "labels", "mode", "move", "multiple", "need", "one", "only", "onvaluechange", "pair", "period", "pill", "pressed", "render", "reported", "segment", "segmented", "set", "single", "size", "slides", "springs", "the", "time", "toggle", "toggles", "type", "uncontrolled", "value", "values", "view", "when", "with"], registryDependencies: ["list", "use-controllable-state", "use-indicator", "utils"] }, { name: "tooltip", type: "registry:ui", title: "Tooltip", description: "Short label on hover or keyboard focus, also wired as the trigger's aria-describedby. Warm-up: once one opens, neighbors open instantly. Hoverable, Esc dismisses, ignores touch.", exports: ["Tooltip"], keywords: ["accessible", "action", "align", "alignment", "also", "and", "aria", "becomes", "before", "button", "children", "content", "control", "default", "delay", "description", "dismisses", "element", "esc", "first", "flips", "focus", "focusable", "for", "hold", "hover", "how", "icon", "keyboard", "label", "one", "open", "opens", "own", "pointer", "preferred", "press", "reaches", "room", "short", "shows", "side", "stays", "still", "tap", "text", "the", "their", "there", "toggles", "tooltip", "touch", "trigger", "triggers", "wcag", "when", "while", "with"], registryDependencies: ["platform", "use-anchor", "use-dismiss", "use-hover-open", "utils"] }, { name: "typewriter", type: "registry:ui", title: "Typewriter", description: "Types out text character-by-character with optional phrase cycling and pulsing cursor. Full copy stays in the DOM for SEO.", exports: ["Typewriter"], keywords: ["accessible", "and", "animation", "array", "before", "blinking", "button", "called", "can", "character", "classes", "content", "controls", "cursor", "cursorchar", "cursorclassname", "custom", "cycle", "default", "delay", "deletespeed", "deleting", "dom", "effect", "focus", "for", "full", "fully", "headers", "hero", "hidden", "hover", "keeps", "keyboard", "loop", "milliseconds", "motion", "mouse", "moving", "name", "next", "once", "oncomplete", "other", "pause", "pauseduration", "pauselabel", "pauseonhover", "per", "phrase", "phrases", "quotes", "reduced", "seo", "show", "shows", "speed", "starting", "stays", "stop", "text", "the", "through", "too", "touch", "true", "type", "typed", "typewriter", "typing", "use", "users", "wait", "wcag", "when", "whether", "while", "with"], registryDependencies: ["button-variants", "platform", "utils"] }, { name: "use-anchor", type: "registry:hook", description: `Fixed-position coordinates for a floating panel next to its anchor: on the preferred side,
|
|
17858
18047
|
flipped when it doesn't fit, clamped to the viewport, kept in place on scroll and resize.
|
|
17859
18048
|
Measures before paint, so nothing jumps.`, exports: ["useAnchor"], keywords: ["anchor", "and", "before", "clamped", "coordinates", "doesn", "fit", "fixed", "flipped", "floating", "for", "its", "jumps", "kept", "measures", "next", "nothing", "paint", "panel", "place", "position", "preferred", "resize", "scroll", "side", "the", "viewport", "when"], registryDependencies: ["utils"] }, { name: "use-controllable-state", type: "registry:hook", description: "State a parent may own. Controlled while `value` is defined; otherwise kept here, seeded from\n`defaultValue`. The setter always reports through `onChange`, so both modes stay observable.", exports: ["useControllableState"], keywords: ["always", "both", "controlled", "defaultvalue", "defined", "from", "here", "kept", "may", "modes", "observable", "onchange", "otherwise", "own", "parent", "reports", "seeded", "setter", "state", "stay", "the", "through", "value", "while"], registryDependencies: [] }, { name: "use-disclosure", type: "registry:hook", description: "Open state for any overlay. Spread `props` onto Dialog, Drawer, Popover or DropdownMenu and\nopen it from anywhere: `const drawer = useDisclosure(); <Drawer {...drawer.props}>`, `drawer.show()`.", exports: ["useDisclosure"], keywords: ["and", "any", "anywhere", "const", "dialog", "drawer", "dropdownmenu", "for", "from", "onto", "open", "overlay", "popover", "props", "show", "spread", "state", "usedisclosure"], registryDependencies: [] }, { name: "use-dismiss", type: "registry:hook", description: "Inside one of `refs` (panel first, then its trigger), or inside a floating child opened from\nthe panel: a Select list or menu portals out, but it is marked `data-floating`.", exports: ["isWithin", "useDismiss"], keywords: ["and", "but", "child", "children", "closes", "data", "first", "floating", "from", "inside", "its", "list", "marked", "menu", "one", "opened", "out", "outside", "panel", "pointerdown", "portals", "refs", "select", "the", "then", "trigger"], registryDependencies: [] }, { name: "use-hotkey", type: "registry:hook", description: '`"mod+k"`: ⌘K on Mac, Ctrl+K elsewhere, without Alt. `"/"`: the key alone, no modifiers.', exports: ["matchesHotkey", "useHotkey"], keywords: ["alone", "alt", "browser", "ctrl", "disable", "elsewhere", "false", "handler", "key", "keyboard", "leave", "mac", "mod", "modifiers", "pass", "preventdefault", "returns", "shortcut", "the", "undefined", "wide", "window", "without"], registryDependencies: [] }, { name: "use-hover-open", type: "registry:hook", description: "Open-on-hover state with intent delays, shared by Tooltip and HoverCard. Mouse only (touch\ntaps go to the trigger's own action); keyboard focus opens it too. Spread `trigger` on the\nanchor and `floating` on the panel so moving between them keeps it open.", exports: ["useHoverOpen"], keywords: ["action", "anchor", "and", "between", "delays", "floating", "focus", "hover", "hovercard", "intent", "keeps", "keyboard", "mouse", "moving", "only", "open", "opens", "own", "panel", "shared", "spread", "state", "taps", "the", "them", "too", "tooltip", "touch", "trigger", "with"], registryDependencies: [] }, { name: "use-indicator", type: "registry:hook", description: `Offset and width of the active item, for one indicator that springs between items (Tabs,
|
|
17860
18049
|
ToggleGroup). Re-measures when the item or its row resizes (font swap, wrap), or the pill
|
|
@@ -17951,10 +18140,18 @@ async function fetchAllItems(source) {
|
|
|
17951
18140
|
const index = await fetchIndex(source);
|
|
17952
18141
|
return Promise.all(index.map((i) => fetchItem(source, i.name)));
|
|
17953
18142
|
}
|
|
18143
|
+
var jsrBase = async (version) => {
|
|
18144
|
+
try {
|
|
18145
|
+
const res = await fetch(`https://jsr.io/@kznlabs/labsui/${version}/src/registry.json`);
|
|
18146
|
+
return res.ok ? (await res.json()).items : undefined;
|
|
18147
|
+
} catch {
|
|
18148
|
+
return;
|
|
18149
|
+
}
|
|
18150
|
+
};
|
|
17954
18151
|
// jsr.json
|
|
17955
18152
|
var jsr_default = {
|
|
17956
18153
|
name: "@kznlabs/labsui",
|
|
17957
|
-
version: "0.1.
|
|
18154
|
+
version: "0.1.14",
|
|
17958
18155
|
license: "MIT",
|
|
17959
18156
|
exports: "./src/index.ts",
|
|
17960
18157
|
publish: {
|
|
@@ -18082,6 +18279,9 @@ ${bold("Mount once")} in the root layout, inside <MotionProvider>:`);
|
|
|
18082
18279
|
// src/commands/diff.ts
|
|
18083
18280
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
18084
18281
|
import { relative as relative3 } from "node:path";
|
|
18282
|
+
var same = (a, b) => lines(a).join(`
|
|
18283
|
+
`) === lines(b).join(`
|
|
18284
|
+
`);
|
|
18085
18285
|
function matches(a, b) {
|
|
18086
18286
|
const out = new Int32Array(a.length).fill(-1);
|
|
18087
18287
|
let i = 0;
|
|
@@ -18162,7 +18362,7 @@ async function installedItems(config, base, all) {
|
|
|
18162
18362
|
return files.some((file) => existsSync4(file)) ? [{ item, files }] : [];
|
|
18163
18363
|
});
|
|
18164
18364
|
}
|
|
18165
|
-
async function diff(cwd, config, names, version) {
|
|
18365
|
+
async function diff(cwd, config, names, version, loadBase = jsrBase) {
|
|
18166
18366
|
const base = projectBase(cwd, config);
|
|
18167
18367
|
const wanted = new Set(names.map(itemName));
|
|
18168
18368
|
const all = await fetchAllItems(config.registry);
|
|
@@ -18171,10 +18371,22 @@ async function diff(cwd, config, names, version) {
|
|
|
18171
18371
|
throw new CliError(`Unknown item(s): ${unknown.join(", ")}. Search finds the names.`);
|
|
18172
18372
|
const installed = await installedItems(config, base, all);
|
|
18173
18373
|
const out = [...wanted].filter((name) => !installed.some(({ item }) => item.name === name)).map((name) => ({ name, installed: false, files: [] }));
|
|
18374
|
+
const bases = new Map;
|
|
18375
|
+
const baseOf = async (name, since) => {
|
|
18376
|
+
if (!since || config.registry)
|
|
18377
|
+
return;
|
|
18378
|
+
if (since === version)
|
|
18379
|
+
return all.find((item) => item.name === name);
|
|
18380
|
+
if (!bases.has(since))
|
|
18381
|
+
bases.set(since, loadBase(since));
|
|
18382
|
+
return (await bases.get(since))?.[name];
|
|
18383
|
+
};
|
|
18174
18384
|
for (const { item, files } of installed) {
|
|
18175
18385
|
if (wanted.size && !wanted.has(item.name))
|
|
18176
18386
|
continue;
|
|
18177
18387
|
const changed = [];
|
|
18388
|
+
const since = config.installed?.[item.name];
|
|
18389
|
+
const old = await baseOf(item.name, since);
|
|
18178
18390
|
item.files.forEach((file, i) => {
|
|
18179
18391
|
const local = files[i];
|
|
18180
18392
|
if (!local || !existsSync4(local))
|
|
@@ -18187,19 +18399,31 @@ async function diff(cwd, config, names, version) {
|
|
|
18187
18399
|
return;
|
|
18188
18400
|
const body = patch.split(`
|
|
18189
18401
|
`).slice(2);
|
|
18402
|
+
const before = old?.files.find((entry) => entry.path === file.path)?.content;
|
|
18403
|
+
const was = before === undefined ? undefined : rewriteAlias(before, config.alias);
|
|
18190
18404
|
changed.push({
|
|
18191
18405
|
path: shown,
|
|
18192
18406
|
added: body.filter((line) => line.startsWith("+")).length,
|
|
18193
18407
|
removed: body.filter((line) => line.startsWith("-")).length,
|
|
18194
18408
|
formatting: sameCode(theirs, yours),
|
|
18409
|
+
changedBy: was === undefined ? undefined : same(was, yours) ? "upstream" : same(was, theirs) ? "local" : "both",
|
|
18195
18410
|
patch
|
|
18196
18411
|
});
|
|
18197
18412
|
});
|
|
18198
18413
|
if (changed.length)
|
|
18199
|
-
out.push({ name: item.name, installed: true, files: changed });
|
|
18414
|
+
out.push({ name: item.name, installed: true, since, files: changed });
|
|
18200
18415
|
}
|
|
18201
18416
|
return out;
|
|
18202
18417
|
}
|
|
18418
|
+
function changeNote(file, since) {
|
|
18419
|
+
if (file.changedBy === "upstream")
|
|
18420
|
+
return `only labsUI changed it since ${since}; update replaces it`;
|
|
18421
|
+
if (file.changedBy === "local")
|
|
18422
|
+
return "your edits; update keeps them";
|
|
18423
|
+
if (file.changedBy === "both")
|
|
18424
|
+
return `your edits and labsUI's since ${since}; update merges them`;
|
|
18425
|
+
return "";
|
|
18426
|
+
}
|
|
18203
18427
|
function printDiff(items, full) {
|
|
18204
18428
|
if (!items.length)
|
|
18205
18429
|
return console.log(`${green("✓")} Installed items match this labsui version.`);
|
|
@@ -18207,10 +18431,13 @@ function printDiff(items, full) {
|
|
|
18207
18431
|
if (!item.installed)
|
|
18208
18432
|
console.log(` ${item.name.padEnd(24)} ${dim(`not installed (labsui add ${item.name})`)}`);
|
|
18209
18433
|
for (const file of item.files) {
|
|
18434
|
+
const note = file.formatting ? "formatting only" : changeNote(file, item.since);
|
|
18210
18435
|
if (!full) {
|
|
18211
|
-
console.log(` ${item.name.padEnd(24)} ${green(`+${file.added}`)} ${red(`-${file.removed}`)} ${dim(file.path)}${
|
|
18436
|
+
console.log(` ${item.name.padEnd(24)} ${green(`+${file.added}`)} ${red(`-${file.removed}`)} ${dim(file.path)}${note ? dim(` (${note})`) : ""}`);
|
|
18212
18437
|
continue;
|
|
18213
18438
|
}
|
|
18439
|
+
if (note)
|
|
18440
|
+
console.log(dim(`# ${item.name}: ${note}`));
|
|
18214
18441
|
for (const line of file.patch.split(`
|
|
18215
18442
|
`))
|
|
18216
18443
|
console.log(line.startsWith("+") ? green(line) : line.startsWith("-") ? red(line) : line.startsWith("@@") ? dim(line) : line);
|
|
@@ -18218,7 +18445,7 @@ function printDiff(items, full) {
|
|
|
18218
18445
|
}
|
|
18219
18446
|
if (!full)
|
|
18220
18447
|
console.log(`
|
|
18221
|
-
${dim("labsui diff <name> shows the changes;
|
|
18448
|
+
${dim("labsui diff <name> shows the changes; labsui update <name> takes this version and keeps your edits.")}`);
|
|
18222
18449
|
}
|
|
18223
18450
|
|
|
18224
18451
|
// src/commands/doctor.ts
|
|
@@ -18226,7 +18453,7 @@ import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as
|
|
|
18226
18453
|
import { join as join4, relative as relative4 } from "node:path";
|
|
18227
18454
|
|
|
18228
18455
|
// src/merge.ts
|
|
18229
|
-
var
|
|
18456
|
+
var same2 = (a, b) => a.length === b.length && a.every((line, i) => line === b[i]);
|
|
18230
18457
|
function merge3(base, ours, theirs, labels = { ours: "yours", theirs: "labsui" }) {
|
|
18231
18458
|
const baseLines = lines(base);
|
|
18232
18459
|
const ourLines = lines(ours);
|
|
@@ -18247,9 +18474,9 @@ function merge3(base, ours, theirs, labels = { ours: "yours", theirs: "labsui" }
|
|
|
18247
18474
|
const chunkO = baseLines.slice(i, key);
|
|
18248
18475
|
const chunkA = ourLines.slice(ourIndex, ourStop);
|
|
18249
18476
|
const chunkB = theirLines.slice(theirIndex, theirStop);
|
|
18250
|
-
if (
|
|
18477
|
+
if (same2(chunkA, chunkO))
|
|
18251
18478
|
out.push(...chunkB);
|
|
18252
|
-
else if (
|
|
18479
|
+
else if (same2(chunkB, chunkO) || same2(chunkA, chunkB))
|
|
18253
18480
|
out.push(...chunkA);
|
|
18254
18481
|
else {
|
|
18255
18482
|
conflicts++;
|
|
@@ -18628,14 +18855,6 @@ ${dim(`labsui view ${hits[0]?.name} shows props and an example.`)}`);
|
|
|
18628
18855
|
// src/commands/update.ts
|
|
18629
18856
|
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
18630
18857
|
import { dirname as dirname3, relative as relative6 } from "node:path";
|
|
18631
|
-
var jsrBase = async (version) => {
|
|
18632
|
-
try {
|
|
18633
|
-
const res = await fetch(`https://jsr.io/@kznlabs/labsui/${version}/src/registry.json`);
|
|
18634
|
-
return res.ok ? (await res.json()).items : undefined;
|
|
18635
|
-
} catch {
|
|
18636
|
-
return;
|
|
18637
|
-
}
|
|
18638
|
-
};
|
|
18639
18858
|
var norm = (text) => text.replace(/\r\n/g, `
|
|
18640
18859
|
`).replace(/\n*$/, `
|
|
18641
18860
|
`);
|
|
@@ -18955,7 +19174,7 @@ var TOOLS = [
|
|
|
18955
19174
|
{
|
|
18956
19175
|
name: "diff",
|
|
18957
19176
|
title: "Diff installed labsUI items",
|
|
18958
|
-
description: "Compare installed labsUI items with this labsUI version. Returns a unified patch per changed file, from the registry file to the project file (+ lines exist only in the project), with formatting: true when only blank lines or indentation differ; matching items are left out, a named item that isn't installed comes back with installed: false, and when nothing differs it says so. Use it to see
|
|
19177
|
+
description: "Compare installed labsUI items with this labsUI version. Returns a unified patch per changed file, from the registry file to the project file (+ lines exist only in the project), with formatting: true when only blank lines or indentation differ and changedBy saying whose change it is since the installed version: upstream (only labsUI changed it; update replaces the file), local (only you did; update keeps it) or both (update merges), unset when that version can't be fetched; matching items are left out, a named item that isn't installed comes back with installed: false, and when nothing differs it says so. Use it before update to see what it will do. Needs init.",
|
|
18959
19178
|
inputSchema: {
|
|
18960
19179
|
type: "object",
|
|
18961
19180
|
properties: { names: installed }
|
|
@@ -19044,9 +19263,13 @@ async function callTool(ctx, name, args) {
|
|
|
19044
19263
|
if (!config)
|
|
19045
19264
|
throw new CliError("labsUI is not set up here. Call the init tool first.");
|
|
19046
19265
|
const items = await diff(ctx.cwd, { ...config, ...ctx.registry && { registry: ctx.registry } }, list(args.names), ctx.version);
|
|
19047
|
-
const body = items.length ? items.flatMap((i) => i.installed ? i.files.map((file) =>
|
|
19266
|
+
const body = items.length ? items.flatMap((i) => i.installed ? i.files.map((file) => {
|
|
19267
|
+
const note = changeNote(file, i.since);
|
|
19268
|
+
return `${note ? `${i.name}: ${note}
|
|
19269
|
+
` : ""}\`\`\`diff
|
|
19048
19270
|
${file.patch}
|
|
19049
|
-
|
|
19271
|
+
\`\`\``;
|
|
19272
|
+
}) : [`${i.name} is not installed; add installs it.`]).join(`
|
|
19050
19273
|
|
|
19051
19274
|
`) : "Installed items match this labsui version.";
|
|
19052
19275
|
return text(body, { items });
|