@kznlabs/labsui 0.1.6 → 0.1.7
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/dist/index.js +491 -149
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -231,6 +231,7 @@ export function AccentPicker({ className }: { className?: string }) {
|
|
|
231
231
|
return (
|
|
232
232
|
<fieldset className={cn("flex items-center gap-1.5", className)}>
|
|
233
233
|
<legend className="sr-only">Accent color</legend>
|
|
234
|
+
|
|
234
235
|
{accents.map((accent) => (
|
|
235
236
|
<label
|
|
236
237
|
key={accent}
|
|
@@ -245,12 +246,15 @@ export function AccentPicker({ className }: { className?: string }) {
|
|
|
245
246
|
onChange={() => setAccent(accent)}
|
|
246
247
|
className="peer sr-only"
|
|
247
248
|
/>
|
|
249
|
+
|
|
248
250
|
<span className="sr-only">{accent}</span>
|
|
251
|
+
|
|
249
252
|
<span
|
|
250
253
|
aria-hidden="true"
|
|
251
254
|
style={{ backgroundColor: swatch[accent] }}
|
|
252
255
|
className="size-4 rounded-full ring-1 ring-foreground/10 ring-inset transition-transform duration-200 peer-hover:scale-110 peer-focus-visible:ring-[3px] peer-focus-visible:ring-ring/50"
|
|
253
256
|
/>
|
|
257
|
+
|
|
254
258
|
{current === accent && (
|
|
255
259
|
<motion.span
|
|
256
260
|
aria-hidden="true"
|
|
@@ -418,6 +422,7 @@ export function AccordionTrigger({
|
|
|
418
422
|
)}
|
|
419
423
|
>
|
|
420
424
|
{children}
|
|
425
|
+
|
|
421
426
|
<motion.span
|
|
422
427
|
animate={{ rotate: open ? 180 : 0 }}
|
|
423
428
|
transition={spring}
|
|
@@ -537,7 +542,9 @@ export function Alert({
|
|
|
537
542
|
{glyph}
|
|
538
543
|
</span>
|
|
539
544
|
)}
|
|
545
|
+
|
|
540
546
|
<div className="grid min-w-0 flex-1 gap-1">{children}</div>
|
|
547
|
+
|
|
541
548
|
{action && <div className="-my-1 shrink-0 self-center">{action}</div>}
|
|
542
549
|
</div>
|
|
543
550
|
);
|
|
@@ -670,14 +677,17 @@ export function AppWindow({
|
|
|
670
677
|
);
|
|
671
678
|
})}
|
|
672
679
|
</div>
|
|
680
|
+
|
|
673
681
|
{header ??
|
|
674
682
|
(title && (
|
|
675
683
|
<p className="pointer-events-none absolute inset-x-24 truncate text-center font-medium text-muted-foreground text-xs">
|
|
676
684
|
{title}
|
|
677
685
|
</p>
|
|
678
686
|
))}
|
|
687
|
+
|
|
679
688
|
{actions && <div className="ml-auto flex items-center gap-1.5">{actions}</div>}
|
|
680
689
|
</div>
|
|
690
|
+
|
|
681
691
|
<div className={cn("flex-1", bodyClassName)}>{children}</div>
|
|
682
692
|
</div>
|
|
683
693
|
);
|
|
@@ -716,7 +726,7 @@ export function AspectRatio({ ratio = 16 / 9, className, style, ...props }: Aspe
|
|
|
716
726
|
}
|
|
717
727
|
` }] }, attachment: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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.", docs: "https://kznlabs.github.io/labsUI/components/attachment", usage: '<FileDropzone name="clips" multiple accept="video/*" maxSize={50 * 1024 * 1024} onFiles={add} onReject={warn} /><AttachmentList>{files.map((f) => <Attachment key={f.id} name={f.name} size={f.size} type={f.type} progress={f.progress} onRemove={() => remove(f.id)} />)}</AttachmentList>', examples: [{ name: "clip-upload.tsx", code: `import { FileDropzone } from "@/components/ui/attachment";
|
|
718
728
|
import { Button } from "@/components/ui/button";
|
|
719
|
-
import { uploadClips } from "./actions";
|
|
729
|
+
import { uploadClips } from "./actions";
|
|
720
730
|
|
|
721
731
|
// Picked and dropped files both land in the input, so the Server Action gets them.
|
|
722
732
|
export function ClipUpload() {
|
|
@@ -747,8 +757,14 @@ export function Attachments({ files, onRemove }: { files: File[]; onRemove: (id:
|
|
|
747
757
|
))}
|
|
748
758
|
</AttachmentList>
|
|
749
759
|
);
|
|
750
|
-
}` }
|
|
751
|
-
|
|
760
|
+
}` }, { name: "actions.ts", code: `"use server";
|
|
761
|
+
|
|
762
|
+
export async function uploadClips(formData: FormData) {
|
|
763
|
+
const clips = formData.getAll("clips") as File[];
|
|
764
|
+
|
|
765
|
+
console.log(clips.map((clip) => clip.name));
|
|
766
|
+
}` }], api: { entries: [{ kind: "function", name: "formatBytes", signature: "formatBytes(bytes: number, locale?: string) => string", description: 'Bytes as text in the given locale: 1536 is "1.5 kB".' }, { kind: "component", name: "Attachment", props: [{ name: "name", type: "string", required: true, description: "File name." }, { name: "error", type: "string", required: false, description: "Replaces the size line and turns the chip red." }, { name: "href", type: "string", required: false, description: "Makes the name a download link." }, { name: "locale", type: "string", required: false, description: "BCP 47 locale for the size." }, { name: "onRemove", type: "() => void", required: false, description: "Shows a remove button; called when it is pressed." }, { name: "progress", type: "number", required: false, description: "0 to 100 while uploading; the bar springs along the bottom edge." }, { name: "size", type: "number", required: false, description: "Bytes." }, { name: "src", type: "string", required: false, description: "Thumbnail source: a URL, or `URL.createObjectURL(file)` for a fresh upload." }, { name: "type", type: "string", required: false, description: "MIME type; picks the icon, and image types show `src` as a thumbnail." }], description: "File chip: thumbnail or type icon, name, size, upload progress or error, and remove." }, { kind: "component", name: "AttachmentList", props: [], extends: "<ul>", description: "Attachments that spring in, out and into place when one is removed. Key each child." }, { kind: "component", name: "FileDropzone", props: [{ name: "accept", type: "string", required: false, description: 'Same syntax as the input attribute: ".pdf,image/*". Enforced on drop and paste too.' }, { name: "aria-describedby", type: "string", required: false, description: "Ids of hint or error text; Field fills it in." }, { name: "aria-invalid", type: "boolean", required: false, description: "Error styling; Field sets it from `error`." }, { name: "children", type: "ReactNode", required: false, description: 'Replaces the default "Drop files or browse" content.' }, { name: "disabled", type: "boolean", required: false, description: "Blocks drops and the picker; no file is submitted." }, { name: "id", type: "string", required: false, description: "Id of the file input; Field fills it in." }, { name: "maxSize", type: "number", required: false, description: "Bytes." }, { name: "multiple", type: "boolean", required: false, description: "Accept more than one file." }, { name: "name", type: "string", required: false, description: "Form field name: picked and dropped files both submit with the form." }, { name: "onFiles", type: "(files: File[]) => void", required: false, description: "Files that passed `accept` and `maxSize`, from a drop or the picker." }, { name: "onReject", type: "(rejected: FileRejection[]) => void", required: false, description: "Files that failed `accept` or `maxSize`, each with a reason." }, { name: "required", type: "boolean", required: false, description: "Form submit needs at least one file." }], description: `Drop area around a real file input: click, Enter/Space and the native picker work as-is, and
|
|
767
|
+
dropped files are written back into the input so plain forms and Server Actions get them.` }], types: { FileRejection: '{ file: File; reason: "type" | "size" }' } }, dependencies: ["lucide-react@1.63.0", "motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/field.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/attachment.tsx", type: "registry:ui", content: `"use client";
|
|
752
768
|
|
|
753
769
|
import {
|
|
754
770
|
CloudUpload,
|
|
@@ -904,6 +920,7 @@ export function Attachment({
|
|
|
904
920
|
<Icon className="size-5" aria-hidden="true" />
|
|
905
921
|
)}
|
|
906
922
|
</span>
|
|
923
|
+
|
|
907
924
|
<span className="grid min-w-0 flex-1">
|
|
908
925
|
{href ? (
|
|
909
926
|
<a
|
|
@@ -916,6 +933,7 @@ export function Attachment({
|
|
|
916
933
|
) : (
|
|
917
934
|
<span className="truncate font-medium text-sm">{name}</span>
|
|
918
935
|
)}
|
|
936
|
+
|
|
919
937
|
<span
|
|
920
938
|
className={cn(
|
|
921
939
|
"truncate text-xs tabular-nums",
|
|
@@ -925,6 +943,7 @@ export function Attachment({
|
|
|
925
943
|
{meta || " "}
|
|
926
944
|
</span>
|
|
927
945
|
</span>
|
|
946
|
+
|
|
928
947
|
{onRemove && (
|
|
929
948
|
<button
|
|
930
949
|
type="button"
|
|
@@ -935,6 +954,7 @@ export function Attachment({
|
|
|
935
954
|
<X className="size-3.5" aria-hidden="true" />
|
|
936
955
|
</button>
|
|
937
956
|
)}
|
|
957
|
+
|
|
938
958
|
{uploading && (
|
|
939
959
|
<motion.span
|
|
940
960
|
aria-hidden="true"
|
|
@@ -963,7 +983,7 @@ export function AttachmentList({ className, children, ...props }: ComponentProps
|
|
|
963
983
|
|
|
964
984
|
/* ---------- FileDropzone ---------- */
|
|
965
985
|
|
|
966
|
-
type
|
|
986
|
+
export type FileRejection = { file: File; reason: "type" | "size" };
|
|
967
987
|
|
|
968
988
|
type FileDropzoneProps = {
|
|
969
989
|
/** Same syntax as the input attribute: ".pdf,image/*". Enforced on drop and paste too. */
|
|
@@ -975,12 +995,12 @@ type FileDropzoneProps = {
|
|
|
975
995
|
/** Files that passed \`accept\` and \`maxSize\`, from a drop or the picker. */
|
|
976
996
|
onFiles?: (files: File[]) => void;
|
|
977
997
|
/** Files that failed \`accept\` or \`maxSize\`, each with a reason. */
|
|
978
|
-
onReject?: (rejected:
|
|
998
|
+
onReject?: (rejected: FileRejection[]) => void;
|
|
979
999
|
/** Form field name: picked and dropped files both submit with the form. */
|
|
980
1000
|
name?: string;
|
|
981
1001
|
/** Id of the file input; Field fills it in. */
|
|
982
1002
|
id?: string;
|
|
983
|
-
/** Blocks drops and the picker. */
|
|
1003
|
+
/** Blocks drops and the picker; no file is submitted. */
|
|
984
1004
|
disabled?: boolean;
|
|
985
1005
|
/** Form submit needs at least one file. */
|
|
986
1006
|
required?: boolean;
|
|
@@ -1019,7 +1039,7 @@ export function FileDropzone(allProps: FileDropzoneProps) {
|
|
|
1019
1039
|
const all = [...list];
|
|
1020
1040
|
const picked = multiple ? all : all.slice(0, 1);
|
|
1021
1041
|
const ok: File[] = [];
|
|
1022
|
-
const rejected:
|
|
1042
|
+
const rejected: FileRejection[] = [];
|
|
1023
1043
|
|
|
1024
1044
|
for (const file of picked) {
|
|
1025
1045
|
if (!accepts(file, accept)) rejected.push({ file, reason: "type" });
|
|
@@ -1075,7 +1095,7 @@ export function FileDropzone(allProps: FileDropzoneProps) {
|
|
|
1075
1095
|
}}
|
|
1076
1096
|
onDrop={onDrop}
|
|
1077
1097
|
onPaste={(event) =>
|
|
1078
|
-
event.clipboardData.files.length > 0 && take(event.clipboardData.files, false)
|
|
1098
|
+
!disabled && event.clipboardData.files.length > 0 && take(event.clipboardData.files, false)
|
|
1079
1099
|
}
|
|
1080
1100
|
className={cn(
|
|
1081
1101
|
"flex cursor-pointer flex-col items-center justify-center gap-2 rounded-xl border border-dashed bg-card p-6 text-center transition-colors has-[:focus-visible]:ring-[3px] has-[:focus-visible]:ring-ring/50",
|
|
@@ -1095,15 +1115,18 @@ export function FileDropzone(allProps: FileDropzoneProps) {
|
|
|
1095
1115
|
className="sr-only"
|
|
1096
1116
|
{...input}
|
|
1097
1117
|
/>
|
|
1118
|
+
|
|
1098
1119
|
{children ?? (
|
|
1099
1120
|
<>
|
|
1100
1121
|
<span className="grid size-10 place-items-center rounded-full bg-muted text-muted-foreground">
|
|
1101
1122
|
<CloudUpload className="size-5" aria-hidden="true" />
|
|
1102
1123
|
</span>
|
|
1124
|
+
|
|
1103
1125
|
<span className="text-sm">
|
|
1104
1126
|
<span className="font-medium">Drop {multiple ? "files" : "a file"}</span> or{" "}
|
|
1105
1127
|
<span className="font-medium text-primary-ink">browse</span>
|
|
1106
1128
|
</span>
|
|
1129
|
+
|
|
1107
1130
|
{hint && <span className="text-muted-foreground text-xs">{hint}</span>}
|
|
1108
1131
|
</>
|
|
1109
1132
|
)}
|
|
@@ -1176,6 +1199,7 @@ export function Avatar({ src, alt, fallback, status, size = "default", className
|
|
|
1176
1199
|
className="relative flex size-full select-none items-center justify-center overflow-hidden rounded-full bg-muted font-medium text-muted-foreground ring-1 ring-border"
|
|
1177
1200
|
>
|
|
1178
1201
|
<span aria-hidden="true">{fallback ?? initials(alt)}</span>
|
|
1202
|
+
|
|
1179
1203
|
{showImg && (
|
|
1180
1204
|
// biome-ignore lint/performance/noImgElement: arbitrary remote avatars; no next/image loader config
|
|
1181
1205
|
<img
|
|
@@ -1196,6 +1220,7 @@ export function Avatar({ src, alt, fallback, status, size = "default", className
|
|
|
1196
1220
|
/>
|
|
1197
1221
|
)}
|
|
1198
1222
|
</span>
|
|
1223
|
+
|
|
1199
1224
|
{status && (
|
|
1200
1225
|
<span
|
|
1201
1226
|
className={cn(
|
|
@@ -1213,14 +1238,14 @@ export function Avatar({ src, alt, fallback, status, size = "default", className
|
|
|
1213
1238
|
` }] }, "avatar-group": { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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.", docs: "https://kznlabs.github.io/labsUI/components/avatar-group", usage: '<AvatarGroup users={team} max={4} size="lg" />', examples: [{ name: "team.tsx", code: `import { AvatarGroup } from "@/components/ui/avatar-group";
|
|
1214
1239
|
export function Team({ users }: { users: { alt: string; src?: string }[] }) {
|
|
1215
1240
|
return <AvatarGroup users={users} max={4} size="lg" />;
|
|
1216
|
-
}` }], api: { entries: [{ kind: "component", name: "AvatarGroup", props: [{ name: "users", type: "
|
|
1241
|
+
}` }], api: { entries: [{ kind: "component", name: "AvatarGroup", props: [{ name: "users", type: "AvatarGroupUser[]", required: true, description: "People in order; `alt` is each one's name." }, { name: "max", type: "number", required: false, default: "4", description: "How many avatars to show before the `+N` chip." }, { name: "size", type: '"sm" | "default" | "lg"', required: false, default: '"default"', description: "Size of every avatar and the chip." }], description: "Overlapping avatars with a `+N` overflow chip. Hover gently enlarges the avatar on a spring, staying in stack order." }], types: { AvatarGroupUser: "{ alt: string; src?: string; id?: string; }" } }, dependencies: ["motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/avatar.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/avatar-group.tsx", type: "registry:ui", content: `"use client";
|
|
1217
1242
|
|
|
1218
1243
|
import { motion } from "motion/react";
|
|
1219
1244
|
import { useState } from "react";
|
|
1220
1245
|
import { Avatar } from "@/components/ui/avatar";
|
|
1221
1246
|
import { cn, spring } from "@/lib/utils";
|
|
1222
1247
|
|
|
1223
|
-
type
|
|
1248
|
+
export type AvatarGroupUser = {
|
|
1224
1249
|
alt: string;
|
|
1225
1250
|
/** Image URL; initials from \`alt\` show without one. */
|
|
1226
1251
|
src?: string;
|
|
@@ -1230,7 +1255,7 @@ type GroupUser = {
|
|
|
1230
1255
|
|
|
1231
1256
|
type AvatarGroupProps = {
|
|
1232
1257
|
/** People in order; \`alt\` is each one's name. */
|
|
1233
|
-
users:
|
|
1258
|
+
users: AvatarGroupUser[];
|
|
1234
1259
|
/** How many avatars to show before the \`+N\` chip. */
|
|
1235
1260
|
max?: number;
|
|
1236
1261
|
/** Size of every avatar and the chip. */
|
|
@@ -1274,6 +1299,7 @@ export function AvatarGroup({ users, max = 4, size = "default", className }: Ava
|
|
|
1274
1299
|
<Avatar src={user.src} alt={user.alt} size={size} />
|
|
1275
1300
|
</motion.li>
|
|
1276
1301
|
))}
|
|
1302
|
+
|
|
1277
1303
|
{overflow > 0 && (
|
|
1278
1304
|
<li
|
|
1279
1305
|
className={cn(
|
|
@@ -1344,9 +1370,11 @@ export function Badge({
|
|
|
1344
1370
|
{pulse && (
|
|
1345
1371
|
<span className="absolute inset-0 animate-ping rounded-full bg-current opacity-60 [animation-duration:1.6s] motion-reduce:hidden" />
|
|
1346
1372
|
)}
|
|
1373
|
+
|
|
1347
1374
|
<span className="relative size-1.5 rounded-full bg-current" />
|
|
1348
1375
|
</span>
|
|
1349
1376
|
)}
|
|
1377
|
+
|
|
1350
1378
|
{children}
|
|
1351
1379
|
</span>
|
|
1352
1380
|
);
|
|
@@ -1363,10 +1391,10 @@ export function NavTrail({ post }: { post: { title: string } }) {
|
|
|
1363
1391
|
]}
|
|
1364
1392
|
/>
|
|
1365
1393
|
);
|
|
1366
|
-
}` }], api: { entries: [{ kind: "component", name: "Breadcrumb", props: [{ name: "items", type: "
|
|
1394
|
+
}` }], api: { entries: [{ kind: "component", name: "Breadcrumb", props: [{ name: "items", type: "BreadcrumbItem[]", required: true, description: "The trail, outermost first. The last item is the current page." }, { name: "separator", type: "ReactNode", required: false, description: "Drawn between items. Default a chevron." }], extends: "<nav>", description: "Where the page sits in the site: a list of links ending at the current page. Server-safe." }], types: { BreadcrumbItem: "{ label: ReactNode; href?: string; onClick?: MouseEventHandler<HTMLAnchorElement>; }" } }, dependencies: ["lucide-react@1.63.0"], registryDependencies: [], files: [{ path: "components/ui/breadcrumb.tsx", type: "registry:ui", content: `import { ChevronRight } from "lucide-react";
|
|
1367
1395
|
import type { ComponentProps, MouseEventHandler, ReactNode } from "react";
|
|
1368
1396
|
|
|
1369
|
-
type
|
|
1397
|
+
export type BreadcrumbItem = {
|
|
1370
1398
|
/** What the step reads as. */
|
|
1371
1399
|
label: ReactNode;
|
|
1372
1400
|
/** Leave out for a step that has no page of its own. */
|
|
@@ -1377,7 +1405,7 @@ type Crumb = {
|
|
|
1377
1405
|
|
|
1378
1406
|
type BreadcrumbProps = Omit<ComponentProps<"nav">, "children"> & {
|
|
1379
1407
|
/** The trail, outermost first. The last item is the current page. */
|
|
1380
|
-
items:
|
|
1408
|
+
items: BreadcrumbItem[];
|
|
1381
1409
|
/** Drawn between items. Default a chevron. */
|
|
1382
1410
|
separator?: ReactNode;
|
|
1383
1411
|
};
|
|
@@ -1401,6 +1429,7 @@ export function Breadcrumb({
|
|
|
1401
1429
|
{separator ?? <ChevronRight />}
|
|
1402
1430
|
</span>
|
|
1403
1431
|
)}
|
|
1432
|
+
|
|
1404
1433
|
{i === items.length - 1 ? (
|
|
1405
1434
|
<span aria-current="page" className="text-foreground">
|
|
1406
1435
|
{item.label}
|
|
@@ -1475,9 +1504,9 @@ import {
|
|
|
1475
1504
|
import { spring } from "@/lib/utils";
|
|
1476
1505
|
import { buttonVariants, type VariantProps } from "./button-variants";
|
|
1477
1506
|
|
|
1478
|
-
type ButtonStatus = "idle" | "loading" | "success";
|
|
1507
|
+
export type ButtonStatus = "idle" | "loading" | "success";
|
|
1479
1508
|
/** Where the old label slides out to; the new one comes from the opposite side. */
|
|
1480
|
-
type SwapDirection = "left" | "right" | "up" | "down";
|
|
1509
|
+
export type SwapDirection = "left" | "right" | "up" | "down";
|
|
1481
1510
|
type Phase = "idle" | "armed" | "loading" | "success";
|
|
1482
1511
|
|
|
1483
1512
|
type ButtonProps = Omit<HTMLMotionProps<"button">, "children"> &
|
|
@@ -1620,14 +1649,19 @@ export function Button({
|
|
|
1620
1649
|
{/* Hidden copies of every label reserve the widest one, so swaps never resize the button. */}
|
|
1621
1650
|
<span className="inline-grid justify-items-center">
|
|
1622
1651
|
{children !== undefined && <SizerLabel>{children}</SizerLabel>}
|
|
1652
|
+
|
|
1623
1653
|
{loadingLabel !== undefined && <SizerLabel>{loadingLabel}</SizerLabel>}
|
|
1654
|
+
|
|
1624
1655
|
{confirmLabel !== undefined && <SizerLabel>{confirmLabel}</SizerLabel>}
|
|
1656
|
+
|
|
1625
1657
|
{successLabel !== undefined && (
|
|
1626
1658
|
<SizerLabel>
|
|
1627
1659
|
<Check />
|
|
1660
|
+
|
|
1628
1661
|
{successLabel}
|
|
1629
1662
|
</SizerLabel>
|
|
1630
1663
|
)}
|
|
1664
|
+
|
|
1631
1665
|
<AnimatePresence mode="wait" initial={false}>
|
|
1632
1666
|
{phase === "success" && (
|
|
1633
1667
|
<motion.span
|
|
@@ -1645,9 +1679,11 @@ export function Button({
|
|
|
1645
1679
|
>
|
|
1646
1680
|
<Check aria-hidden="true" />
|
|
1647
1681
|
</motion.span>
|
|
1682
|
+
|
|
1648
1683
|
{successLabel ?? "Done"}
|
|
1649
1684
|
</motion.span>
|
|
1650
1685
|
)}
|
|
1686
|
+
|
|
1651
1687
|
{phase === "armed" && (
|
|
1652
1688
|
<motion.span
|
|
1653
1689
|
key="armed"
|
|
@@ -1659,6 +1695,7 @@ export function Button({
|
|
|
1659
1695
|
{confirmLabel}
|
|
1660
1696
|
</motion.span>
|
|
1661
1697
|
)}
|
|
1698
|
+
|
|
1662
1699
|
{phase === "loading" && (
|
|
1663
1700
|
<motion.span
|
|
1664
1701
|
key="loading"
|
|
@@ -1678,9 +1715,11 @@ export function Button({
|
|
|
1678
1715
|
<Loader2 className="animate-spin" aria-hidden="true" />
|
|
1679
1716
|
</motion.span>
|
|
1680
1717
|
</AnimatePresence>
|
|
1718
|
+
|
|
1681
1719
|
{loadingLabel ?? children}
|
|
1682
1720
|
</motion.span>
|
|
1683
1721
|
)}
|
|
1722
|
+
|
|
1684
1723
|
{phase === "idle" && (
|
|
1685
1724
|
<motion.span
|
|
1686
1725
|
key="idle"
|
|
@@ -2047,6 +2086,7 @@ export function Calendar(props: CalendarProps) {
|
|
|
2047
2086
|
<p id={\`\${id}-caption\`} aria-live="polite" className="font-semibold text-sm">
|
|
2048
2087
|
{month ? fmtMonth.format(month) : " "}
|
|
2049
2088
|
</p>
|
|
2089
|
+
|
|
2050
2090
|
<div className="flex gap-0.5">
|
|
2051
2091
|
<Button
|
|
2052
2092
|
variant="ghost"
|
|
@@ -2058,6 +2098,7 @@ export function Calendar(props: CalendarProps) {
|
|
|
2058
2098
|
>
|
|
2059
2099
|
<ChevronLeft />
|
|
2060
2100
|
</Button>
|
|
2101
|
+
|
|
2061
2102
|
<Button
|
|
2062
2103
|
variant="ghost"
|
|
2063
2104
|
size="icon"
|
|
@@ -2103,6 +2144,7 @@ export function Calendar(props: CalendarProps) {
|
|
|
2103
2144
|
))}
|
|
2104
2145
|
</tr>
|
|
2105
2146
|
</thead>
|
|
2147
|
+
|
|
2106
2148
|
<tbody>
|
|
2107
2149
|
{weeks.map((week, weekIndex) => (
|
|
2108
2150
|
// biome-ignore lint/suspicious/noArrayIndexKey: six fixed rows
|
|
@@ -2694,7 +2736,7 @@ export function ChangelogPage() {
|
|
|
2694
2736
|
]}
|
|
2695
2737
|
/>
|
|
2696
2738
|
);
|
|
2697
|
-
}` }], api: { entries: [{ kind: "component", name: "Changelog", props: [{ name: "releases", type: "
|
|
2739
|
+
}` }], api: { entries: [{ kind: "component", name: "Changelog", props: [{ name: "releases", type: "ChangelogRelease[]", required: true, description: "Newest first; the first one is highlighted." }], description: "Release notes on a Timeline: the newest release is the highlighted one, changes are typed with Badges, Kbd shows how to try it." }], types: { ChangelogRelease: "{ version: string; title: string; date: string; dateTime?: string; changes: Change[]; shortcut?: string[]; }" } }, dependencies: ["lucide-react@1.63.0"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/badge.json", "https://kznlabs.github.io/labsUI/r/kbd.json", "https://kznlabs.github.io/labsUI/r/timeline.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/blocks/changelog.tsx", type: "registry:block", content: `import { Rocket } from "lucide-react";
|
|
2698
2740
|
import type { ReactNode } from "react";
|
|
2699
2741
|
import { Badge } from "@/components/ui/badge";
|
|
2700
2742
|
import { KbdGroup } from "@/components/ui/kbd";
|
|
@@ -2705,7 +2747,7 @@ import { cn } from "@/lib/utils";
|
|
|
2705
2747
|
|
|
2706
2748
|
type Change = { type: "new" | "improved" | "fixed"; text: ReactNode };
|
|
2707
2749
|
|
|
2708
|
-
type
|
|
2750
|
+
export type ChangelogRelease = {
|
|
2709
2751
|
version: string;
|
|
2710
2752
|
/** Release headline, shown beside the version. */
|
|
2711
2753
|
title: string;
|
|
@@ -2730,7 +2772,7 @@ export function Changelog({
|
|
|
2730
2772
|
className,
|
|
2731
2773
|
}: {
|
|
2732
2774
|
/** Newest first; the first one is highlighted. */
|
|
2733
|
-
releases:
|
|
2775
|
+
releases: ChangelogRelease[];
|
|
2734
2776
|
className?: string;
|
|
2735
2777
|
}) {
|
|
2736
2778
|
return (
|
|
@@ -2747,6 +2789,7 @@ export function Changelog({
|
|
|
2747
2789
|
<Badge variant="secondary" className="font-mono">
|
|
2748
2790
|
{release.version}
|
|
2749
2791
|
</Badge>
|
|
2792
|
+
|
|
2750
2793
|
{release.title}
|
|
2751
2794
|
</span>
|
|
2752
2795
|
}
|
|
@@ -2758,10 +2801,12 @@ export function Changelog({
|
|
|
2758
2801
|
<Badge variant={kinds[change.type].variant} className="mt-px w-16 justify-center">
|
|
2759
2802
|
{kinds[change.type].label}
|
|
2760
2803
|
</Badge>
|
|
2804
|
+
|
|
2761
2805
|
<span>{change.text}</span>
|
|
2762
2806
|
</li>
|
|
2763
2807
|
))}
|
|
2764
2808
|
</ul>
|
|
2809
|
+
|
|
2765
2810
|
{release.shortcut && (
|
|
2766
2811
|
<p className="mt-3 flex items-center gap-2 text-muted-foreground text-xs">
|
|
2767
2812
|
Try it <KbdGroup keys={release.shortcut} />
|
|
@@ -2823,7 +2868,7 @@ import {
|
|
|
2823
2868
|
import { useDismiss } from "@/lib/use-dismiss";
|
|
2824
2869
|
import { clamp, cn, material, spring } from "@/lib/utils";
|
|
2825
2870
|
|
|
2826
|
-
type ChartSeries = {
|
|
2871
|
+
export type ChartSeries = {
|
|
2827
2872
|
key: string;
|
|
2828
2873
|
/** Legend and tooltip name of the series. */
|
|
2829
2874
|
label: string;
|
|
@@ -3168,9 +3213,11 @@ export function Chart({
|
|
|
3168
3213
|
// stop.
|
|
3169
3214
|
<Table containerClassName={view === "table" ? "max-h-[32rem]" : "overflow-x-visible"}>
|
|
3170
3215
|
<TableCaption className={view === "chart" ? "sr-only" : undefined}>{label}</TableCaption>
|
|
3216
|
+
|
|
3171
3217
|
<TableHeader>
|
|
3172
3218
|
<TableRow>
|
|
3173
3219
|
<TableHead className="capitalize">{index}</TableHead>
|
|
3220
|
+
|
|
3174
3221
|
{series.map((item) => (
|
|
3175
3222
|
<TableHead key={item.key} className="text-right">
|
|
3176
3223
|
{item.label}
|
|
@@ -3178,10 +3225,12 @@ export function Chart({
|
|
|
3178
3225
|
))}
|
|
3179
3226
|
</TableRow>
|
|
3180
3227
|
</TableHeader>
|
|
3228
|
+
|
|
3181
3229
|
<TableBody>
|
|
3182
3230
|
{data.map((row) => (
|
|
3183
3231
|
<TableRow key={String(row[index])}>
|
|
3184
3232
|
<TableCell className="font-medium">{row[index]}</TableCell>
|
|
3233
|
+
|
|
3185
3234
|
{series.map((item) => (
|
|
3186
3235
|
<TableCell key={item.key} className="text-right tabular-nums">
|
|
3187
3236
|
{fmt.format(num(row[item.key]))}
|
|
@@ -3229,6 +3278,7 @@ export function Chart({
|
|
|
3229
3278
|
}
|
|
3230
3279
|
style={{ background: color(slots.get(item.key) ?? 1) }}
|
|
3231
3280
|
/>
|
|
3281
|
+
|
|
3232
3282
|
<span className={cn(off && "line-through")}>{item.label}</span>
|
|
3233
3283
|
</button>
|
|
3234
3284
|
</li>
|
|
@@ -3238,6 +3288,7 @@ export function Chart({
|
|
|
3238
3288
|
) : (
|
|
3239
3289
|
<span />
|
|
3240
3290
|
)}
|
|
3291
|
+
|
|
3241
3292
|
<Button
|
|
3242
3293
|
size="sm"
|
|
3243
3294
|
variant="ghost"
|
|
@@ -3289,6 +3340,7 @@ export function Chart({
|
|
|
3289
3340
|
className={tick === 0 ? "stroke-foreground/25" : "stroke-border"}
|
|
3290
3341
|
shapeRendering="crispEdges"
|
|
3291
3342
|
/>
|
|
3343
|
+
|
|
3292
3344
|
<text
|
|
3293
3345
|
x={margin.left - 8}
|
|
3294
3346
|
y={y(tick)}
|
|
@@ -3300,6 +3352,7 @@ export function Chart({
|
|
|
3300
3352
|
</text>
|
|
3301
3353
|
</g>
|
|
3302
3354
|
))}
|
|
3355
|
+
|
|
3303
3356
|
{data.map((row, i) =>
|
|
3304
3357
|
i % every === 0 ? (
|
|
3305
3358
|
<text
|
|
@@ -3360,6 +3413,7 @@ export function Chart({
|
|
|
3360
3413
|
fillOpacity={0.1}
|
|
3361
3414
|
/>
|
|
3362
3415
|
)}
|
|
3416
|
+
|
|
3363
3417
|
<motion.path
|
|
3364
3418
|
initial={{ d: linePath(flat(pts)) }}
|
|
3365
3419
|
animate={{ d: linePath(pts) }}
|
|
@@ -3370,6 +3424,7 @@ export function Chart({
|
|
|
3370
3424
|
strokeLinejoin="round"
|
|
3371
3425
|
strokeLinecap="round"
|
|
3372
3426
|
/>
|
|
3427
|
+
|
|
3373
3428
|
{(active === null ? [rowCount - 1] : [active]).map((i) =>
|
|
3374
3429
|
pts[i] ? (
|
|
3375
3430
|
<motion.circle
|
|
@@ -3435,6 +3490,7 @@ export function Chart({
|
|
|
3435
3490
|
)}
|
|
3436
3491
|
>
|
|
3437
3492
|
<p className="mb-1.5 text-muted-foreground">{row[index]}</p>
|
|
3493
|
+
|
|
3438
3494
|
<ul className="grid gap-1">
|
|
3439
3495
|
{shown.map((item) => (
|
|
3440
3496
|
<li key={item.key} className="flex items-center gap-2">
|
|
@@ -3442,7 +3498,9 @@ export function Chart({
|
|
|
3442
3498
|
className="h-0.5 w-2.5 shrink-0 rounded-full"
|
|
3443
3499
|
style={{ background: color(slots.get(item.key) ?? 1) }}
|
|
3444
3500
|
/>
|
|
3501
|
+
|
|
3445
3502
|
<span className="text-muted-foreground">{item.label}</span>
|
|
3503
|
+
|
|
3446
3504
|
<span className="ml-auto pl-3 font-medium tabular-nums">
|
|
3447
3505
|
{fmt.format(num(row[item.key]))}
|
|
3448
3506
|
</span>
|
|
@@ -3454,9 +3512,11 @@ export function Chart({
|
|
|
3454
3512
|
)}
|
|
3455
3513
|
</AnimatePresence>
|
|
3456
3514
|
</div>
|
|
3515
|
+
|
|
3457
3516
|
<p className="sr-only" aria-live="polite">
|
|
3458
3517
|
{readout}
|
|
3459
3518
|
</p>
|
|
3519
|
+
|
|
3460
3520
|
{/* The same numbers for screen readers, search engines and no-JS. */}
|
|
3461
3521
|
<div className="sr-only">{table}</div>
|
|
3462
3522
|
</>
|
|
@@ -3528,6 +3588,7 @@ export function Checkbox({
|
|
|
3528
3588
|
className="peer absolute -inset-1 z-10 cursor-pointer appearance-none rounded-[5px] outline-none disabled:cursor-not-allowed"
|
|
3529
3589
|
{...props}
|
|
3530
3590
|
/>
|
|
3591
|
+
|
|
3531
3592
|
<span
|
|
3532
3593
|
aria-hidden="true"
|
|
3533
3594
|
className={cn(
|
|
@@ -3638,10 +3699,13 @@ export function ClipCard({
|
|
|
3638
3699
|
className="grid place-items-center overflow-hidden rounded-lg bg-foreground/[0.06] text-muted-foreground"
|
|
3639
3700
|
>
|
|
3640
3701
|
{thumbnail ?? <Film className="size-5" aria-hidden="true" />}
|
|
3702
|
+
|
|
3641
3703
|
<Badge variant="secondary" className="absolute right-1.5 bottom-2 tabular-nums">
|
|
3642
3704
|
<span className="sr-only">Duration </span>
|
|
3705
|
+
|
|
3643
3706
|
{duration}
|
|
3644
3707
|
</Badge>
|
|
3708
|
+
|
|
3645
3709
|
{watched !== undefined && watched > 0 && (
|
|
3646
3710
|
<Progress
|
|
3647
3711
|
value={watched}
|
|
@@ -3651,8 +3715,10 @@ export function ClipCard({
|
|
|
3651
3715
|
/>
|
|
3652
3716
|
)}
|
|
3653
3717
|
</AspectRatio>
|
|
3718
|
+
|
|
3654
3719
|
<div className="grid gap-0.5 px-0.5 text-left">
|
|
3655
3720
|
<p className="truncate font-medium text-sm">{title}</p>
|
|
3721
|
+
|
|
3656
3722
|
{meta && <p className="truncate text-muted-foreground text-xs">{meta}</p>}
|
|
3657
3723
|
</div>
|
|
3658
3724
|
</>
|
|
@@ -3672,6 +3738,7 @@ export function ClipCard({
|
|
|
3672
3738
|
>
|
|
3673
3739
|
{body}
|
|
3674
3740
|
</ContextMenuTrigger>
|
|
3741
|
+
|
|
3675
3742
|
<ContextMenuContent>{actions}</ContextMenuContent>
|
|
3676
3743
|
</ContextMenu>
|
|
3677
3744
|
);
|
|
@@ -3717,13 +3784,13 @@ import { OverflowFocus } from "@/lib/overflow-focus";
|
|
|
3717
3784
|
import { cn } from "@/lib/utils";
|
|
3718
3785
|
|
|
3719
3786
|
/** One line, or an inclusive \`[from, to]\` run. Numbers are the ones shown in the gutter. */
|
|
3720
|
-
type LineRange = number | [from: number, to: number];
|
|
3787
|
+
export type LineRange = number | [from: number, to: number];
|
|
3721
3788
|
|
|
3722
3789
|
/**
|
|
3723
3790
|
* Text to mark: every match of a string, matches on one line, or an exact \`[start, end)\`
|
|
3724
3791
|
* column range (String#slice indices) on one line.
|
|
3725
3792
|
*/
|
|
3726
|
-
type CodeMark =
|
|
3793
|
+
export type CodeMark =
|
|
3727
3794
|
| string
|
|
3728
3795
|
| { text: string; line?: number }
|
|
3729
3796
|
| { line: number; start: number; end: number };
|
|
@@ -3739,7 +3806,7 @@ type Annotations = {
|
|
|
3739
3806
|
notes?: Record<number, ReactNode>;
|
|
3740
3807
|
};
|
|
3741
3808
|
|
|
3742
|
-
type CodeFile = Annotations & {
|
|
3809
|
+
export type CodeFile = Annotations & {
|
|
3743
3810
|
name: string;
|
|
3744
3811
|
code: string;
|
|
3745
3812
|
/** Lines from \`highlightCode()\`, made ahead of time (server or build): no Shiki in the browser. */
|
|
@@ -4076,9 +4143,11 @@ function CodeLines({
|
|
|
4076
4143
|
)}
|
|
4077
4144
|
/>
|
|
4078
4145
|
)}
|
|
4146
|
+
|
|
4079
4147
|
{numbered && <span style={{ minWidth: \`\${digits}ch\` }}>{lineNumber}</span>}
|
|
4080
4148
|
</span>
|
|
4081
4149
|
)}
|
|
4150
|
+
|
|
4082
4151
|
{note ? (
|
|
4083
4152
|
<Tooltip content={note} side="top" align="start" delay={150} touch="tap">
|
|
4084
4153
|
<span
|
|
@@ -4122,8 +4191,10 @@ export function Code({
|
|
|
4122
4191
|
>
|
|
4123
4192
|
<div className="min-w-0 flex-1 overflow-x-auto whitespace-nowrap rounded-sm font-mono text-xs outline-none [scrollbar-width:none] focus-visible:ring-[3px] focus-visible:ring-ring/50 [&::-webkit-scrollbar]:hidden">
|
|
4124
4193
|
<CodeLines text={code} lines={lines} marks={annotations.marks} inline />
|
|
4194
|
+
|
|
4125
4195
|
<OverflowFocus />
|
|
4126
4196
|
</div>
|
|
4197
|
+
|
|
4127
4198
|
<CopyButton code={code} />
|
|
4128
4199
|
</div>
|
|
4129
4200
|
);
|
|
@@ -4138,10 +4209,13 @@ export function Code({
|
|
|
4138
4209
|
>
|
|
4139
4210
|
<div className="flex h-8 items-center justify-between border-b px-3">
|
|
4140
4211
|
<span className="font-mono text-muted-foreground text-xs">{language}</span>
|
|
4212
|
+
|
|
4141
4213
|
<CopyButton code={code} />
|
|
4142
4214
|
</div>
|
|
4215
|
+
|
|
4143
4216
|
<div className="overflow-x-auto font-mono text-xs leading-5 outline-none [--code-px:--spacing(3)] focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:ring-inset">
|
|
4144
4217
|
<CodeLines text={code} lines={lines} {...annotations} />
|
|
4218
|
+
|
|
4145
4219
|
<OverflowFocus />
|
|
4146
4220
|
</div>
|
|
4147
4221
|
</div>
|
|
@@ -4196,8 +4270,10 @@ export function CodePanel({ files, format, collapse, className }: CodePanelProps
|
|
|
4196
4270
|
</TabsTrigger>
|
|
4197
4271
|
))}
|
|
4198
4272
|
</TabsList>
|
|
4273
|
+
|
|
4199
4274
|
<CopyButton code={copyText} />
|
|
4200
4275
|
</div>
|
|
4276
|
+
|
|
4201
4277
|
{files.map((tab) => (
|
|
4202
4278
|
<TabsContent
|
|
4203
4279
|
key={tab.name}
|
|
@@ -4215,6 +4291,7 @@ export function CodePanel({ files, format, collapse, className }: CodePanelProps
|
|
|
4215
4291
|
</div>
|
|
4216
4292
|
</TabsContent>
|
|
4217
4293
|
))}
|
|
4294
|
+
|
|
4218
4295
|
{foldable && (
|
|
4219
4296
|
<button
|
|
4220
4297
|
type="button"
|
|
@@ -4329,6 +4406,7 @@ export function CollapsibleTrigger({
|
|
|
4329
4406
|
{...props}
|
|
4330
4407
|
>
|
|
4331
4408
|
{children}
|
|
4409
|
+
|
|
4332
4410
|
{chevron && (
|
|
4333
4411
|
<motion.span
|
|
4334
4412
|
animate={{ rotate: open ? 180 : 0 }}
|
|
@@ -4368,7 +4446,7 @@ export function CollapsibleContent({
|
|
|
4368
4446
|
</motion.div>
|
|
4369
4447
|
);
|
|
4370
4448
|
}
|
|
4371
|
-
` }] }, combobox: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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
|
|
4449
|
+
` }] }, combobox: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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.", docs: "https://kznlabs.github.io/labsUI/components/combobox", usage: '<Combobox options={channels} value={channel} onValueChange={setChannel} placeholder="Pick a channel…" />', examples: [{ name: "channel-picker.tsx", code: `"use client";
|
|
4372
4450
|
|
|
4373
4451
|
import { Hash } from "lucide-react";
|
|
4374
4452
|
import { useState } from "react";
|
|
@@ -4411,7 +4489,7 @@ export function RolePicker() {
|
|
|
4411
4489
|
placeholder="Add roles…"
|
|
4412
4490
|
/>
|
|
4413
4491
|
);
|
|
4414
|
-
}` }], api: { entries: [{ kind: "component", name: "Combobox", props: [{ name: "options", type: "ComboboxOption[]", required: true, description: "Choices; filtered as the user types." }, { name: "aria-describedby", type: "string", required: false, description: "Ids of hint or error text; Field fills it in." }, { name: "aria-invalid", type: "boolean", required: false, description: "Error styling; Field sets it from `error`." }, { name: "aria-label", type: "string", required: false, description: "Accessible name when there is no visible label." }, { name: "defaultValue", type: "string | string[]", required: false, description: "Value, or values when `multiple`, at first render (uncontrolled)." }, { name: "disabled", type: "boolean", required: false, description: "Blocks typing and opening." }, { name: "emptyText", type: "ReactNode", required: false, description: "Shown in the list when nothing matches." }, { name: "id", type: "string", required: false, description: "Id of the input; Field fills it in." }, { name: "multiple", type: "boolean", required: false, description: "Picks stay open and show as removable tags; Backspace removes the last one." }, { name: "name", type: "string", required: false, description: "
|
|
4492
|
+
}` }], api: { entries: [{ kind: "component", name: "Combobox", props: [{ name: "options", type: "ComboboxOption[]", required: true, description: "Choices; filtered as the user types." }, { name: "aria-describedby", type: "string", required: false, description: "Ids of hint or error text; Field fills it in." }, { name: "aria-invalid", type: "boolean", required: false, description: "Error styling; Field sets it from `error`." }, { name: "aria-label", type: "string", required: false, description: "Accessible name when there is no visible label." }, { name: "defaultValue", type: "string | string[]", required: false, description: "Value, or values when `multiple`, at first render (uncontrolled)." }, { name: "disabled", type: "boolean", required: false, description: "Blocks typing and opening; the value isn't submitted." }, { name: "emptyText", type: "ReactNode", required: false, description: "Shown in the list when nothing matches." }, { name: "id", type: "string", required: false, description: "Id of the input; Field fills it in." }, { name: "multiple", type: "boolean", required: false, description: "Picks stay open and show as removable tags; Backspace removes the last one." }, { name: "name", type: "string", required: false, description: "Form field name: one entry per picked value." }, { name: "onValueChange", type: "((value: string) => void) | ((value: string[]) => void)", required: false, description: "Called with the picked value, or every picked value when `multiple`." }, { name: "placeholder", type: "string", required: false, description: "Field text when nothing is picked." }, { name: "required", type: "boolean", required: false, description: "Form submit needs a value." }, { name: "value", type: "string | string[]", required: false, description: "Controlled value, or values when `multiple`; pair with `onValueChange`." }], description: "Select you can type into: the input filters the list, Up and Down move, Enter picks, Esc closes.\nSingle shows the pick in the input; `multiple` shows picks as tags and keeps the list open." }], types: { ComboboxOption: "{ value: string; label: string; icon?: ReactNode; keywords?: string[]; disabled?: boolean; }" } }, dependencies: ["lucide-react@1.63.0", "motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/field.json", "https://kznlabs.github.io/labsUI/r/form-value.json", "https://kznlabs.github.io/labsUI/r/list.json", "https://kznlabs.github.io/labsUI/r/platform.json", "https://kznlabs.github.io/labsUI/r/tag-input.json", "https://kznlabs.github.io/labsUI/r/use-anchor.json", "https://kznlabs.github.io/labsUI/r/use-controllable-state.json", "https://kznlabs.github.io/labsUI/r/use-dismiss.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/combobox.tsx", type: "registry:ui", content: `"use client";
|
|
4415
4493
|
|
|
4416
4494
|
import { Check, ChevronsUpDown } from "lucide-react";
|
|
4417
4495
|
import { AnimatePresence, motion } from "motion/react";
|
|
@@ -4447,11 +4525,11 @@ type BaseProps = {
|
|
|
4447
4525
|
placeholder?: string;
|
|
4448
4526
|
/** Shown in the list when nothing matches. */
|
|
4449
4527
|
emptyText?: ReactNode;
|
|
4450
|
-
/**
|
|
4528
|
+
/** Form field name: one entry per picked value. */
|
|
4451
4529
|
name?: string;
|
|
4452
4530
|
/** Id of the input; Field fills it in. */
|
|
4453
4531
|
id?: string;
|
|
4454
|
-
/** Blocks typing and opening. */
|
|
4532
|
+
/** Blocks typing and opening; the value isn't submitted. */
|
|
4455
4533
|
disabled?: boolean;
|
|
4456
4534
|
/** Form submit needs a value. */
|
|
4457
4535
|
required?: boolean;
|
|
@@ -4637,11 +4715,13 @@ export function Combobox(allProps: ComboboxProps) {
|
|
|
4637
4715
|
))}
|
|
4638
4716
|
</AnimatePresence>
|
|
4639
4717
|
)}
|
|
4718
|
+
|
|
4640
4719
|
{!multiple && !open && selected[0]?.icon && (
|
|
4641
4720
|
<span className="mr-2 flex shrink-0 text-muted-foreground [&_svg]:size-4">
|
|
4642
4721
|
{selected[0].icon}
|
|
4643
4722
|
</span>
|
|
4644
4723
|
)}
|
|
4724
|
+
|
|
4645
4725
|
<input
|
|
4646
4726
|
ref={input}
|
|
4647
4727
|
id={id}
|
|
@@ -4668,21 +4748,26 @@ export function Combobox(allProps: ComboboxProps) {
|
|
|
4668
4748
|
multiple && "px-1",
|
|
4669
4749
|
)}
|
|
4670
4750
|
/>
|
|
4751
|
+
|
|
4671
4752
|
<ChevronsUpDown
|
|
4672
4753
|
className="ml-1 size-4 shrink-0 self-center text-muted-foreground"
|
|
4673
4754
|
aria-hidden="true"
|
|
4674
4755
|
/>
|
|
4675
4756
|
</div>
|
|
4757
|
+
|
|
4676
4758
|
{name &&
|
|
4677
4759
|
(multiple ? values : values.slice(0, 1)).map((picked) => (
|
|
4678
|
-
<input key={picked} type="hidden" name={name} value={picked} />
|
|
4760
|
+
<input key={picked} type="hidden" name={name} value={picked} disabled={disabled} />
|
|
4679
4761
|
))}
|
|
4762
|
+
|
|
4680
4763
|
<FormValue
|
|
4681
4764
|
value={values.join(",")}
|
|
4682
|
-
required={required
|
|
4765
|
+
required={required}
|
|
4766
|
+
disabled={disabled}
|
|
4683
4767
|
anchor={input}
|
|
4684
4768
|
onInvalid={() => setInvalid(true)}
|
|
4685
4769
|
/>
|
|
4770
|
+
|
|
4686
4771
|
{mounted &&
|
|
4687
4772
|
createPortal(
|
|
4688
4773
|
<AnimatePresence>
|
|
@@ -4738,7 +4823,9 @@ export function Combobox(allProps: ComboboxProps) {
|
|
|
4738
4823
|
{values.includes(option.value) && (
|
|
4739
4824
|
<Check className="!text-current absolute left-2" aria-hidden="true" />
|
|
4740
4825
|
)}
|
|
4826
|
+
|
|
4741
4827
|
{option.icon}
|
|
4828
|
+
|
|
4742
4829
|
<span className="truncate">{option.label}</span>
|
|
4743
4830
|
</div>
|
|
4744
4831
|
))
|
|
@@ -5010,6 +5097,7 @@ export function CommandGroup({
|
|
|
5010
5097
|
{heading}
|
|
5011
5098
|
</div>
|
|
5012
5099
|
)}
|
|
5100
|
+
|
|
5013
5101
|
{children}
|
|
5014
5102
|
</div>
|
|
5015
5103
|
);
|
|
@@ -5123,6 +5211,7 @@ export function CommandDialog({
|
|
|
5123
5211
|
)}
|
|
5124
5212
|
>
|
|
5125
5213
|
<DialogTitle className="sr-only">{title}</DialogTitle>
|
|
5214
|
+
|
|
5126
5215
|
<Command close={() => setOpen(false)} className="rounded-none border-0 bg-transparent">
|
|
5127
5216
|
{children}
|
|
5128
5217
|
</Command>
|
|
@@ -5185,7 +5274,7 @@ import {
|
|
|
5185
5274
|
DialogTitle,
|
|
5186
5275
|
} from "@/components/ui/dialog";
|
|
5187
5276
|
|
|
5188
|
-
type ConfirmOptions = {
|
|
5277
|
+
export type ConfirmOptions = {
|
|
5189
5278
|
/** Question in the dialog heading. */
|
|
5190
5279
|
title: ReactNode;
|
|
5191
5280
|
/** Consequences, under the title. */
|
|
@@ -5249,10 +5338,13 @@ export function Confirmer() {
|
|
|
5249
5338
|
<DialogContent alert className="max-w-md">
|
|
5250
5339
|
<DialogHeader>
|
|
5251
5340
|
<DialogTitle>{title}</DialogTitle>
|
|
5341
|
+
|
|
5252
5342
|
{description && <DialogDescription>{description}</DialogDescription>}
|
|
5253
5343
|
</DialogHeader>
|
|
5344
|
+
|
|
5254
5345
|
<DialogFooter>
|
|
5255
5346
|
<DialogClose>{cancelLabel}</DialogClose>
|
|
5347
|
+
|
|
5256
5348
|
<Button variant={destructive ? "destructive" : "default"} onClick={() => settle(true)}>
|
|
5257
5349
|
{options.confirmLabel ?? (destructive ? "Delete" : "Continue")}
|
|
5258
5350
|
</Button>
|
|
@@ -5505,9 +5597,11 @@ export function CountTo({
|
|
|
5505
5597
|
return (
|
|
5506
5598
|
<span ref={ref} className={className}>
|
|
5507
5599
|
<span className="sr-only">{target}</span>
|
|
5600
|
+
|
|
5508
5601
|
<span ref={out} aria-hidden="true" data-count-to-animated="">
|
|
5509
5602
|
{text(from)}
|
|
5510
5603
|
</span>
|
|
5604
|
+
|
|
5511
5605
|
<span aria-hidden="true" data-count-to-fallback="" className="hidden">
|
|
5512
5606
|
{target}
|
|
5513
5607
|
</span>
|
|
@@ -5616,6 +5710,7 @@ export function CustomCursor() {
|
|
|
5616
5710
|
}}
|
|
5617
5711
|
transition={spring}
|
|
5618
5712
|
/>
|
|
5713
|
+
|
|
5619
5714
|
<motion.div
|
|
5620
5715
|
className="absolute top-0 left-0 size-1.5 rounded-full bg-foreground"
|
|
5621
5716
|
style={{ x, y, translateX: "-50%", translateY: "-50%" }}
|
|
@@ -5678,7 +5773,7 @@ export function MembersDataTable({
|
|
|
5678
5773
|
)}
|
|
5679
5774
|
/>
|
|
5680
5775
|
);
|
|
5681
|
-
}` }], api: { entries: [{ kind: "component", name: "DataTable", props: [{ name: "columns", type: "DataTableColumn<T>[]", required: true, description: "Column definitions in display order." }, { name: "data", type: "T[]", required: true, description: "Rows; search, sort and paging happen on the client." }, { name: "getRowId", type: "(row: T) => string", required: true, description: "Stable id per row; selection is kept by it across pages." }, { name: "label", type: "string", required: true, description: `Names the table, the search box and each row's controls: "Members", "Select Jane".` }, { name: "rowLabel", type: "(row: T) => string", required: true, description: "Row name for screen readers, e.g. on its checkbox and ⋯ menu." }, { name: "defaultSort", type: "
|
|
5776
|
+
}` }], api: { entries: [{ kind: "component", name: "DataTable", props: [{ name: "columns", type: "DataTableColumn<T>[]", required: true, description: "Column definitions in display order." }, { name: "data", type: "T[]", required: true, description: "Rows; search, sort and paging happen on the client." }, { name: "getRowId", type: "(row: T) => string", required: true, description: "Stable id per row; selection is kept by it across pages." }, { name: "label", type: "string", required: true, description: `Names the table, the search box and each row's controls: "Members", "Select Jane".` }, { name: "rowLabel", type: "(row: T) => string", required: true, description: "Row name for screen readers, e.g. on its checkbox and ⋯ menu." }, { name: "defaultSort", type: "DataTableSort", required: false, description: "Sort at first render." }, { name: "onSelectionChange", type: "(ids: string[]) => void", required: false, description: "Called with every selected row id." }, { name: "pageSize", type: "number", required: false, default: "10", description: "Rows per page." }, { name: "rowActions", type: "(row: T) => ReactNode", required: false, description: "Items for the row's ⋯ menu (DropdownMenuItem and friends)." }, { name: "searchable", type: "boolean", required: false, default: "true", description: "Show a search field that filters every column." }, { name: "searchPlaceholder", type: "string", required: false, default: '"Search…"', description: "Search field placeholder." }, { name: "selectable", type: "boolean", required: false, default: "false", description: "Checkbox column; selection survives search and paging." }, { name: "selection", type: "string[]", required: false, description: "Controlled selected row ids; pair with `onSelectionChange`." }, { name: "striped", type: "boolean", required: false, default: "false", description: "Alternate row backgrounds." }, { name: "toolbar", type: "(selected: T[]) => ReactNode", required: false, description: "Right side of the toolbar; receives the selected rows (bulk actions)." }], description: "Table with search, sortable columns, row selection, row actions and pagination, all on the\nclient. Columns are data: `{ id, header, accessor, cell?, sortable? }`. For server-driven\ndata, compose Table + Pagination yourself." }], types: { DataTableSort: '{ id: string; dir: "ascending" | "descending" }', DataTableColumn: "{ id: string; header: ReactNode; accessor?: (row: T) => string | number; cell?: (row: T) => ReactNode; sortable?: boolean; className?: string; }" } }, dependencies: ["lucide-react@1.63.0"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/button.json", "https://kznlabs.github.io/labsUI/r/checkbox.json", "https://kznlabs.github.io/labsUI/r/dropdown-menu.json", "https://kznlabs.github.io/labsUI/r/empty.json", "https://kznlabs.github.io/labsUI/r/pagination.json", "https://kznlabs.github.io/labsUI/r/search-input.json", "https://kznlabs.github.io/labsUI/r/table.json", "https://kznlabs.github.io/labsUI/r/use-controllable-state.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/data-table.tsx", type: "registry:ui", content: `"use client";
|
|
5682
5777
|
|
|
5683
5778
|
import { MoreHorizontal, SearchX } from "lucide-react";
|
|
5684
5779
|
import { type ReactNode, useLayoutEffect, useRef, useState } from "react";
|
|
@@ -5703,7 +5798,7 @@ import {
|
|
|
5703
5798
|
import { useControllableState } from "@/lib/use-controllable-state";
|
|
5704
5799
|
import { cn } from "@/lib/utils";
|
|
5705
5800
|
|
|
5706
|
-
type
|
|
5801
|
+
export type DataTableSort = { id: string; dir: "ascending" | "descending" };
|
|
5707
5802
|
|
|
5708
5803
|
export type DataTableColumn<T> = {
|
|
5709
5804
|
/** Unique key; also used by \`defaultSort\`. */
|
|
@@ -5732,7 +5827,7 @@ type DataTableProps<T> = {
|
|
|
5732
5827
|
/** Rows per page. */
|
|
5733
5828
|
pageSize?: number;
|
|
5734
5829
|
/** Sort at first render. */
|
|
5735
|
-
defaultSort?:
|
|
5830
|
+
defaultSort?: DataTableSort;
|
|
5736
5831
|
/** Show a search field that filters every column. */
|
|
5737
5832
|
searchable?: boolean;
|
|
5738
5833
|
/** Search field placeholder. */
|
|
@@ -5861,6 +5956,7 @@ export function DataTable<T>({
|
|
|
5861
5956
|
className="w-full sm:max-w-64"
|
|
5862
5957
|
/>
|
|
5863
5958
|
)}
|
|
5959
|
+
|
|
5864
5960
|
{toolbar && (
|
|
5865
5961
|
<div className="ml-auto flex items-center gap-2">
|
|
5866
5962
|
{toolbar(data.filter((row) => selected.includes(getRowId(row))))}
|
|
@@ -5890,6 +5986,7 @@ export function DataTable<T>({
|
|
|
5890
5986
|
/>
|
|
5891
5987
|
</TableHead>
|
|
5892
5988
|
)}
|
|
5989
|
+
|
|
5893
5990
|
{columns.map((column) => (
|
|
5894
5991
|
<TableHead
|
|
5895
5992
|
key={column.id}
|
|
@@ -5913,6 +6010,7 @@ export function DataTable<T>({
|
|
|
5913
6010
|
{column.header}
|
|
5914
6011
|
</TableHead>
|
|
5915
6012
|
))}
|
|
6013
|
+
|
|
5916
6014
|
{rowActions && (
|
|
5917
6015
|
<TableHead className="w-px">
|
|
5918
6016
|
<span className="sr-only">Actions</span>
|
|
@@ -5920,6 +6018,7 @@ export function DataTable<T>({
|
|
|
5920
6018
|
)}
|
|
5921
6019
|
</TableRow>
|
|
5922
6020
|
</TableHeader>
|
|
6021
|
+
|
|
5923
6022
|
<TableBody>
|
|
5924
6023
|
{rows.length === 0 ? (
|
|
5925
6024
|
<TableRow className="hover:bg-transparent">
|
|
@@ -5955,11 +6054,13 @@ export function DataTable<T>({
|
|
|
5955
6054
|
/>
|
|
5956
6055
|
</TableCell>
|
|
5957
6056
|
)}
|
|
6057
|
+
|
|
5958
6058
|
{columns.map((column) => (
|
|
5959
6059
|
<TableCell key={column.id} className={column.className}>
|
|
5960
6060
|
{column.cell ? column.cell(row) : column.accessor?.(row)}
|
|
5961
6061
|
</TableCell>
|
|
5962
6062
|
))}
|
|
6063
|
+
|
|
5963
6064
|
{rowActions && (
|
|
5964
6065
|
<TableCell className="py-1 pr-2">
|
|
5965
6066
|
<DropdownMenu>
|
|
@@ -5971,6 +6072,7 @@ export function DataTable<T>({
|
|
|
5971
6072
|
>
|
|
5972
6073
|
<MoreHorizontal />
|
|
5973
6074
|
</DropdownMenuTrigger>
|
|
6075
|
+
|
|
5974
6076
|
<DropdownMenuContent align="end">{rowActions(row)}</DropdownMenuContent>
|
|
5975
6077
|
</DropdownMenu>
|
|
5976
6078
|
</TableCell>
|
|
@@ -5987,10 +6089,12 @@ export function DataTable<T>({
|
|
|
5987
6089
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
5988
6090
|
<p className="text-muted-foreground text-xs tabular-nums" aria-live="polite">
|
|
5989
6091
|
{selectable && selected.length > 0 && \`\${selected.length} selected · \`}
|
|
6092
|
+
|
|
5990
6093
|
{sorted.length === 0
|
|
5991
6094
|
? "0 results"
|
|
5992
6095
|
: \`\${from}–\${from + rows.length - 1} of \${sorted.length}\`}
|
|
5993
6096
|
</p>
|
|
6097
|
+
|
|
5994
6098
|
{pages > 1 && (
|
|
5995
6099
|
<Pagination
|
|
5996
6100
|
page={page}
|
|
@@ -6003,7 +6107,7 @@ export function DataTable<T>({
|
|
|
6003
6107
|
</div>
|
|
6004
6108
|
);
|
|
6005
6109
|
}
|
|
6006
|
-
` }] }, "date-picker": { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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
|
|
6110
|
+
` }] }, "date-picker": { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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.", docs: "https://kznlabs.github.io/labsUI/components/date-picker", usage: '<Field label="Report period"><DatePicker mode="range" name="period" presets={[{ label: "Last 7 days", value: { from, to } }]} /></Field>', examples: [{ name: "report-filters.tsx", code: `"use client";
|
|
6007
6111
|
|
|
6008
6112
|
import { DatePicker } from "@/components/ui/date-picker";
|
|
6009
6113
|
import { Field } from "@/components/ui/field";
|
|
@@ -6026,8 +6130,8 @@ export function ReportFilters() {
|
|
|
6026
6130
|
/>
|
|
6027
6131
|
</Field>
|
|
6028
6132
|
);
|
|
6029
|
-
}` }], api: { entries: [{ kind: "component", name: "DatePicker", props: [{ name: "aria-describedby", type: "string", required: false, description: "Ids of hint or error text; Field fills it in." }, { name: "aria-invalid", type: "boolean", required: false, description: "Error styling; Field sets it from `error`." }, { name: "aria-label", type: "string", required: false, description: "Accessible name when there is no visible label." }, { name: "defaultValue", type: "Date | DateRange | null", required: false, description: "Day or range at first render (uncontrolled)." }, { name: "disabled", type: "boolean", required: false, description: "Blocks opening." }, { name: "id", type: "string", required: false, description: "Pair with <Label htmlFor>, or let <Field> fill it in." }, { name: "isDisabled", type: "(date: Date) => boolean", required: false, description: "Extra rule, e.g. weekends off. Client-only (a function)." }, { name: "locale", type: "string", required: false, description: "BCP 47 locale for the button text and the calendar." }, { name: "max", type: "Date", required: false, description: "Latest selectable day." }, { name: "min", type: "Date", required: false, description: "Earliest selectable day." }, { name: "mode", type: '"single" | "range"', required: false, description: '"single": pick one day. "range": pick a start, then an end.' }, { name: "name", type: "string", required: false, description: "Submits ISO
|
|
6030
|
-
date closes on pick; a range closes once both ends are set. Optional presets, form-ready.` }], types: {
|
|
6133
|
+
}` }], api: { entries: [{ kind: "component", name: "DatePicker", props: [{ name: "aria-describedby", type: "string", required: false, description: "Ids of hint or error text; Field fills it in." }, { name: "aria-invalid", type: "boolean", required: false, description: "Error styling; Field sets it from `error`." }, { name: "aria-label", type: "string", required: false, description: "Accessible name when there is no visible label." }, { name: "defaultValue", type: "Date | DateRange | null", required: false, description: "Day or range at first render (uncontrolled)." }, { name: "disabled", type: "boolean", required: false, description: "Blocks opening; the value isn't submitted." }, { name: "id", type: "string", required: false, description: "Pair with <Label htmlFor>, or let <Field> fill it in." }, { name: "isDisabled", type: "(date: Date) => boolean", required: false, description: "Extra rule, e.g. weekends off. Client-only (a function)." }, { name: "locale", type: "string", required: false, description: "BCP 47 locale for the button text and the calendar." }, { name: "max", type: "Date", required: false, description: "Latest selectable day." }, { name: "min", type: "Date", required: false, description: "Earliest selectable day." }, { name: "mode", type: '"single" | "range"', required: false, description: '"single": pick one day. "range": pick a start, then an end.' }, { name: "name", type: "string", required: false, description: "Submits ISO: `2026-10-21`, or `2026-10-03/2026-10-09` for a range." }, { name: "onValueChange", type: "((date: Date) => void) | ((range: DateRange) => void)", required: false, description: "Called with the picked day, or with the range once both ends are set." }, { name: "placeholder", type: "string", required: false, description: "Button text when nothing is picked." }, { name: "presets", type: "DatePickerPreset<Date>[] | DatePickerPreset<DateRange>[]", required: false, description: "Shortcuts beside the grid, e.g. Today, or Last 7 days for a range. Readers reach for these first." }, { name: "required", type: "boolean", required: false, description: "Form submit needs a date." }, { name: "value", type: "Date | DateRange | null", required: false, description: "Controlled day, or `{ from, to }` in range mode; null = nothing picked." }, { name: "weekStartsOn", type: "0 | 1 | 2 | 3 | 4 | 5 | 6", required: false, description: "0 is Sunday, 6 is Saturday. Default 1 (Monday, ISO)." }], description: `Field-styled button that opens a Calendar in a Popover, focused on the picked day. A single
|
|
6134
|
+
date closes on pick; a range closes once both ends are set. Optional presets, form-ready.` }], types: { DatePickerPreset: "{ label: string; value: Value }" } }, dependencies: ["lucide-react@1.63.0"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/calendar.json", "https://kznlabs.github.io/labsUI/r/field.json", "https://kznlabs.github.io/labsUI/r/form-value.json", "https://kznlabs.github.io/labsUI/r/input.json", "https://kznlabs.github.io/labsUI/r/popover.json", "https://kznlabs.github.io/labsUI/r/use-controllable-state.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/date-picker.tsx", type: "registry:ui", content: `"use client";
|
|
6031
6135
|
|
|
6032
6136
|
import { CalendarDays } from "lucide-react";
|
|
6033
6137
|
import { useRef, useState } from "react";
|
|
@@ -6039,7 +6143,7 @@ import { FormValue } from "@/lib/form-value";
|
|
|
6039
6143
|
import { useControllableState } from "@/lib/use-controllable-state";
|
|
6040
6144
|
import { cn } from "@/lib/utils";
|
|
6041
6145
|
|
|
6042
|
-
type
|
|
6146
|
+
export type DatePickerPreset<Value> = { label: string; value: Value };
|
|
6043
6147
|
|
|
6044
6148
|
type Base = {
|
|
6045
6149
|
/** Button text when nothing is picked. */
|
|
@@ -6054,11 +6158,11 @@ type Base = {
|
|
|
6054
6158
|
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
6055
6159
|
/** BCP 47 locale for the button text and the calendar. */
|
|
6056
6160
|
locale?: string;
|
|
6057
|
-
/** Submits ISO
|
|
6161
|
+
/** Submits ISO: \`2026-10-21\`, or \`2026-10-03/2026-10-09\` for a range. */
|
|
6058
6162
|
name?: string;
|
|
6059
6163
|
/** Pair with <Label htmlFor>, or let <Field> fill it in. */
|
|
6060
6164
|
id?: string;
|
|
6061
|
-
/** Blocks opening. */
|
|
6165
|
+
/** Blocks opening; the value isn't submitted. */
|
|
6062
6166
|
disabled?: boolean;
|
|
6063
6167
|
/** Form submit needs a date. */
|
|
6064
6168
|
required?: boolean;
|
|
@@ -6081,7 +6185,7 @@ type SingleProps = Base & {
|
|
|
6081
6185
|
/** Called with the picked day, or with the range once both ends are set. */
|
|
6082
6186
|
onValueChange?: (date: Date) => void;
|
|
6083
6187
|
/** Shortcuts beside the grid, e.g. Today, or Last 7 days for a range. Readers reach for these first. */
|
|
6084
|
-
presets?:
|
|
6188
|
+
presets?: DatePickerPreset<Date>[];
|
|
6085
6189
|
};
|
|
6086
6190
|
|
|
6087
6191
|
type RangeProps = Base & {
|
|
@@ -6089,7 +6193,7 @@ type RangeProps = Base & {
|
|
|
6089
6193
|
value?: DateRange | null;
|
|
6090
6194
|
defaultValue?: DateRange | null;
|
|
6091
6195
|
onValueChange?: (range: DateRange) => void;
|
|
6092
|
-
presets?:
|
|
6196
|
+
presets?: DatePickerPreset<DateRange>[];
|
|
6093
6197
|
};
|
|
6094
6198
|
|
|
6095
6199
|
type DatePickerProps = SingleProps | RangeProps;
|
|
@@ -6168,8 +6272,10 @@ export function DatePicker(allProps: DatePickerProps) {
|
|
|
6168
6272
|
aria-invalid={trigger["aria-invalid"] || (invalid && !iso) || undefined}
|
|
6169
6273
|
>
|
|
6170
6274
|
<CalendarDays />
|
|
6275
|
+
|
|
6171
6276
|
<span className="truncate">{label ?? placeholder}</span>
|
|
6172
6277
|
</PopoverTrigger>
|
|
6278
|
+
|
|
6173
6279
|
<PopoverContent align="start" className="flex w-auto flex-col gap-3 p-3 sm:flex-row">
|
|
6174
6280
|
{presets && presets.length > 0 && (
|
|
6175
6281
|
// No room beside the calendar on phones.
|
|
@@ -6193,6 +6299,7 @@ export function DatePicker(allProps: DatePickerProps) {
|
|
|
6193
6299
|
))}
|
|
6194
6300
|
</ul>
|
|
6195
6301
|
)}
|
|
6302
|
+
|
|
6196
6303
|
{range ? (
|
|
6197
6304
|
<Calendar key={shown} mode="range" {...shared} value={span} onValueChange={commit} />
|
|
6198
6305
|
) : (
|
|
@@ -6205,10 +6312,12 @@ export function DatePicker(allProps: DatePickerProps) {
|
|
|
6205
6312
|
)}
|
|
6206
6313
|
</PopoverContent>
|
|
6207
6314
|
</Popover>
|
|
6315
|
+
|
|
6208
6316
|
<FormValue
|
|
6209
6317
|
name={name}
|
|
6210
6318
|
value={iso}
|
|
6211
|
-
required={required
|
|
6319
|
+
required={required}
|
|
6320
|
+
disabled={trigger.disabled}
|
|
6212
6321
|
anchor={anchor}
|
|
6213
6322
|
onInvalid={() => setInvalid(true)}
|
|
6214
6323
|
/>
|
|
@@ -6240,8 +6349,8 @@ export function EditProfile() {
|
|
|
6240
6349
|
</DialogContent>
|
|
6241
6350
|
</Dialog>
|
|
6242
6351
|
);
|
|
6243
|
-
}` }], api: { entries: [{ kind: "function", name: "useDialog", signature: "useDialog() =>
|
|
6244
|
-
lock page scroll, close on Esc (topmost modal only). Attach the returned ref to the panel.` }, { kind: "component", name: "DialogContent", props: [{ name: "children", type: "ReactNode", required: true, description: "Header, body and footer." }, { name: "alert", type: "boolean", required: false, default: "false", description: 'Confirm-a-destructive-action mode: role="alertdialog", backdrop click does not close.' }, { name: "showClose", type: "boolean", required: false, default: "!alert", description: "Close button in the corner. Off by default for `alert` dialogs." }], description: "Portal + blurred backdrop + spring scale-in. Esc/backdrop close, focus trap, scroll lock." }, { kind: "component", name: "DialogHeader", props: [], extends: "<div>" }, { kind: "component", name: "DialogFooter", props: [], extends: "<div>" }, { kind: "component", name: "DialogTitle", props: [], extends: "<h2>" }, { kind: "component", name: "DialogDescription", props: [], extends: "<p>" }], types: {
|
|
6352
|
+
}` }], api: { entries: [{ kind: "function", name: "useDialog", signature: "useDialog() => DialogState" }, { kind: "component", name: "Dialog", props: [{ name: "children", type: "ReactNode", required: true, description: "`DialogTrigger` and `DialogContent`." }, { name: "defaultOpen", type: "boolean", required: false, default: "false", description: "Open at first render (uncontrolled)." }, { name: "onOpenChange", type: "(open: boolean) => void", required: false, description: "Called on the trigger, Esc, backdrop click or `DialogClose`." }, { name: "open", type: "boolean", required: false, description: "Controlled state; pair with `onOpenChange`." }], description: "State root. Controlled (`open` + `onOpenChange`) or uncontrolled." }, { kind: "component", name: "DialogTrigger", props: [{ name: "children", type: "ReactNode", required: false, description: "Label; its width is reserved so loading and success never resize the button." }, { name: "confirmLabel", type: "ReactNode", required: false, description: "Two-step action: first click swaps the label to this, second click runs `onClick`." }, { name: "confirmTimeout", type: "number", required: false, description: "Ms before an armed confirm step resets. Also resets on blur or Esc." }, { name: "loading", type: "boolean", required: false, description: "Shows a spinner before the label and ignores clicks and Enter, so a form can't submit twice. Stays focusable." }, { name: "loadingLabel", type: "ReactNode", required: false, description: "Label shown while loading, with the spinner. Defaults to `children`." }, { name: "size", type: '"default" | "sm" | "lg" | "icon"', required: false, description: 'Height and padding; "icon" is square, so give it an `aria-label`.' }, { name: "status", type: "ButtonStatus", required: false, description: "In-place feedback: `loading` spins, `success` swaps the label to `successLabel`. Both ignore clicks and Enter, so a form can't submit twice." }, { name: "successLabel", type: "ReactNode", required: false, description: 'Text shown on `status="success"`, with a check icon. Defaults to "Done".' }, { name: "swap", type: "SwapDirection", required: false, description: 'Where the old label slides out to on a swap. Default "left".' }, { name: "variant", type: '"default" | "secondary" | "outline" | "ghost" | "destructive"', required: false, description: "Fill and emphasis: one primary action per view, `destructive` for irreversible ones." }], extends: "Button" }, { kind: "component", name: "DialogClose", props: [{ name: "children", type: "ReactNode", required: false, description: "Label; its width is reserved so loading and success never resize the button." }, { name: "confirmLabel", type: "ReactNode", required: false, description: "Two-step action: first click swaps the label to this, second click runs `onClick`." }, { name: "confirmTimeout", type: "number", required: false, description: "Ms before an armed confirm step resets. Also resets on blur or Esc." }, { name: "loading", type: "boolean", required: false, description: "Shows a spinner before the label and ignores clicks and Enter, so a form can't submit twice. Stays focusable." }, { name: "loadingLabel", type: "ReactNode", required: false, description: "Label shown while loading, with the spinner. Defaults to `children`." }, { name: "size", type: '"default" | "sm" | "lg" | "icon"', required: false, description: 'Height and padding; "icon" is square, so give it an `aria-label`.' }, { name: "status", type: "ButtonStatus", required: false, description: "In-place feedback: `loading` spins, `success` swaps the label to `successLabel`. Both ignore clicks and Enter, so a form can't submit twice." }, { name: "successLabel", type: "ReactNode", required: false, description: 'Text shown on `status="success"`, with a check icon. Defaults to "Done".' }, { name: "swap", type: "SwapDirection", required: false, description: 'Where the old label slides out to on a swap. Default "left".' }, { name: "variant", type: '"default" | "secondary" | "outline" | "ghost" | "destructive"', required: false, default: '"outline"', description: "Fill and emphasis: one primary action per view, `destructive` for irreversible ones." }], extends: "Button" }, { kind: "function", name: "useModal", signature: "useModal() => RefObject<HTMLDivElement | null>", description: `Modal behavior shared by Dialog and Drawer: focus first field, trap Tab, restore focus,
|
|
6353
|
+
lock page scroll, close on Esc (topmost modal only). Attach the returned ref to the panel.` }, { kind: "component", name: "DialogContent", props: [{ name: "children", type: "ReactNode", required: true, description: "Header, body and footer." }, { name: "alert", type: "boolean", required: false, default: "false", description: 'Confirm-a-destructive-action mode: role="alertdialog", backdrop click does not close.' }, { name: "showClose", type: "boolean", required: false, default: "!alert", description: "Close button in the corner. Off by default for `alert` dialogs." }], description: "Portal + blurred backdrop + spring scale-in. Esc/backdrop close, focus trap, scroll lock." }, { kind: "component", name: "DialogHeader", props: [], extends: "<div>" }, { kind: "component", name: "DialogFooter", props: [], extends: "<div>" }, { kind: "component", name: "DialogTitle", props: [], extends: "<h2>" }, { kind: "component", name: "DialogDescription", props: [], extends: "<p>" }], types: { DialogState: "{ open: boolean; setOpen: (open: boolean) => void; titleId: string; descId: string; }" } }, 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/platform.json", "https://kznlabs.github.io/labsUI/r/use-controllable-state.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/dialog.tsx", type: "registry:ui", content: `"use client";
|
|
6245
6354
|
|
|
6246
6355
|
import { X } from "lucide-react";
|
|
6247
6356
|
import { AnimatePresence, motion, useIsPresent } from "motion/react";
|
|
@@ -6261,9 +6370,14 @@ import { useControllableState } from "@/lib/use-controllable-state";
|
|
|
6261
6370
|
import { cn, spring } from "@/lib/utils";
|
|
6262
6371
|
import { Button } from "./button";
|
|
6263
6372
|
|
|
6264
|
-
type
|
|
6373
|
+
export type DialogState = {
|
|
6374
|
+
open: boolean;
|
|
6375
|
+
setOpen: (open: boolean) => void;
|
|
6376
|
+
titleId: string;
|
|
6377
|
+
descId: string;
|
|
6378
|
+
};
|
|
6265
6379
|
|
|
6266
|
-
const DialogContext = createContext<
|
|
6380
|
+
const DialogContext = createContext<DialogState | null>(null);
|
|
6267
6381
|
|
|
6268
6382
|
export function useDialog() {
|
|
6269
6383
|
const ctx = use(DialogContext);
|
|
@@ -6435,6 +6549,7 @@ export function DialogContent({
|
|
|
6435
6549
|
transition={spring}
|
|
6436
6550
|
onClick={alert ? undefined : () => setOpen(false)}
|
|
6437
6551
|
/>
|
|
6552
|
+
|
|
6438
6553
|
<motion.div
|
|
6439
6554
|
ref={ref}
|
|
6440
6555
|
role={alert ? "alertdialog" : "dialog"}
|
|
@@ -6452,6 +6567,7 @@ export function DialogContent({
|
|
|
6452
6567
|
)}
|
|
6453
6568
|
>
|
|
6454
6569
|
{children}
|
|
6570
|
+
|
|
6455
6571
|
{showClose && (
|
|
6456
6572
|
<button
|
|
6457
6573
|
type="button"
|
|
@@ -6459,6 +6575,7 @@ export function DialogContent({
|
|
|
6459
6575
|
className="absolute top-4 right-4 cursor-pointer rounded-md p-1 text-muted-foreground outline-none transition-colors hover:bg-accent hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
|
6460
6576
|
>
|
|
6461
6577
|
<X className="size-4" aria-hidden="true" />
|
|
6578
|
+
|
|
6462
6579
|
<span className="sr-only">Close</span>
|
|
6463
6580
|
</button>
|
|
6464
6581
|
)}
|
|
@@ -6540,8 +6657,8 @@ export function CartDrawer({ items }: { items: string[] }) {
|
|
|
6540
6657
|
</DrawerContent>
|
|
6541
6658
|
</Drawer>
|
|
6542
6659
|
);
|
|
6543
|
-
}` }], api: { entries: [{ kind: "component", name: "Drawer", props: [{ name: "children", type: "ReactNode", required: true, description: "`DialogTrigger` and `DialogContent`." }, { name: "defaultOpen", type: "boolean", required: false, default: "false", description: "Open at first render (uncontrolled)." }, { name: "onOpenChange", type: "(open: boolean) => void", required: false, description: "Called on the trigger, Esc, backdrop click or `DialogClose`." }, { name: "open", type: "boolean", required: false, description: "Controlled state; pair with `onOpenChange`." }] }, { kind: "component", name: "DrawerTrigger", props: [{ name: "children", type: "ReactNode", required: false, description: "Label; its width is reserved so loading and success never resize the button." }, { name: "confirmLabel", type: "ReactNode", required: false, description: "Two-step action: first click swaps the label to this, second click runs `onClick`." }, { name: "confirmTimeout", type: "number", required: false, description: "Ms before an armed confirm step resets. Also resets on blur or Esc." }, { name: "loading", type: "boolean", required: false, description: "Shows a spinner before the label and ignores clicks and Enter, so a form can't submit twice. Stays focusable." }, { name: "loadingLabel", type: "ReactNode", required: false, description: "Label shown while loading, with the spinner. Defaults to `children`." }, { name: "size", type: '"default" | "sm" | "lg" | "icon"', required: false, description: 'Height and padding; "icon" is square, so give it an `aria-label`.' }, { name: "status", type: "ButtonStatus", required: false, description: "In-place feedback: `loading` spins, `success` swaps the label to `successLabel`. Both ignore clicks and Enter, so a form can't submit twice." }, { name: "successLabel", type: "ReactNode", required: false, description: 'Text shown on `status="success"`, with a check icon. Defaults to "Done".' }, { name: "swap", type: "SwapDirection", required: false, description: 'Where the old label slides out to on a swap. Default "left".' }, { name: "variant", type: '"default" | "secondary" | "outline" | "ghost" | "destructive"', required: false, description: "Fill and emphasis: one primary action per view, `destructive` for irreversible ones." }], extends: "Button" }, { kind: "component", name: "DrawerClose", props: [{ name: "children", type: "ReactNode", required: false, description: "Label; its width is reserved so loading and success never resize the button." }, { name: "confirmLabel", type: "ReactNode", required: false, description: "Two-step action: first click swaps the label to this, second click runs `onClick`." }, { name: "confirmTimeout", type: "number", required: false, description: "Ms before an armed confirm step resets. Also resets on blur or Esc." }, { name: "loading", type: "boolean", required: false, description: "Shows a spinner before the label and ignores clicks and Enter, so a form can't submit twice. Stays focusable." }, { name: "loadingLabel", type: "ReactNode", required: false, description: "Label shown while loading, with the spinner. Defaults to `children`." }, { name: "size", type: '"default" | "sm" | "lg" | "icon"', required: false, description: 'Height and padding; "icon" is square, so give it an `aria-label`.' }, { name: "status", type: "ButtonStatus", required: false, description: "In-place feedback: `loading` spins, `success` swaps the label to `successLabel`. Both ignore clicks and Enter, so a form can't submit twice." }, { name: "successLabel", type: "ReactNode", required: false, description: 'Text shown on `status="success"`, with a check icon. Defaults to "Done".' }, { name: "swap", type: "SwapDirection", required: false, description: 'Where the old label slides out to on a swap. Default "left".' }, { name: "variant", type: '"default" | "secondary" | "outline" | "ghost" | "destructive"', required: false, default: '"outline"', description: "Fill and emphasis: one primary action per view, `destructive` for irreversible ones." }], extends: "Button" }, { kind: "component", name: "DrawerTitle", props: [], extends: "<h2>" }, { kind: "component", name: "DrawerDescription", props: [], extends: "<p>" }, { kind: "component", name: "DrawerContent", props: [{ name: "children", type: "ReactNode", required: true, description: "Header, body and footer." }, { name: "showClose", type: "boolean", required: false, default: 'side === "left" || side === "right"', description: "Close button in the corner. On by default for side drawers." }, { name: "side", type: "
|
|
6544
|
-
drag from anywhere, top/bottom drawers from the handle or header so their content can scroll.` }, { kind: "component", name: "DrawerHeader", props: [], extends: "<div>", description: "Also a drag handle on top/bottom drawers." }, { kind: "component", name: "DrawerBody", props: [], extends: "<div>", description: "Scrolls when content is taller than the drawer." }, { kind: "component", name: "DrawerFooter", props: [], extends: "<div>" }], types: {
|
|
6660
|
+
}` }], api: { entries: [{ kind: "component", name: "Drawer", props: [{ name: "children", type: "ReactNode", required: true, description: "`DialogTrigger` and `DialogContent`." }, { name: "defaultOpen", type: "boolean", required: false, default: "false", description: "Open at first render (uncontrolled)." }, { name: "onOpenChange", type: "(open: boolean) => void", required: false, description: "Called on the trigger, Esc, backdrop click or `DialogClose`." }, { name: "open", type: "boolean", required: false, description: "Controlled state; pair with `onOpenChange`." }] }, { kind: "component", name: "DrawerTrigger", props: [{ name: "children", type: "ReactNode", required: false, description: "Label; its width is reserved so loading and success never resize the button." }, { name: "confirmLabel", type: "ReactNode", required: false, description: "Two-step action: first click swaps the label to this, second click runs `onClick`." }, { name: "confirmTimeout", type: "number", required: false, description: "Ms before an armed confirm step resets. Also resets on blur or Esc." }, { name: "loading", type: "boolean", required: false, description: "Shows a spinner before the label and ignores clicks and Enter, so a form can't submit twice. Stays focusable." }, { name: "loadingLabel", type: "ReactNode", required: false, description: "Label shown while loading, with the spinner. Defaults to `children`." }, { name: "size", type: '"default" | "sm" | "lg" | "icon"', required: false, description: 'Height and padding; "icon" is square, so give it an `aria-label`.' }, { name: "status", type: "ButtonStatus", required: false, description: "In-place feedback: `loading` spins, `success` swaps the label to `successLabel`. Both ignore clicks and Enter, so a form can't submit twice." }, { name: "successLabel", type: "ReactNode", required: false, description: 'Text shown on `status="success"`, with a check icon. Defaults to "Done".' }, { name: "swap", type: "SwapDirection", required: false, description: 'Where the old label slides out to on a swap. Default "left".' }, { name: "variant", type: '"default" | "secondary" | "outline" | "ghost" | "destructive"', required: false, description: "Fill and emphasis: one primary action per view, `destructive` for irreversible ones." }], extends: "Button" }, { kind: "component", name: "DrawerClose", props: [{ name: "children", type: "ReactNode", required: false, description: "Label; its width is reserved so loading and success never resize the button." }, { name: "confirmLabel", type: "ReactNode", required: false, description: "Two-step action: first click swaps the label to this, second click runs `onClick`." }, { name: "confirmTimeout", type: "number", required: false, description: "Ms before an armed confirm step resets. Also resets on blur or Esc." }, { name: "loading", type: "boolean", required: false, description: "Shows a spinner before the label and ignores clicks and Enter, so a form can't submit twice. Stays focusable." }, { name: "loadingLabel", type: "ReactNode", required: false, description: "Label shown while loading, with the spinner. Defaults to `children`." }, { name: "size", type: '"default" | "sm" | "lg" | "icon"', required: false, description: 'Height and padding; "icon" is square, so give it an `aria-label`.' }, { name: "status", type: "ButtonStatus", required: false, description: "In-place feedback: `loading` spins, `success` swaps the label to `successLabel`. Both ignore clicks and Enter, so a form can't submit twice." }, { name: "successLabel", type: "ReactNode", required: false, description: 'Text shown on `status="success"`, with a check icon. Defaults to "Done".' }, { name: "swap", type: "SwapDirection", required: false, description: 'Where the old label slides out to on a swap. Default "left".' }, { name: "variant", type: '"default" | "secondary" | "outline" | "ghost" | "destructive"', required: false, default: '"outline"', description: "Fill and emphasis: one primary action per view, `destructive` for irreversible ones." }], extends: "Button" }, { kind: "component", name: "DrawerTitle", props: [], extends: "<h2>" }, { kind: "component", name: "DrawerDescription", props: [], extends: "<p>" }, { kind: "component", name: "DrawerContent", props: [{ name: "children", type: "ReactNode", required: true, description: "Header, body and footer." }, { name: "showClose", type: "boolean", required: false, default: 'side === "left" || side === "right"', description: "Close button in the corner. On by default for side drawers." }, { name: "side", type: "DrawerSide", required: false, default: '"right"', description: "Edge it slides in from." }], description: `Panel that slides in from an edge on a spring. Swipe toward the edge to dismiss: side drawers
|
|
6661
|
+
drag from anywhere, top/bottom drawers from the handle or header so their content can scroll.` }, { kind: "component", name: "DrawerHeader", props: [], extends: "<div>", description: "Also a drag handle on top/bottom drawers." }, { kind: "component", name: "DrawerBody", props: [], extends: "<div>", description: "Scrolls when content is taller than the drawer." }, { kind: "component", name: "DrawerFooter", props: [], extends: "<div>" }], types: { DrawerSide: '"left" | "right" | "top" | "bottom"' } }, dependencies: ["lucide-react@1.63.0", "motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/dialog.json", "https://kznlabs.github.io/labsUI/r/platform.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/drawer.tsx", type: "registry:ui", content: `"use client";
|
|
6545
6662
|
|
|
6546
6663
|
import { X } from "lucide-react";
|
|
6547
6664
|
import { AnimatePresence, motion, type PanInfo, useDragControls, useIsPresent } from "motion/react";
|
|
@@ -6567,9 +6684,9 @@ export const DrawerClose = DialogClose;
|
|
|
6567
6684
|
export const DrawerTitle = DialogTitle;
|
|
6568
6685
|
export const DrawerDescription = DialogDescription;
|
|
6569
6686
|
|
|
6570
|
-
type
|
|
6687
|
+
export type DrawerSide = "left" | "right" | "top" | "bottom";
|
|
6571
6688
|
|
|
6572
|
-
const offscreen: Record<
|
|
6689
|
+
const offscreen: Record<DrawerSide, { x?: string; y?: string }> = {
|
|
6573
6690
|
left: { x: "-100%" },
|
|
6574
6691
|
right: { x: "100%" },
|
|
6575
6692
|
top: { y: "-100%" },
|
|
@@ -6578,7 +6695,7 @@ const offscreen: Record<Side, { x?: string; y?: string }> = {
|
|
|
6578
6695
|
|
|
6579
6696
|
// The \`after:\` bleed paints past the screen edge, so overshoot or dragging inward never shows a
|
|
6580
6697
|
// gap.
|
|
6581
|
-
const placement: Record<
|
|
6698
|
+
const placement: Record<DrawerSide, string> = {
|
|
6582
6699
|
left: "inset-y-0 left-0 h-full w-[min(20rem,85vw)] border-r after:right-full after:inset-y-0 after:w-[50vw]",
|
|
6583
6700
|
right:
|
|
6584
6701
|
"inset-y-0 right-0 h-full w-[min(24rem,90vw)] border-l after:left-full after:inset-y-0 after:w-[50vw]",
|
|
@@ -6602,7 +6719,7 @@ export function DrawerContent({
|
|
|
6602
6719
|
children,
|
|
6603
6720
|
}: {
|
|
6604
6721
|
/** Edge it slides in from. */
|
|
6605
|
-
side?:
|
|
6722
|
+
side?: DrawerSide;
|
|
6606
6723
|
/** Close button in the corner. On by default for side drawers. */
|
|
6607
6724
|
showClose?: boolean;
|
|
6608
6725
|
className?: string;
|
|
@@ -6651,6 +6768,7 @@ export function DrawerContent({
|
|
|
6651
6768
|
transition={spring}
|
|
6652
6769
|
onClick={() => setOpen(false)}
|
|
6653
6770
|
/>
|
|
6771
|
+
|
|
6654
6772
|
<motion.div
|
|
6655
6773
|
ref={ref}
|
|
6656
6774
|
role="dialog"
|
|
@@ -6684,8 +6802,11 @@ export function DrawerContent({
|
|
|
6684
6802
|
)}
|
|
6685
6803
|
>
|
|
6686
6804
|
{side === "bottom" && <Handle />}
|
|
6805
|
+
|
|
6687
6806
|
{children}
|
|
6807
|
+
|
|
6688
6808
|
{side === "top" && <Handle />}
|
|
6809
|
+
|
|
6689
6810
|
{showClose && (
|
|
6690
6811
|
<button
|
|
6691
6812
|
type="button"
|
|
@@ -6694,6 +6815,7 @@ export function DrawerContent({
|
|
|
6694
6815
|
className="absolute top-4 right-4 cursor-pointer rounded-md p-1 text-muted-foreground outline-none transition-colors hover:bg-accent hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
|
6695
6816
|
>
|
|
6696
6817
|
<X className="size-4" aria-hidden="true" />
|
|
6818
|
+
|
|
6697
6819
|
<span className="sr-only">Close</span>
|
|
6698
6820
|
</button>
|
|
6699
6821
|
)}
|
|
@@ -7082,6 +7204,7 @@ export function DropdownMenuItem({
|
|
|
7082
7204
|
)}
|
|
7083
7205
|
>
|
|
7084
7206
|
{children}
|
|
7207
|
+
|
|
7085
7208
|
<Shortcut>{shortcut}</Shortcut>
|
|
7086
7209
|
</div>
|
|
7087
7210
|
);
|
|
@@ -7112,7 +7235,9 @@ export function DropdownMenuCheckboxItem({
|
|
|
7112
7235
|
<span className="absolute left-2 flex size-4 items-center justify-center">
|
|
7113
7236
|
{checked && <Check className="!text-current" aria-hidden="true" />}
|
|
7114
7237
|
</span>
|
|
7238
|
+
|
|
7115
7239
|
{children}
|
|
7240
|
+
|
|
7116
7241
|
<Shortcut>{shortcut}</Shortcut>
|
|
7117
7242
|
</div>
|
|
7118
7243
|
);
|
|
@@ -7169,6 +7294,7 @@ export function DropdownMenuRadioItem({
|
|
|
7169
7294
|
<span className="absolute left-2 flex size-4 items-center justify-center">
|
|
7170
7295
|
{checked && <Circle className="!size-2 !text-current fill-current" aria-hidden="true" />}
|
|
7171
7296
|
</span>
|
|
7297
|
+
|
|
7172
7298
|
{children}
|
|
7173
7299
|
</div>
|
|
7174
7300
|
);
|
|
@@ -7240,12 +7366,15 @@ export function Empty({
|
|
|
7240
7366
|
{icon}
|
|
7241
7367
|
</span>
|
|
7242
7368
|
)}
|
|
7369
|
+
|
|
7243
7370
|
<div className="grid max-w-sm gap-1.5">
|
|
7244
7371
|
<p className="font-semibold text-base tracking-tight">{title}</p>
|
|
7372
|
+
|
|
7245
7373
|
{description && (
|
|
7246
7374
|
<p className="text-muted-foreground text-sm leading-relaxed">{description}</p>
|
|
7247
7375
|
)}
|
|
7248
7376
|
</div>
|
|
7377
|
+
|
|
7249
7378
|
{children && <div className="flex flex-wrap justify-center gap-2">{children}</div>}
|
|
7250
7379
|
</div>
|
|
7251
7380
|
);
|
|
@@ -7361,6 +7490,7 @@ export function Field({
|
|
|
7361
7490
|
const labelEl = label && (
|
|
7362
7491
|
<Label htmlFor={id} className={cn(disabled && "cursor-not-allowed opacity-50")}>
|
|
7363
7492
|
{label}
|
|
7493
|
+
|
|
7364
7494
|
{required && (
|
|
7365
7495
|
<span aria-hidden="true" className="text-destructive-ink">
|
|
7366
7496
|
*
|
|
@@ -7383,14 +7513,17 @@ export function Field({
|
|
|
7383
7513
|
{horizontal ? (
|
|
7384
7514
|
<div className="flex items-center gap-2.5">
|
|
7385
7515
|
{children}
|
|
7516
|
+
|
|
7386
7517
|
{labelEl}
|
|
7387
7518
|
</div>
|
|
7388
7519
|
) : (
|
|
7389
7520
|
<>
|
|
7390
7521
|
{labelEl}
|
|
7522
|
+
|
|
7391
7523
|
{children}
|
|
7392
7524
|
</>
|
|
7393
7525
|
)}
|
|
7526
|
+
|
|
7394
7527
|
{description && (
|
|
7395
7528
|
<p
|
|
7396
7529
|
id={descId}
|
|
@@ -7399,6 +7532,7 @@ export function Field({
|
|
|
7399
7532
|
{description}
|
|
7400
7533
|
</p>
|
|
7401
7534
|
)}
|
|
7535
|
+
|
|
7402
7536
|
{/* Persistent live region, so a new error is announced. */}
|
|
7403
7537
|
{/* -mt-2 cancels the grid gap while empty; the error brings its own pt-2. */}
|
|
7404
7538
|
<div aria-live="polite" className={cn("-mt-2", horizontal && "pl-6.5")}>
|
|
@@ -7422,7 +7556,7 @@ export function Field({
|
|
|
7422
7556
|
</FieldContext>
|
|
7423
7557
|
);
|
|
7424
7558
|
}
|
|
7425
|
-
` }] }, "form-value": { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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`.", api: { entries: [{ kind: "component", name: "FormValue", props: [{ name: "anchor", type: "RefObject<HTMLElement | null>", required: true, description: "The visible control that gets focus when the value is invalid." }, { name: "value", type: "string", required: true, description: 'The value to submit, "" when nothing is picked.' }, { name: "name", type: "string", required: false, description: "Form field name; without it the value only takes part in validation." }, { name: "onInvalid", type: "() => void", required: false, description: "Called when a submit finds the value invalid." }, { name: "required", type: "boolean", required: false, description: "An empty value blocks submit." }], 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`." }], types: {} }, dependencies: [], registryDependencies: [], files: [{ path: "lib/form-value.tsx", type: "registry:lib", content: `import type { RefObject } from "react";
|
|
7559
|
+
` }] }, "form-value": { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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`.", api: { entries: [{ kind: "component", name: "FormValue", props: [{ name: "anchor", type: "RefObject<HTMLElement | null>", required: true, description: "The visible control that gets focus when the value is invalid." }, { name: "value", type: "string", required: true, description: 'The value to submit, "" when nothing is picked.' }, { name: "disabled", type: "boolean", required: false, description: "Skips validation and leaves the value out of the submit, like a disabled input." }, { name: "name", type: "string", required: false, description: "Form field name; without it the value only takes part in validation." }, { name: "onInvalid", type: "() => void", required: false, description: "Called when a submit finds the value invalid." }, { name: "required", type: "boolean", required: false, description: "An empty value blocks submit." }], 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`." }], types: {} }, dependencies: [], registryDependencies: [], files: [{ path: "lib/form-value.tsx", type: "registry:lib", content: `import type { RefObject } from "react";
|
|
7426
7560
|
|
|
7427
7561
|
type FormValueProps = {
|
|
7428
7562
|
/** Form field name; without it the value only takes part in validation. */
|
|
@@ -7431,6 +7565,8 @@ type FormValueProps = {
|
|
|
7431
7565
|
value: string;
|
|
7432
7566
|
/** An empty value blocks submit. */
|
|
7433
7567
|
required?: boolean;
|
|
7568
|
+
/** Skips validation and leaves the value out of the submit, like a disabled input. */
|
|
7569
|
+
disabled?: boolean;
|
|
7434
7570
|
/** The visible control that gets focus when the value is invalid. */
|
|
7435
7571
|
anchor: RefObject<HTMLElement | null>;
|
|
7436
7572
|
/** Called when a submit finds the value invalid. */
|
|
@@ -7441,7 +7577,7 @@ type FormValueProps = {
|
|
|
7441
7577
|
* Submits a custom control's value and takes part in native validation, which a hidden input
|
|
7442
7578
|
* can't: an empty \`required\` value blocks submit, shows the browser's message and focuses \`anchor\`.
|
|
7443
7579
|
*/
|
|
7444
|
-
export function FormValue({ name, value, required, anchor, onInvalid }: FormValueProps) {
|
|
7580
|
+
export function FormValue({ name, value, required, disabled, anchor, onInvalid }: FormValueProps) {
|
|
7445
7581
|
if (!name && !required) return null;
|
|
7446
7582
|
|
|
7447
7583
|
return (
|
|
@@ -7451,6 +7587,7 @@ export function FormValue({ name, value, required, anchor, onInvalid }: FormValu
|
|
|
7451
7587
|
name={name}
|
|
7452
7588
|
value={value}
|
|
7453
7589
|
required={required}
|
|
7590
|
+
disabled={disabled}
|
|
7454
7591
|
onChange={() => undefined}
|
|
7455
7592
|
onInvalid={onInvalid}
|
|
7456
7593
|
onFocus={() => anchor.current?.focus()}
|
|
@@ -7619,6 +7756,7 @@ export function HoverCard({
|
|
|
7619
7756
|
}}
|
|
7620
7757
|
>
|
|
7621
7758
|
{children}
|
|
7759
|
+
|
|
7622
7760
|
{mounted &&
|
|
7623
7761
|
createPortal(
|
|
7624
7762
|
<AnimatePresence>
|
|
@@ -7651,7 +7789,7 @@ export function HoverCard({
|
|
|
7651
7789
|
</span>
|
|
7652
7790
|
);
|
|
7653
7791
|
}
|
|
7654
|
-
` }] }, input: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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.", docs: "https://kznlabs.github.io/labsUI/components/forms", usage: '<Label htmlFor="email">Email</Label><Input id="email" type="email" aria-invalid={err} />', examples: [{ name: "sign-up.tsx", code: `import { Input } from "@/components/ui/input";
|
|
7792
|
+
` }] }, input: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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.", 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";
|
|
7655
7793
|
import { Label } from "@/components/ui/label";
|
|
7656
7794
|
import { Switch } from "@/components/ui/switch";
|
|
7657
7795
|
import { Textarea } from "@/components/ui/textarea";
|
|
@@ -7926,6 +8064,7 @@ export function InputOTP({
|
|
|
7926
8064
|
className="h-px w-2.5 shrink-0 bg-muted-foreground/50"
|
|
7927
8065
|
/>
|
|
7928
8066
|
)}
|
|
8067
|
+
|
|
7929
8068
|
<div className="flex min-w-0 gap-1.5" aria-hidden="true">
|
|
7930
8069
|
{Array.from({ length: count }, (_, slot) => {
|
|
7931
8070
|
const i = starts[group] + slot;
|
|
@@ -7980,6 +8119,7 @@ export function InputOTP({
|
|
|
7980
8119
|
)
|
|
7981
8120
|
)}
|
|
7982
8121
|
</AnimatePresence>
|
|
8122
|
+
|
|
7983
8123
|
{isActive && !char && (
|
|
7984
8124
|
<span className="absolute h-4 w-px animate-caret-blink bg-foreground motion-reduce:animate-none" />
|
|
7985
8125
|
)}
|
|
@@ -7989,6 +8129,7 @@ export function InputOTP({
|
|
|
7989
8129
|
</div>
|
|
7990
8130
|
</Fragment>
|
|
7991
8131
|
))}
|
|
8132
|
+
|
|
7992
8133
|
<AnimatePresence>
|
|
7993
8134
|
{merge && (
|
|
7994
8135
|
// Slots fold into one bar of the same size: a finished field, not a new widget.
|
|
@@ -8007,6 +8148,7 @@ export function InputOTP({
|
|
|
8007
8148
|
transition={{ ...spring, stiffness: 120, delay: 0.15 }}
|
|
8008
8149
|
className="absolute inset-0 rounded-full border-2 border-success"
|
|
8009
8150
|
/>
|
|
8151
|
+
|
|
8010
8152
|
<motion.span
|
|
8011
8153
|
initial={{ scale: 0 }}
|
|
8012
8154
|
animate={{ scale: 1 }}
|
|
@@ -8027,6 +8169,7 @@ export function InputOTP({
|
|
|
8027
8169
|
</svg>
|
|
8028
8170
|
</motion.span>
|
|
8029
8171
|
</span>
|
|
8172
|
+
|
|
8030
8173
|
<motion.span
|
|
8031
8174
|
initial={{ opacity: 0, x: -6 }}
|
|
8032
8175
|
animate={{ opacity: 1, x: 0 }}
|
|
@@ -8038,9 +8181,11 @@ export function InputOTP({
|
|
|
8038
8181
|
</motion.div>
|
|
8039
8182
|
)}
|
|
8040
8183
|
</AnimatePresence>
|
|
8184
|
+
|
|
8041
8185
|
<span role="status" className="sr-only">
|
|
8042
8186
|
{verified ? verifiedLabel : null}
|
|
8043
8187
|
</span>
|
|
8188
|
+
|
|
8044
8189
|
<input
|
|
8045
8190
|
type="text"
|
|
8046
8191
|
autoComplete="one-time-code"
|
|
@@ -8117,7 +8262,7 @@ export function KbdGroup({
|
|
|
8117
8262
|
</kbd>
|
|
8118
8263
|
);
|
|
8119
8264
|
}
|
|
8120
|
-
` }] }, label: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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.", docs: "https://kznlabs.github.io/labsUI/components/forms", usage: '<Label htmlFor="email">Email</Label><Input id="email" type="email" aria-invalid={err} />', examples: [{ name: "sign-up.tsx", code: `import { Input } from "@/components/ui/input";
|
|
8265
|
+
` }] }, label: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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.", 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";
|
|
8121
8266
|
import { Label } from "@/components/ui/label";
|
|
8122
8267
|
import { Switch } from "@/components/ui/switch";
|
|
8123
8268
|
import { Textarea } from "@/components/ui/textarea";
|
|
@@ -8149,7 +8294,7 @@ export function Label({ className, ...props }: ComponentProps<"label">) {
|
|
|
8149
8294
|
}
|
|
8150
8295
|
` }] }, list: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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.", api: { entries: [{ kind: "function", name: "listKeyTarget", signature: 'listKeyTarget(items: readonly T[], index: number, event: Pick<KeyboardEvent, "ctrlKey" | "key">, { orientation, loop, homeEnd }: ListKeyOptions) => T | undefined', 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." }, { kind: "function", name: "scrollIntoList", signature: "scrollIntoList(box: HTMLElement, element: HTMLElement, pad?: number) => void", description: "Scrolls `box` just enough to show `el`, without moving the page the way scrollIntoView can." }], types: { ListKeyOptions: '{ orientation: "horizontal" | "vertical"; loop?: boolean; homeEnd?: boolean | "ctrl"; }' } }, dependencies: [], registryDependencies: ["https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "lib/list.ts", type: "registry:lib", content: `import { clamp } from "@/lib/utils";
|
|
8151
8296
|
|
|
8152
|
-
type ListKeyOptions = {
|
|
8297
|
+
export type ListKeyOptions = {
|
|
8153
8298
|
/** Which arrows move: Left/Right for "horizontal", Up/Down for "vertical". */
|
|
8154
8299
|
orientation: "horizontal" | "vertical";
|
|
8155
8300
|
/** Wrap past the last item to the first and back. Default true. */
|
|
@@ -8485,6 +8630,7 @@ export function Menubar({ className, children, ...props }: ComponentProps<"div">
|
|
|
8485
8630
|
className="-z-10 absolute inset-y-1 left-0 rounded-md bg-foreground/[0.08]"
|
|
8486
8631
|
/>
|
|
8487
8632
|
)}
|
|
8633
|
+
|
|
8488
8634
|
{children}
|
|
8489
8635
|
</div>
|
|
8490
8636
|
</BarContext>
|
|
@@ -8586,7 +8732,7 @@ export function MenubarTrigger({
|
|
|
8586
8732
|
/>
|
|
8587
8733
|
);
|
|
8588
8734
|
}
|
|
8589
|
-
` }] }, "message-bubble": { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", name: "message-bubble", type: "registry:ui", title: "Message Bubble", description: "User/assistant chat bubbles with avatar slot, streaming dots and copyable code blocks.", docs: "https://kznlabs.github.io/labsUI/components/chat", usage: '<MessageBubble from="assistant" avatar={<Avatar
|
|
8735
|
+
` }] }, "message-bubble": { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", name: "message-bubble", type: "registry:ui", title: "Message Bubble", description: "User/assistant chat bubbles with avatar slot, streaming dots and copyable code blocks.", docs: "https://kznlabs.github.io/labsUI/components/chat", usage: '<MessageBubble from="assistant" avatar={<Avatar src={bot} alt="Assistant" />} streaming>…<Code code={src} language="tsx" /></MessageBubble>', examples: [{ name: "message.tsx", code: `import { Avatar } from "@/components/ui/avatar";
|
|
8590
8736
|
import { Code } from "@/components/ui/code";
|
|
8591
8737
|
import { MessageBubble } from "@/components/ui/message-bubble";
|
|
8592
8738
|
export function Reply({ code }: { code: string }) {
|
|
@@ -8644,13 +8790,16 @@ export function MessageBubble({
|
|
|
8644
8790
|
className={cn("flex w-full gap-3", user && "flex-row-reverse", className)}
|
|
8645
8791
|
>
|
|
8646
8792
|
{avatar && <div className="shrink-0 pt-5">{avatar}</div>}
|
|
8793
|
+
|
|
8647
8794
|
<div className={cn("flex min-w-0 max-w-[85%] flex-col gap-1", user && "items-end")}>
|
|
8648
8795
|
{(name || time) && (
|
|
8649
8796
|
<header className="flex items-center gap-2 px-1 text-muted-foreground text-xs">
|
|
8650
8797
|
{name && <span className="font-medium text-foreground">{name}</span>}
|
|
8798
|
+
|
|
8651
8799
|
{time && <time>{time}</time>}
|
|
8652
8800
|
</header>
|
|
8653
8801
|
)}
|
|
8802
|
+
|
|
8654
8803
|
<div
|
|
8655
8804
|
className={cn(
|
|
8656
8805
|
"min-w-0 rounded-2xl px-4 py-2.5 text-sm leading-relaxed",
|
|
@@ -8661,6 +8810,7 @@ export function MessageBubble({
|
|
|
8661
8810
|
)}
|
|
8662
8811
|
>
|
|
8663
8812
|
{children}
|
|
8813
|
+
|
|
8664
8814
|
{streaming && <TypingDots className={children ? "mt-2" : "py-1.5"} />}
|
|
8665
8815
|
</div>
|
|
8666
8816
|
</div>
|
|
@@ -8705,14 +8855,14 @@ export function Storage() {
|
|
|
8705
8855
|
]}
|
|
8706
8856
|
/>
|
|
8707
8857
|
);
|
|
8708
|
-
}` }], api: { entries: [{ kind: "component", name: "Meter", props: [{ name: "high", type: "number", required: false, default: "max", description: "Above this the value is high." }, { name: "label", type: "ReactNode", required: false, description: "Visible name above the bar; also the accessible name." }, { name: "low", type: "number", required: false, default: "min", description: "<meter> thresholds: below `low` is low, above `high` is high." }, { name: "max", type: "number", required: false, default: "100", description: "Upper bound." }, { name: "min", type: "number", required: false, default: "0", description: "Lower bound." }, { name: "optimum", type: "number", required: false, default: "(min + max) / 2", description: "Where good is: below low, between, or above high. Decides the color, like <meter>." }, { name: "segments", type: "
|
|
8858
|
+
}` }], api: { entries: [{ kind: "component", name: "Meter", props: [{ name: "high", type: "number", required: false, default: "max", description: "Above this the value is high." }, { name: "label", type: "ReactNode", required: false, description: "Visible name above the bar; also the accessible name." }, { name: "low", type: "number", required: false, default: "min", description: "<meter> thresholds: below `low` is low, above `high` is high." }, { name: "max", type: "number", required: false, default: "100", description: "Upper bound." }, { name: "min", type: "number", required: false, default: "0", description: "Lower bound." }, { name: "optimum", type: "number", required: false, default: "(min + max) / 2", description: "Where good is: below low, between, or above high. Decides the color, like <meter>." }, { name: "segments", type: "MeterSegment[]", required: false, description: "Stacked parts with a legend (like a storage bar). Overrides the threshold color." }, { name: "value", type: "number", required: false, description: "Total used. With `segments`, defaults to their sum." }, { name: "valueText", type: "string", required: false, description: 'Text on the right and the accessible value, e.g. "48 GB of 100 GB".' }], description: `A measurement within a known range (disk, quota, server load, password strength), not a
|
|
8709
8859
|
task's progress (use Progress). Colors follow <meter>: green in the optimum region, orange one
|
|
8710
|
-
region away, red two away.` }], types: {
|
|
8860
|
+
region away, red two away.` }], types: { MeterSegment: "{ label: string; value: number; className?: string; }" } }, dependencies: [], registryDependencies: ["https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/meter.tsx", type: "registry:ui", content: `import type { ReactNode } from "react";
|
|
8711
8861
|
import { clamp, cn } from "@/lib/utils";
|
|
8712
8862
|
|
|
8713
8863
|
/* Server-safe (no "use client"). */
|
|
8714
8864
|
|
|
8715
|
-
type
|
|
8865
|
+
export type MeterSegment = {
|
|
8716
8866
|
/** Legend text for this part. */
|
|
8717
8867
|
label: string;
|
|
8718
8868
|
/** Amount this part uses. */
|
|
@@ -8735,7 +8885,7 @@ type MeterProps = {
|
|
|
8735
8885
|
/** Where good is: below low, between, or above high. Decides the color, like <meter>. */
|
|
8736
8886
|
optimum?: number;
|
|
8737
8887
|
/** Stacked parts with a legend (like a storage bar). Overrides the threshold color. */
|
|
8738
|
-
segments?:
|
|
8888
|
+
segments?: MeterSegment[];
|
|
8739
8889
|
/** Visible name above the bar; also the accessible name. */
|
|
8740
8890
|
label?: ReactNode;
|
|
8741
8891
|
/** Text on the right and the accessible value, e.g. "48 GB of 100 GB". */
|
|
@@ -8773,11 +8923,13 @@ export function Meter({
|
|
|
8773
8923
|
{(label || valueText) && (
|
|
8774
8924
|
<div className="flex items-baseline justify-between gap-3 text-sm">
|
|
8775
8925
|
{label && <span className="font-medium">{label}</span>}
|
|
8926
|
+
|
|
8776
8927
|
{valueText && (
|
|
8777
8928
|
<span className="ml-auto text-muted-foreground text-xs tabular-nums">{valueText}</span>
|
|
8778
8929
|
)}
|
|
8779
8930
|
</div>
|
|
8780
8931
|
)}
|
|
8932
|
+
|
|
8781
8933
|
{/* The real <meter> carries the semantics; the drawn bar below is decoration. */}
|
|
8782
8934
|
<meter
|
|
8783
8935
|
className="sr-only"
|
|
@@ -8791,6 +8943,7 @@ export function Meter({
|
|
|
8791
8943
|
>
|
|
8792
8944
|
{valueText ?? value}
|
|
8793
8945
|
</meter>
|
|
8946
|
+
|
|
8794
8947
|
<div
|
|
8795
8948
|
aria-hidden="true"
|
|
8796
8949
|
className="flex h-2 w-full gap-px overflow-hidden rounded-full bg-foreground/10"
|
|
@@ -8810,6 +8963,7 @@ export function Meter({
|
|
|
8810
8963
|
<span style={{ width: pct(value) }} className={cn("h-full rounded-full", tone)} />
|
|
8811
8964
|
)}
|
|
8812
8965
|
</div>
|
|
8966
|
+
|
|
8813
8967
|
{segments && (
|
|
8814
8968
|
<ul className="flex flex-wrap gap-x-4 gap-y-1 text-muted-foreground text-xs">
|
|
8815
8969
|
{segments.map((segment, i) => (
|
|
@@ -8821,6 +8975,7 @@ export function Meter({
|
|
|
8821
8975
|
segment.className ?? palette[i % palette.length],
|
|
8822
8976
|
)}
|
|
8823
8977
|
/>
|
|
8978
|
+
|
|
8824
8979
|
{segment.label}
|
|
8825
8980
|
</li>
|
|
8826
8981
|
))}
|
|
@@ -8887,6 +9042,7 @@ export function NativeSelect({ className, children, ...rest }: ComponentProps<"s
|
|
|
8887
9042
|
>
|
|
8888
9043
|
{children}
|
|
8889
9044
|
</select>
|
|
9045
|
+
|
|
8890
9046
|
<ChevronsUpDown
|
|
8891
9047
|
aria-hidden="true"
|
|
8892
9048
|
className="-translate-y-1/2 pointer-events-none absolute top-1/2 right-3 size-4 text-muted-foreground"
|
|
@@ -9246,8 +9402,10 @@ export function NavigationMenu({
|
|
|
9246
9402
|
className="-z-10 absolute inset-y-0 left-0 rounded-lg bg-foreground/[0.06]"
|
|
9247
9403
|
/>
|
|
9248
9404
|
)}
|
|
9405
|
+
|
|
9249
9406
|
{children}
|
|
9250
9407
|
</div>
|
|
9408
|
+
|
|
9251
9409
|
<motion.div
|
|
9252
9410
|
ref={setViewport}
|
|
9253
9411
|
data-floating=""
|
|
@@ -9365,6 +9523,7 @@ export function NavigationMenuTrigger({
|
|
|
9365
9523
|
{...props}
|
|
9366
9524
|
>
|
|
9367
9525
|
{children}
|
|
9526
|
+
|
|
9368
9527
|
<motion.span
|
|
9369
9528
|
aria-hidden="true"
|
|
9370
9529
|
animate={{ rotate: open ? 180 : 0 }}
|
|
@@ -9410,6 +9569,7 @@ export function NavigationMenuContent({
|
|
|
9410
9569
|
return (
|
|
9411
9570
|
<InPanel value>
|
|
9412
9571
|
{!open && <div hidden>{children}</div>}
|
|
9572
|
+
|
|
9413
9573
|
{nav.viewport &&
|
|
9414
9574
|
createPortal(
|
|
9415
9575
|
<AnimatePresence custom={nav.dir} initial={false}>
|
|
@@ -9585,8 +9745,10 @@ export function NavigationMenuLink({
|
|
|
9585
9745
|
{icon}
|
|
9586
9746
|
</span>
|
|
9587
9747
|
)}
|
|
9748
|
+
|
|
9588
9749
|
<span className="flex min-w-0 flex-col gap-0.5">
|
|
9589
9750
|
<span className="font-medium text-foreground">{title}</span>
|
|
9751
|
+
|
|
9590
9752
|
{description && (
|
|
9591
9753
|
<span className="line-clamp-2 text-muted-foreground text-xs leading-snug">
|
|
9592
9754
|
{description}
|
|
@@ -9699,13 +9861,16 @@ export function Newsletter({
|
|
|
9699
9861
|
{title}
|
|
9700
9862
|
</h3>
|
|
9701
9863
|
)}
|
|
9864
|
+
|
|
9702
9865
|
{description && <p className="text-muted-foreground text-sm">{description}</p>}
|
|
9703
9866
|
</div>
|
|
9704
9867
|
)}
|
|
9868
|
+
|
|
9705
9869
|
<InputGroup>
|
|
9706
9870
|
<InputGroupAddon>
|
|
9707
9871
|
<Mail aria-hidden="true" />
|
|
9708
9872
|
</InputGroupAddon>
|
|
9873
|
+
|
|
9709
9874
|
<Input
|
|
9710
9875
|
type="email"
|
|
9711
9876
|
name="email"
|
|
@@ -9714,6 +9879,7 @@ export function Newsletter({
|
|
|
9714
9879
|
placeholder={placeholder}
|
|
9715
9880
|
aria-label="Email address"
|
|
9716
9881
|
/>
|
|
9882
|
+
|
|
9717
9883
|
<InputGroupAddon align="end">
|
|
9718
9884
|
<Button type="submit" size="sm" status={status} successLabel="Subscribed">
|
|
9719
9885
|
{submitLabel}
|
|
@@ -9812,6 +9978,7 @@ export function NumberInput({
|
|
|
9812
9978
|
<Minus aria-hidden="true" />
|
|
9813
9979
|
</button>
|
|
9814
9980
|
</InputGroupAddon>
|
|
9981
|
+
|
|
9815
9982
|
<Input
|
|
9816
9983
|
type="number"
|
|
9817
9984
|
inputMode={Number.isInteger(step) ? "numeric" : "decimal"}
|
|
@@ -9835,6 +10002,7 @@ export function NumberInput({
|
|
|
9835
10002
|
className="px-0 text-center tabular-nums [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
|
|
9836
10003
|
{...props}
|
|
9837
10004
|
/>
|
|
10005
|
+
|
|
9838
10006
|
<InputGroupAddon align="end" className="pr-1">
|
|
9839
10007
|
<button
|
|
9840
10008
|
type="button"
|
|
@@ -9953,6 +10121,7 @@ function Control({
|
|
|
9953
10121
|
const content = (
|
|
9954
10122
|
<>
|
|
9955
10123
|
{active && <PaginationActivePill />}
|
|
10124
|
+
|
|
9956
10125
|
{children}
|
|
9957
10126
|
</>
|
|
9958
10127
|
);
|
|
@@ -10018,9 +10187,11 @@ export function Pagination({
|
|
|
10018
10187
|
className="gap-1 px-2.5"
|
|
10019
10188
|
>
|
|
10020
10189
|
<ChevronLeft aria-hidden="true" />
|
|
10190
|
+
|
|
10021
10191
|
<span className="hidden sm:block">{labels?.previous ?? "Previous"}</span>
|
|
10022
10192
|
</Control>
|
|
10023
10193
|
</li>
|
|
10194
|
+
|
|
10024
10195
|
{pageRange(current, total, siblings).map((page, i) => (
|
|
10025
10196
|
// biome-ignore lint/suspicious/noArrayIndexKey: slots stay mounted so numbers swipe (see pageRange)
|
|
10026
10197
|
<li key={i}>
|
|
@@ -10038,6 +10209,7 @@ export function Pagination({
|
|
|
10038
10209
|
)}
|
|
10039
10210
|
</li>
|
|
10040
10211
|
))}
|
|
10212
|
+
|
|
10041
10213
|
<li>
|
|
10042
10214
|
<Control
|
|
10043
10215
|
{...nav}
|
|
@@ -10047,6 +10219,7 @@ export function Pagination({
|
|
|
10047
10219
|
className="gap-1 px-2.5"
|
|
10048
10220
|
>
|
|
10049
10221
|
<span className="hidden sm:block">{labels?.next ?? "Next"}</span>
|
|
10222
|
+
|
|
10050
10223
|
<ChevronRight aria-hidden="true" />
|
|
10051
10224
|
</Control>
|
|
10052
10225
|
</li>
|
|
@@ -10349,6 +10522,19 @@ export function PopoverContent({
|
|
|
10349
10522
|
aria-labelledby={\`\${id}-trigger\`}
|
|
10350
10523
|
tabIndex={-1}
|
|
10351
10524
|
onKeyDown={(event) => {
|
|
10525
|
+
// The panel sits at the end of body: Tab past its last field goes on from the trigger.
|
|
10526
|
+
if (event.key === "Tab" && !event.shiftKey) {
|
|
10527
|
+
const last = [...(panel.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? [])].at(
|
|
10528
|
+
-1,
|
|
10529
|
+
);
|
|
10530
|
+
|
|
10531
|
+
if (last && event.target !== last) return;
|
|
10532
|
+
setOpen(false);
|
|
10533
|
+
trigger.current?.focus();
|
|
10534
|
+
|
|
10535
|
+
return;
|
|
10536
|
+
}
|
|
10537
|
+
|
|
10352
10538
|
if (event.key !== "Escape" || event.defaultPrevented) return;
|
|
10353
10539
|
// Handled here so an enclosing Dialog stays open.
|
|
10354
10540
|
event.preventDefault();
|
|
@@ -10406,8 +10592,8 @@ export function PricingPage() {
|
|
|
10406
10592
|
]}
|
|
10407
10593
|
/>
|
|
10408
10594
|
);
|
|
10409
|
-
}` }], api: { entries: [{ kind: "component", name: "Pricing", props: [{ name: "plans", type: "
|
|
10410
|
-
rolling up when they drop and down when they rise.` }], types: {
|
|
10595
|
+
}` }], api: { entries: [{ kind: "component", name: "Pricing", props: [{ name: "plans", type: "PricingPlan[]", required: true, description: "Plans in display order." }, { name: "currency", type: "string", required: false, default: '"USD"', description: 'ISO 4217 code for the price, e.g. "EUR".' }, { name: "defaultPeriod", type: "PricingPeriod", required: false, default: '"monthly"', description: "Billing period selected at first render." }, { name: "locale", type: "string", required: false, default: '"en-US"', description: "BCP 47 locale for number formatting." }, { name: "yearlyNote", type: "string", required: false, description: 'Next to "Yearly", e.g. "−17%".' }], description: `Plan cards with a monthly / yearly switch (segmented ToggleGroup). Prices swap on a spring,
|
|
10596
|
+
rolling up when they drop and down when they rise.` }], types: { PricingPeriod: '"monthly" | "yearly"', PricingPlan: "{ name: string; description?: string; price: Record<PricingPeriod, number>; features: string[]; cta: ReactNode; featured?: boolean; badge?: string; }" } }, dependencies: ["lucide-react@1.63.0", "motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/badge.json", "https://kznlabs.github.io/labsUI/r/card.json", "https://kznlabs.github.io/labsUI/r/toggle-group.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/blocks/pricing.tsx", type: "registry:block", content: `"use client";
|
|
10411
10597
|
|
|
10412
10598
|
import { Check } from "lucide-react";
|
|
10413
10599
|
import { AnimatePresence, motion } from "motion/react";
|
|
@@ -10424,15 +10610,15 @@ import {
|
|
|
10424
10610
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
|
10425
10611
|
import { cn, spring } from "@/lib/utils";
|
|
10426
10612
|
|
|
10427
|
-
type
|
|
10613
|
+
export type PricingPeriod = "monthly" | "yearly";
|
|
10428
10614
|
|
|
10429
|
-
type
|
|
10615
|
+
export type PricingPlan = {
|
|
10430
10616
|
/** Plan name, the card's heading. */
|
|
10431
10617
|
name: string;
|
|
10432
10618
|
/** One line under the name. */
|
|
10433
10619
|
description?: string;
|
|
10434
10620
|
/** Price per month on each billing period, e.g. \`{ monthly: 12, yearly: 10 }\`. */
|
|
10435
|
-
price: Record<
|
|
10621
|
+
price: Record<PricingPeriod, number>;
|
|
10436
10622
|
features: string[];
|
|
10437
10623
|
/** Call to action, e.g. \`<Button className="w-full">Start trial</Button>\`. */
|
|
10438
10624
|
cta: ReactNode;
|
|
@@ -10444,13 +10630,13 @@ type Plan = {
|
|
|
10444
10630
|
|
|
10445
10631
|
type PricingProps = {
|
|
10446
10632
|
/** Plans in display order. */
|
|
10447
|
-
plans:
|
|
10633
|
+
plans: PricingPlan[];
|
|
10448
10634
|
/** ISO 4217 code for the price, e.g. "EUR". */
|
|
10449
10635
|
currency?: string;
|
|
10450
10636
|
/** BCP 47 locale for number formatting. */
|
|
10451
10637
|
locale?: string;
|
|
10452
10638
|
/** Billing period selected at first render. */
|
|
10453
|
-
defaultPeriod?:
|
|
10639
|
+
defaultPeriod?: PricingPeriod;
|
|
10454
10640
|
/** Next to "Yearly", e.g. "−17%". */
|
|
10455
10641
|
yearlyNote?: string;
|
|
10456
10642
|
className?: string;
|
|
@@ -10468,7 +10654,7 @@ export function Pricing({
|
|
|
10468
10654
|
yearlyNote,
|
|
10469
10655
|
className,
|
|
10470
10656
|
}: PricingProps) {
|
|
10471
|
-
const [period, setPeriod] = useState<
|
|
10657
|
+
const [period, setPeriod] = useState<PricingPeriod>(defaultPeriod);
|
|
10472
10658
|
const money = new Intl.NumberFormat(locale, {
|
|
10473
10659
|
style: "currency",
|
|
10474
10660
|
currency,
|
|
@@ -10481,9 +10667,10 @@ export function Pricing({
|
|
|
10481
10667
|
<ToggleGroup
|
|
10482
10668
|
aria-label="Billing period"
|
|
10483
10669
|
value={period}
|
|
10484
|
-
onValueChange={(value) => value && setPeriod(value as
|
|
10670
|
+
onValueChange={(value) => value && setPeriod(value as PricingPeriod)}
|
|
10485
10671
|
>
|
|
10486
10672
|
<ToggleGroupItem value="monthly">Monthly</ToggleGroupItem>
|
|
10673
|
+
|
|
10487
10674
|
<ToggleGroupItem value="yearly">
|
|
10488
10675
|
Yearly
|
|
10489
10676
|
{yearlyNote && (
|
|
@@ -10493,6 +10680,7 @@ export function Pricing({
|
|
|
10493
10680
|
)}
|
|
10494
10681
|
</ToggleGroupItem>
|
|
10495
10682
|
</ToggleGroup>
|
|
10683
|
+
|
|
10496
10684
|
<div className="grid w-full gap-4 sm:grid-cols-[repeat(auto-fit,minmax(14rem,1fr))]">
|
|
10497
10685
|
{plans.map((plan) => (
|
|
10498
10686
|
<Card
|
|
@@ -10502,14 +10690,17 @@ export function Pricing({
|
|
|
10502
10690
|
<CardHeader>
|
|
10503
10691
|
<div className="flex min-h-6 items-center justify-between gap-2">
|
|
10504
10692
|
<CardTitle>{plan.name}</CardTitle>
|
|
10693
|
+
|
|
10505
10694
|
{plan.badge && (
|
|
10506
10695
|
<Badge variant={plan.featured ? "default" : "secondary"} shape="pill">
|
|
10507
10696
|
{plan.badge}
|
|
10508
10697
|
</Badge>
|
|
10509
10698
|
)}
|
|
10510
10699
|
</div>
|
|
10700
|
+
|
|
10511
10701
|
{plan.description && <CardDescription>{plan.description}</CardDescription>}
|
|
10512
10702
|
</CardHeader>
|
|
10703
|
+
|
|
10513
10704
|
<CardContent className="flex flex-col gap-5">
|
|
10514
10705
|
<p className="flex items-baseline gap-1">
|
|
10515
10706
|
<span className="relative inline-flex overflow-hidden font-semibold text-4xl tabular-nums tracking-tight">
|
|
@@ -10531,20 +10722,24 @@ export function Pricing({
|
|
|
10531
10722
|
</motion.span>
|
|
10532
10723
|
</AnimatePresence>
|
|
10533
10724
|
</span>
|
|
10725
|
+
|
|
10534
10726
|
<span className="text-muted-foreground text-sm">
|
|
10535
10727
|
/ month
|
|
10536
10728
|
<span className="sr-only">, billed {period}</span>
|
|
10537
10729
|
</span>
|
|
10538
10730
|
</p>
|
|
10731
|
+
|
|
10539
10732
|
<ul className="flex flex-col gap-2 text-sm">
|
|
10540
10733
|
{plan.features.map((feature) => (
|
|
10541
10734
|
<li key={feature} className="flex gap-2">
|
|
10542
10735
|
<Check className="mt-0.5 size-4 shrink-0 text-primary-ink" aria-hidden="true" />
|
|
10736
|
+
|
|
10543
10737
|
{feature}
|
|
10544
10738
|
</li>
|
|
10545
10739
|
))}
|
|
10546
10740
|
</ul>
|
|
10547
10741
|
</CardContent>
|
|
10742
|
+
|
|
10548
10743
|
<CardFooter className="mt-auto">{plan.cta}</CardFooter>
|
|
10549
10744
|
</Card>
|
|
10550
10745
|
))}
|
|
@@ -10552,7 +10747,7 @@ export function Pricing({
|
|
|
10552
10747
|
</div>
|
|
10553
10748
|
);
|
|
10554
10749
|
}
|
|
10555
|
-
` }] }, "product-card": { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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.", docs: "https://kznlabs.github.io/labsUI/components/product-card", usage: '<ProductCard name="Clip camera" price={279} compareAt={349} badge="−20%" media={[<img
|
|
10750
|
+
` }] }, "product-card": { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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.", docs: "https://kznlabs.github.io/labsUI/components/product-card", usage: '<ProductCard name="Clip camera" price={279} compareAt={349} badge="−20%" media={[<img src={front} alt="Front" />, <img src={back} alt="Back" />]} onAddToCart={add} />', examples: [{ name: "product-grid.tsx", code: `import { ProductCard } from "@/components/blocks/product-card";
|
|
10556
10751
|
|
|
10557
10752
|
type Product = {
|
|
10558
10753
|
id: string;
|
|
@@ -10658,17 +10853,20 @@ export function ProductCard({
|
|
|
10658
10853
|
<CarouselItem key={i}>{frame(slide)}</CarouselItem>
|
|
10659
10854
|
))}
|
|
10660
10855
|
</CarouselContent>
|
|
10856
|
+
|
|
10661
10857
|
<CarouselDots className="absolute inset-x-0 bottom-2 mt-0" />
|
|
10662
10858
|
</Carousel>
|
|
10663
10859
|
) : (
|
|
10664
10860
|
frame(media[0])
|
|
10665
10861
|
)}
|
|
10862
|
+
|
|
10666
10863
|
{badge && (
|
|
10667
10864
|
<Badge shape="pill" className="absolute top-2 left-2">
|
|
10668
10865
|
{badge}
|
|
10669
10866
|
</Badge>
|
|
10670
10867
|
)}
|
|
10671
10868
|
</div>
|
|
10869
|
+
|
|
10672
10870
|
<div className="flex flex-1 flex-col gap-3 p-3">
|
|
10673
10871
|
<div className="flex items-baseline justify-between gap-2">
|
|
10674
10872
|
<h3 className="min-w-0 truncate font-medium text-sm">
|
|
@@ -10680,16 +10878,20 @@ export function ProductCard({
|
|
|
10680
10878
|
name
|
|
10681
10879
|
)}
|
|
10682
10880
|
</h3>
|
|
10881
|
+
|
|
10683
10882
|
<p className="shrink-0 text-sm tabular-nums">
|
|
10684
10883
|
{compareAt !== undefined && compareAt > price && (
|
|
10685
10884
|
<>
|
|
10686
10885
|
<del className="mr-1.5 text-muted-foreground">{money.format(compareAt)}</del>
|
|
10886
|
+
|
|
10687
10887
|
<span className="sr-only">now </span>
|
|
10688
10888
|
</>
|
|
10689
10889
|
)}
|
|
10890
|
+
|
|
10690
10891
|
{money.format(price)}
|
|
10691
10892
|
</p>
|
|
10692
10893
|
</div>
|
|
10894
|
+
|
|
10693
10895
|
{onAddToCart && (
|
|
10694
10896
|
<Button size="sm" variant="outline" className="mt-auto w-full" onClick={onAddToCart}>
|
|
10695
10897
|
{addLabel}
|
|
@@ -10750,11 +10952,13 @@ export function Progress({
|
|
|
10750
10952
|
{(label || showValue) && (
|
|
10751
10953
|
<div className="flex items-center justify-between gap-3 text-xs">
|
|
10752
10954
|
{label && <span className="font-medium">{label}</span>}
|
|
10955
|
+
|
|
10753
10956
|
{showValue && determinate && (
|
|
10754
10957
|
<span className="ml-auto text-muted-foreground tabular-nums">{pct}%</span>
|
|
10755
10958
|
)}
|
|
10756
10959
|
</div>
|
|
10757
10960
|
)}
|
|
10961
|
+
|
|
10758
10962
|
<div
|
|
10759
10963
|
role="progressbar"
|
|
10760
10964
|
aria-label={label ?? ariaLabel}
|
|
@@ -10866,6 +11070,7 @@ export function Quote({
|
|
|
10866
11070
|
className={cn("flex flex-col", surface[variant], className)}
|
|
10867
11071
|
>
|
|
10868
11072
|
{large && <QuoteIcon aria-hidden="true" className="size-8 text-muted-foreground/60" />}
|
|
11073
|
+
|
|
10869
11074
|
<blockquote
|
|
10870
11075
|
lang={lang}
|
|
10871
11076
|
className={cn(
|
|
@@ -10906,11 +11111,14 @@ export function Quote({
|
|
|
10906
11111
|
)}
|
|
10907
11112
|
</div>
|
|
10908
11113
|
</blockquote>
|
|
11114
|
+
|
|
10909
11115
|
{(author || avatar) && (
|
|
10910
11116
|
<figcaption className="flex items-center gap-3">
|
|
10911
11117
|
{avatar}
|
|
11118
|
+
|
|
10912
11119
|
<span className="grid text-left">
|
|
10913
11120
|
{author && <span className="font-medium text-foreground text-sm">{author}</span>}
|
|
11121
|
+
|
|
10914
11122
|
{subtitle && <span className="text-muted-foreground text-xs">{subtitle}</span>}
|
|
10915
11123
|
</span>
|
|
10916
11124
|
</figcaption>
|
|
@@ -10927,7 +11135,7 @@ export function PlanPicker() {
|
|
|
10927
11135
|
<RadioGroupItem value="pro" description="10 servers, unlimited clips.">Pro</RadioGroupItem>
|
|
10928
11136
|
</RadioGroup>
|
|
10929
11137
|
);
|
|
10930
|
-
}` }], api: { entries: [{ kind: "component", name: "RadioGroup", props: [{ name: "aria-invalid", type: "boolean", required: false, description: "Indicates the entered value does not conform to the format expected by the application." }, { name: "defaultValue", type: "string", required: false, default: '""', description: "Value at first render (uncontrolled)." }, { name: "legend", type: "ReactNode", required: false, description: "Visible group label, rendered as the fieldset's <legend>." }, { name: "name", type: "string", required: false, description: "Form field name; generated when omitted." }, { name: "onValueChange", type: "(value: string) => void", required: false, description: "Called with the picked item's value." }, { name: "orientation", type: '"vertical" | "horizontal"', required: false, default: '"vertical"', description: "Stack items or lay them in a row." }, { name: "value", type: "string", required: false, description: "Controlled value; pair with `onValueChange`." }], extends: "<fieldset>", description: "Native radios in a fieldset: arrow keys, form submit and labels work as the browser does." }, { kind: "component", name: "RadioGroupItem", props: [{ name: "children", type: "ReactNode", required: true, description: "Label." }, { name: "value", type: "string", required: true, description: "Submitted value." }, { name: "description", type: "ReactNode", required: false, description: "Muted line under the label." }, { name: "disabled", type: "boolean", required: false, description: "Shown but not selectable." }], description: "One option. `children` is its label; `description` adds a muted line under it." }], types: {} }, dependencies: ["motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/field.json", "https://kznlabs.github.io/labsUI/r/use-controllable-state.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/radio-group.tsx", type: "registry:ui", content: `"use client";
|
|
11138
|
+
}` }], api: { entries: [{ kind: "component", name: "RadioGroup", props: [{ name: "aria-invalid", type: "boolean", required: false, description: "Indicates the entered value does not conform to the format expected by the application." }, { name: "defaultValue", type: "string", required: false, default: '""', description: "Value at first render (uncontrolled)." }, { name: "legend", type: "ReactNode", required: false, description: "Visible group label, rendered as the fieldset's <legend>." }, { name: "name", type: "string", required: false, description: "Form field name; generated when omitted." }, { name: "onValueChange", type: "(value: string) => void", required: false, description: "Called with the picked item's value." }, { name: "orientation", type: '"vertical" | "horizontal"', required: false, default: '"vertical"', description: "Stack items or lay them in a row." }, { name: "required", type: "boolean", required: false, description: "Submit needs a picked item." }, { name: "value", type: "string", required: false, description: "Controlled value; pair with `onValueChange`." }], extends: "<fieldset>", description: "Native radios in a fieldset: arrow keys, form submit and labels work as the browser does." }, { kind: "component", name: "RadioGroupItem", props: [{ name: "children", type: "ReactNode", required: true, description: "Label." }, { name: "value", type: "string", required: true, description: "Submitted value." }, { name: "description", type: "ReactNode", required: false, description: "Muted line under the label." }, { name: "disabled", type: "boolean", required: false, description: "Shown but not selectable." }], description: "One option. `children` is its label; `description` adds a muted line under it." }], types: {} }, dependencies: ["motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/field.json", "https://kznlabs.github.io/labsUI/r/use-controllable-state.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/radio-group.tsx", type: "registry:ui", content: `"use client";
|
|
10931
11139
|
|
|
10932
11140
|
import { motion } from "motion/react";
|
|
10933
11141
|
import { type ComponentProps, createContext, type ReactNode, use, useId } from "react";
|
|
@@ -10940,6 +11148,7 @@ type RadioCtx = {
|
|
|
10940
11148
|
value: string;
|
|
10941
11149
|
setValue: (value: string) => void;
|
|
10942
11150
|
disabled?: boolean;
|
|
11151
|
+
required?: boolean;
|
|
10943
11152
|
invalid?: boolean;
|
|
10944
11153
|
};
|
|
10945
11154
|
|
|
@@ -10958,6 +11167,8 @@ type RadioGroupProps = Omit<ComponentProps<"fieldset">, "defaultValue" | "onChan
|
|
|
10958
11167
|
name?: string;
|
|
10959
11168
|
/** Stack items or lay them in a row. */
|
|
10960
11169
|
orientation?: "vertical" | "horizontal";
|
|
11170
|
+
/** Submit needs a picked item. */
|
|
11171
|
+
required?: boolean;
|
|
10961
11172
|
"aria-invalid"?: boolean;
|
|
10962
11173
|
};
|
|
10963
11174
|
|
|
@@ -10973,12 +11184,8 @@ export function RadioGroup({
|
|
|
10973
11184
|
children,
|
|
10974
11185
|
...rest
|
|
10975
11186
|
}: RadioGroupProps) {
|
|
10976
|
-
// A fieldset takes no id from Field (labels can't target it)
|
|
10977
|
-
const {
|
|
10978
|
-
id: _id,
|
|
10979
|
-
required: _required,
|
|
10980
|
-
...props
|
|
10981
|
-
} = useFieldControl<typeof rest & { required?: boolean }>(rest);
|
|
11187
|
+
// A fieldset takes no id from Field (labels can't target it); required goes on the radios.
|
|
11188
|
+
const { id: _id, required, ...props } = useFieldControl(rest);
|
|
10982
11189
|
const auto = useId();
|
|
10983
11190
|
const [value, setValue] = useControllableState(valueProp, defaultValue, onValueChange);
|
|
10984
11191
|
|
|
@@ -10989,11 +11196,13 @@ export function RadioGroup({
|
|
|
10989
11196
|
value,
|
|
10990
11197
|
setValue,
|
|
10991
11198
|
disabled: props.disabled,
|
|
11199
|
+
required,
|
|
10992
11200
|
invalid: !!props["aria-invalid"],
|
|
10993
11201
|
}}
|
|
10994
11202
|
>
|
|
10995
11203
|
<fieldset className={cn("min-w-0", className)} {...props}>
|
|
10996
11204
|
{legend && <legend className="mb-3 font-medium text-sm leading-none">{legend}</legend>}
|
|
11205
|
+
|
|
10997
11206
|
<div
|
|
10998
11207
|
className={cn(
|
|
10999
11208
|
"flex gap-3",
|
|
@@ -11043,11 +11252,13 @@ export function RadioGroupItem({
|
|
|
11043
11252
|
value={value}
|
|
11044
11253
|
checked={checked}
|
|
11045
11254
|
disabled={off}
|
|
11255
|
+
required={ctx.required}
|
|
11046
11256
|
aria-invalid={ctx.invalid || undefined}
|
|
11047
11257
|
aria-describedby={description ? \`\${id}-description\` : undefined}
|
|
11048
11258
|
onChange={() => ctx.setValue(value)}
|
|
11049
11259
|
className="peer absolute inset-0 z-10 cursor-pointer appearance-none rounded-full outline-none disabled:cursor-not-allowed"
|
|
11050
11260
|
/>
|
|
11261
|
+
|
|
11051
11262
|
<span
|
|
11052
11263
|
aria-hidden="true"
|
|
11053
11264
|
className={cn(
|
|
@@ -11063,6 +11274,7 @@ export function RadioGroupItem({
|
|
|
11063
11274
|
/>
|
|
11064
11275
|
</span>
|
|
11065
11276
|
</span>
|
|
11277
|
+
|
|
11066
11278
|
<span className="grid gap-1">
|
|
11067
11279
|
<label
|
|
11068
11280
|
htmlFor={id}
|
|
@@ -11073,6 +11285,7 @@ export function RadioGroupItem({
|
|
|
11073
11285
|
>
|
|
11074
11286
|
{children}
|
|
11075
11287
|
</label>
|
|
11288
|
+
|
|
11076
11289
|
{description && (
|
|
11077
11290
|
<span id={\`\${id}-description\`} className="text-muted-foreground text-xs">
|
|
11078
11291
|
{description}
|
|
@@ -11092,11 +11305,12 @@ export function ProductRating({ average, count }: { average: number; count: numb
|
|
|
11092
11305
|
<span className="text-muted-foreground">· {count} reviews</span>
|
|
11093
11306
|
</div>
|
|
11094
11307
|
);
|
|
11095
|
-
}` }], api: { entries: [{ kind: "component", name: "Rating", props: [{ name: "
|
|
11308
|
+
}` }], api: { entries: [{ kind: "component", name: "Rating", props: [{ name: "aria-describedby", type: "string", required: false, description: "Ids of hint or error text; Field fills it in." }, { name: "aria-invalid", type: "boolean", required: false, description: "Error styling; Field sets it from `error`." }, { name: "defaultValue", type: "number", required: false, description: "Rating at first render (uncontrolled)." }, { name: "disabled", type: "boolean", required: false, description: "Stars can't be picked and the value isn't submitted." }, { name: "label", type: "string", required: false, description: "Group label (visually hidden)." }, { name: "max", type: "number", required: false, description: "Number of stars." }, { name: "name", type: "string", required: false, description: "Form field name for the chosen value." }, { name: "onValueChange", type: "(value: number) => void", required: false, description: "Called with the picked rating." }, { name: "readOnly", type: "boolean", required: false, description: "Display only: renders fractions (4.6) as a partially filled star, no inputs." }, { name: "required", type: "boolean", required: false, description: "Submit needs a rating." }, { name: "size", type: '"sm" | "default"', required: false, description: "Star size." }, { name: "value", type: "number", required: false, description: "Controlled rating; pair with `onValueChange`." }], description: "Star rating. Interactive: native radios (arrow keys, forms, screen readers) with a hover\npreview and a spring lift/press on the stars. `readOnly`: an image of the score, fractions included." }], types: {} }, dependencies: ["lucide-react@1.63.0", "motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/field.json", "https://kznlabs.github.io/labsUI/r/use-controllable-state.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/rating.tsx", type: "registry:ui", content: `"use client";
|
|
11096
11309
|
|
|
11097
11310
|
import { Star } from "lucide-react";
|
|
11098
11311
|
import { motion } from "motion/react";
|
|
11099
11312
|
import { useId, useState } from "react";
|
|
11313
|
+
import { useFieldControl } from "@/components/ui/field";
|
|
11100
11314
|
import { useControllableState } from "@/lib/use-controllable-state";
|
|
11101
11315
|
import { clamp, cn, spring } from "@/lib/utils";
|
|
11102
11316
|
|
|
@@ -11113,10 +11327,18 @@ type RatingProps = {
|
|
|
11113
11327
|
readOnly?: boolean;
|
|
11114
11328
|
/** Form field name for the chosen value. */
|
|
11115
11329
|
name?: string;
|
|
11330
|
+
/** Submit needs a rating. */
|
|
11331
|
+
required?: boolean;
|
|
11332
|
+
/** Stars can't be picked and the value isn't submitted. */
|
|
11333
|
+
disabled?: boolean;
|
|
11116
11334
|
/** Group label (visually hidden). */
|
|
11117
11335
|
label?: string;
|
|
11118
11336
|
/** Star size. */
|
|
11119
11337
|
size?: "sm" | "default";
|
|
11338
|
+
/** Ids of hint or error text; Field fills it in. */
|
|
11339
|
+
"aria-describedby"?: string;
|
|
11340
|
+
/** Error styling; Field sets it from \`error\`. */
|
|
11341
|
+
"aria-invalid"?: boolean;
|
|
11120
11342
|
className?: string;
|
|
11121
11343
|
};
|
|
11122
11344
|
|
|
@@ -11126,17 +11348,22 @@ const sizes = { sm: "size-4", default: "size-5" } as const;
|
|
|
11126
11348
|
* Star rating. Interactive: native radios (arrow keys, forms, screen readers) with a hover
|
|
11127
11349
|
* preview and a spring lift/press on the stars. \`readOnly\`: an image of the score, fractions included.
|
|
11128
11350
|
*/
|
|
11129
|
-
export function Rating({
|
|
11130
|
-
|
|
11131
|
-
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
|
|
11351
|
+
export function Rating(allProps: RatingProps) {
|
|
11352
|
+
const {
|
|
11353
|
+
value: valueProp,
|
|
11354
|
+
defaultValue = 0,
|
|
11355
|
+
onValueChange,
|
|
11356
|
+
max = 5,
|
|
11357
|
+
readOnly = false,
|
|
11358
|
+
name,
|
|
11359
|
+
required,
|
|
11360
|
+
disabled,
|
|
11361
|
+
label = "Rating",
|
|
11362
|
+
size = "default",
|
|
11363
|
+
className,
|
|
11364
|
+
"aria-describedby": describedBy,
|
|
11365
|
+
"aria-invalid": invalid,
|
|
11366
|
+
} = useFieldControl(allProps);
|
|
11140
11367
|
const auto = useId();
|
|
11141
11368
|
const [value, setValue] = useControllableState(valueProp, defaultValue, onValueChange);
|
|
11142
11369
|
const [hover, setHover] = useState<number | null>(null);
|
|
@@ -11160,6 +11387,7 @@ export function Rating({
|
|
|
11160
11387
|
fill="currentColor"
|
|
11161
11388
|
strokeWidth={0}
|
|
11162
11389
|
/>
|
|
11390
|
+
|
|
11163
11391
|
<span
|
|
11164
11392
|
className="absolute inset-0 overflow-hidden"
|
|
11165
11393
|
style={{ width: \`\${fill * 100}%\` }}
|
|
@@ -11182,23 +11410,34 @@ export function Rating({
|
|
|
11182
11410
|
|
|
11183
11411
|
return (
|
|
11184
11412
|
<fieldset
|
|
11413
|
+
disabled={disabled}
|
|
11414
|
+
aria-describedby={describedBy}
|
|
11185
11415
|
onPointerLeave={() => setHover(null)}
|
|
11186
|
-
className={cn("inline-flex items-center gap-0.5", className)}
|
|
11416
|
+
className={cn("inline-flex items-center gap-0.5", disabled && "opacity-50", className)}
|
|
11187
11417
|
>
|
|
11188
11418
|
<legend className="sr-only">{label}</legend>
|
|
11419
|
+
|
|
11189
11420
|
{stars.map((star) => (
|
|
11190
|
-
<label
|
|
11421
|
+
<label
|
|
11422
|
+
key={star}
|
|
11423
|
+
className={cn("relative", disabled ? "cursor-not-allowed" : "cursor-pointer")}
|
|
11424
|
+
onPointerEnter={() => !disabled && setHover(star)}
|
|
11425
|
+
>
|
|
11191
11426
|
<input
|
|
11192
11427
|
type="radio"
|
|
11193
11428
|
name={name ?? auto}
|
|
11194
11429
|
value={star}
|
|
11195
11430
|
checked={value === star}
|
|
11431
|
+
required={required}
|
|
11432
|
+
aria-invalid={invalid || undefined}
|
|
11196
11433
|
onChange={() => setValue(star)}
|
|
11197
11434
|
className="peer sr-only"
|
|
11198
11435
|
/>
|
|
11436
|
+
|
|
11199
11437
|
<span className="sr-only">
|
|
11200
11438
|
{star} {star === 1 ? "star" : "stars"}
|
|
11201
11439
|
</span>
|
|
11440
|
+
|
|
11202
11441
|
<motion.span
|
|
11203
11442
|
aria-hidden="true"
|
|
11204
11443
|
// whileTap would make the star focusable, the radio underneath is the tab stop.
|
|
@@ -11350,11 +11589,11 @@ export function AdminLayout({ nav, log, children }: {
|
|
|
11350
11589
|
</ResizablePanel>
|
|
11351
11590
|
</ResizablePanelGroup>
|
|
11352
11591
|
);
|
|
11353
|
-
}` }], api: { entries: [{ kind: "component", name: "ResizablePanelGroup", props: [{ name: "direction", type: "
|
|
11592
|
+
}` }], api: { entries: [{ kind: "component", name: "ResizablePanelGroup", props: [{ name: "direction", type: "ResizableDirection", required: false, default: '"horizontal"', description: '"horizontal": panels side by side. "vertical": stacked.' }, { name: "keyboardStep", type: "number", required: false, default: "5", description: "% per arrow key press." }, { name: "onLayout", type: "(sizes: number[]) => void", required: false, description: "Final sizes after each drag, key or toggle, e.g. to save them in a cookie for SSR." }], extends: "<div>", description: `Split panes (admin layouts, docs, editors). Sizes are % of the group. Handles are APG window
|
|
11354
11593
|
splitters: arrows step, Home/End jump to the limits, Enter collapses a collapsible neighbor.
|
|
11355
11594
|
Drag follows the pointer; keys and toggles spring. Panels and handles find their order in the
|
|
11356
11595
|
DOM, so wrappers and Server Component parents work. Until hydration the layout is plain CSS
|
|
11357
|
-
(\`defaultSize\` as a flex basis, the others share the rest), so SSR already shows it.` }, { kind: "component", name: "ResizablePanel", props: [{ name: "collapsedSize", type: "number", required: false, default: "0", description: "Share in % when collapsed (default 0), e.g. an icon rail." }, { name: "collapsible", type: "boolean", required: false, default: "false", description: "Dragging past half the minimum snaps it shut; Enter or double-click on the handle toggles." }, { name: "defaultSize", type: "number", required: false, description: "Starting share of the group in %. Panels without one split what is left evenly." }, { name: "maxSize", type: "number", required: false, default: "100", description: "Largest size in % of the group." }, { name: "minSize", type: "number", required: false, default: "10", description: "Smallest share in % (default 10)." }], extends: "<div>" }, { kind: "component", name: "ResizableHandle", props: [{ name: "withHandle", type: "boolean", required: false, description: "Visible grip in the middle of the divider." }], extends: "<div>", description: "The divider between two panels. `withHandle` adds a visible grip." }], types: {
|
|
11596
|
+
(\`defaultSize\` as a flex basis, the others share the rest), so SSR already shows it.` }, { kind: "component", name: "ResizablePanel", props: [{ name: "collapsedSize", type: "number", required: false, default: "0", description: "Share in % when collapsed (default 0), e.g. an icon rail." }, { name: "collapsible", type: "boolean", required: false, default: "false", description: "Dragging past half the minimum snaps it shut; Enter or double-click on the handle toggles." }, { name: "defaultSize", type: "number", required: false, description: "Starting share of the group in %. Panels without one split what is left evenly." }, { name: "maxSize", type: "number", required: false, default: "100", description: "Largest size in % of the group." }, { name: "minSize", type: "number", required: false, default: "10", description: "Smallest share in % (default 10)." }], extends: "<div>" }, { kind: "component", name: "ResizableHandle", props: [{ name: "withHandle", type: "boolean", required: false, description: "Visible grip in the middle of the divider." }], extends: "<div>", description: "The divider between two panels. `withHandle` adds a visible grip." }], types: { ResizableDirection: '"horizontal" | "vertical"' } }, dependencies: ["motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/resizable.tsx", type: "registry:ui", content: `"use client";
|
|
11358
11597
|
|
|
11359
11598
|
import { type AnimationPlaybackControls, animate, useReducedMotion } from "motion/react";
|
|
11360
11599
|
import {
|
|
@@ -11370,7 +11609,7 @@ import {
|
|
|
11370
11609
|
} from "react";
|
|
11371
11610
|
import { cn, spring } from "@/lib/utils";
|
|
11372
11611
|
|
|
11373
|
-
type
|
|
11612
|
+
export type ResizableDirection = "horizontal" | "vertical";
|
|
11374
11613
|
type Limits = { min: number; max: number; collapsible: boolean; collapsed: number };
|
|
11375
11614
|
|
|
11376
11615
|
type ResizablePanelProps = ComponentProps<"div"> & {
|
|
@@ -11394,7 +11633,7 @@ type Layout = {
|
|
|
11394
11633
|
};
|
|
11395
11634
|
|
|
11396
11635
|
type GroupCtx = {
|
|
11397
|
-
direction:
|
|
11636
|
+
direction: ResizableDirection;
|
|
11398
11637
|
id: string;
|
|
11399
11638
|
layout: Layout | null;
|
|
11400
11639
|
/** Panels report their limits here from a layout effect; the group's own effect reads them. */
|
|
@@ -11496,7 +11735,7 @@ export function ResizablePanelGroup({
|
|
|
11496
11735
|
...props
|
|
11497
11736
|
}: ComponentProps<"div"> & {
|
|
11498
11737
|
/** "horizontal": panels side by side. "vertical": stacked. */
|
|
11499
|
-
direction?:
|
|
11738
|
+
direction?: ResizableDirection;
|
|
11500
11739
|
/** Final sizes after each drag, key or toggle, e.g. to save them in a cookie for SSR. */
|
|
11501
11740
|
onLayout?: (sizes: number[]) => void;
|
|
11502
11741
|
/** % per arrow key press. */
|
|
@@ -12117,7 +12356,9 @@ export function ScrollArea({
|
|
|
12117
12356
|
>
|
|
12118
12357
|
{children}
|
|
12119
12358
|
</div>
|
|
12359
|
+
|
|
12120
12360
|
{orientation !== "horizontal" && <Thumb axis="y" viewport={viewport} />}
|
|
12361
|
+
|
|
12121
12362
|
{orientation !== "vertical" && <Thumb axis="x" viewport={viewport} />}
|
|
12122
12363
|
</div>
|
|
12123
12364
|
);
|
|
@@ -12214,6 +12455,7 @@ export function SearchInput({
|
|
|
12214
12455
|
aria-hidden="true"
|
|
12215
12456
|
className="-translate-y-1/2 pointer-events-none absolute top-1/2 left-3 size-4 text-muted-foreground"
|
|
12216
12457
|
/>
|
|
12458
|
+
|
|
12217
12459
|
<Input
|
|
12218
12460
|
ref={(element) => {
|
|
12219
12461
|
input.current = element;
|
|
@@ -12240,6 +12482,7 @@ export function SearchInput({
|
|
|
12240
12482
|
)}
|
|
12241
12483
|
{...props}
|
|
12242
12484
|
/>
|
|
12485
|
+
|
|
12243
12486
|
<span className="-translate-y-1/2 absolute top-1/2 right-2 flex">
|
|
12244
12487
|
<AnimatePresence mode="popLayout" initial={false}>
|
|
12245
12488
|
{value ? (
|
|
@@ -12346,6 +12589,7 @@ export function SegmentedTabs({ label, items, value, onChange, className }: Segm
|
|
|
12346
12589
|
transition={spring}
|
|
12347
12590
|
className="-z-10 absolute inset-y-1 left-0 rounded-md bg-raised shadow-sm ring-[0.5px] ring-raised-ring"
|
|
12348
12591
|
/>
|
|
12592
|
+
|
|
12349
12593
|
{items.map((item) => (
|
|
12350
12594
|
<button
|
|
12351
12595
|
key={item}
|
|
@@ -12364,11 +12608,12 @@ export function SegmentedTabs({ label, items, value, onChange, className }: Segm
|
|
|
12364
12608
|
{item}
|
|
12365
12609
|
</button>
|
|
12366
12610
|
))}
|
|
12611
|
+
|
|
12367
12612
|
<OverflowFocus />
|
|
12368
12613
|
</nav>
|
|
12369
12614
|
);
|
|
12370
12615
|
}
|
|
12371
|
-
` }] }, select: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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
|
|
12616
|
+
` }] }, select: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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.", docs: "https://kznlabs.github.io/labsUI/components/select", usage: '<Select id="role" name="role" options={[{ value: "admin", label: "Admin" }]} value={v} onValueChange={setV} placeholder="Pick a role" />', examples: [{ name: "role-picker.tsx", code: `"use client";
|
|
12372
12617
|
|
|
12373
12618
|
import { useState } from "react";
|
|
12374
12619
|
import { Field } from "@/components/ui/field";
|
|
@@ -12387,7 +12632,7 @@ export function RolePicker() {
|
|
|
12387
12632
|
<Select name="role" options={roles} value={role} onValueChange={setRole} />
|
|
12388
12633
|
</Field>
|
|
12389
12634
|
);
|
|
12390
|
-
}` }], api: { entries: [{ kind: "component", name: "Select", props: [{ name: "options", type: "SelectGroup[] | SelectOption[]", required: true, description: "Flat options, or labeled groups of options." }, { name: "aria-describedby", type: "string", required: false, description: "Ids of hint or error text; Field fills it in." }, { name: "aria-invalid", type: "boolean", required: false, description: "Error styling; Field sets it from `error`." }, { name: "aria-label", type: "string", required: false, description: "Accessible name when there is no visible label." }, { name: "defaultValue", type: "string", required: false, description: "Value at first render (uncontrolled)." }, { name: "disabled", type: "boolean", required: false, description: "Blocks opening." }, { name: "id", type: "string", required: false, description: "Pair with <Label htmlFor>, or let <Field> fill it in." }, { name: "name", type: "string", required: false, description: "
|
|
12635
|
+
}` }], api: { entries: [{ kind: "component", name: "Select", props: [{ name: "options", type: "SelectGroup[] | SelectOption[]", required: true, description: "Flat options, or labeled groups of options." }, { name: "aria-describedby", type: "string", required: false, description: "Ids of hint or error text; Field fills it in." }, { name: "aria-invalid", type: "boolean", required: false, description: "Error styling; Field sets it from `error`." }, { name: "aria-label", type: "string", required: false, description: "Accessible name when there is no visible label." }, { name: "defaultValue", type: "string", required: false, description: "Value at first render (uncontrolled)." }, { name: "disabled", type: "boolean", required: false, description: "Blocks opening; the value isn't submitted." }, { name: "id", type: "string", required: false, description: "Pair with <Label htmlFor>, or let <Field> fill it in." }, { name: "name", type: "string", required: false, description: "Form field name for the picked value." }, { name: "onValueChange", type: "(value: string) => void", required: false, description: "Called with the picked value." }, { name: "placeholder", type: "string", required: false, description: "Trigger text when nothing is picked." }, { name: "required", type: "boolean", required: false, description: "Form submit needs a value." }, { name: "value", type: "string", required: false, description: "Controlled value; pair with `onValueChange`." }], description: `Single-choice listbox styled like Input. Keyboard matches a native select: Up, Down, Home, End,
|
|
12391
12636
|
typeahead (also while closed), Enter or Space to pick, Esc to cancel.` }], types: { SelectOption: "{ value: string; label: string; icon?: ReactNode; disabled?: boolean }", SelectGroup: "{ label: string; options: SelectOption[] }" } }, dependencies: ["lucide-react@1.63.0", "motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/field.json", "https://kznlabs.github.io/labsUI/r/form-value.json", "https://kznlabs.github.io/labsUI/r/input.json", "https://kznlabs.github.io/labsUI/r/list.json", "https://kznlabs.github.io/labsUI/r/platform.json", "https://kznlabs.github.io/labsUI/r/use-anchor.json", "https://kznlabs.github.io/labsUI/r/use-controllable-state.json", "https://kznlabs.github.io/labsUI/r/use-dismiss.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/select.tsx", type: "registry:ui", content: `"use client";
|
|
12392
12637
|
|
|
12393
12638
|
import { Check, ChevronsUpDown } from "lucide-react";
|
|
@@ -12418,11 +12663,11 @@ type SelectProps = {
|
|
|
12418
12663
|
onValueChange?: (value: string) => void;
|
|
12419
12664
|
/** Trigger text when nothing is picked. */
|
|
12420
12665
|
placeholder?: string;
|
|
12421
|
-
/**
|
|
12666
|
+
/** Form field name for the picked value. */
|
|
12422
12667
|
name?: string;
|
|
12423
12668
|
/** Pair with <Label htmlFor>, or let <Field> fill it in. */
|
|
12424
12669
|
id?: string;
|
|
12425
|
-
/** Blocks opening. */
|
|
12670
|
+
/** Blocks opening; the value isn't submitted. */
|
|
12426
12671
|
disabled?: boolean;
|
|
12427
12672
|
/** Form submit needs a value. */
|
|
12428
12673
|
required?: boolean;
|
|
@@ -12575,17 +12820,22 @@ export function Select(allProps: SelectProps) {
|
|
|
12575
12820
|
)}
|
|
12576
12821
|
>
|
|
12577
12822
|
{selected?.icon}
|
|
12823
|
+
|
|
12578
12824
|
{selected?.label ?? placeholder}
|
|
12579
12825
|
</span>
|
|
12826
|
+
|
|
12580
12827
|
<ChevronsUpDown className="text-muted-foreground" aria-hidden="true" />
|
|
12581
12828
|
</button>
|
|
12829
|
+
|
|
12582
12830
|
<FormValue
|
|
12583
12831
|
name={name}
|
|
12584
12832
|
value={value}
|
|
12585
|
-
required={required
|
|
12833
|
+
required={required}
|
|
12834
|
+
disabled={disabled}
|
|
12586
12835
|
anchor={trigger}
|
|
12587
12836
|
onInvalid={() => setInvalid(true)}
|
|
12588
12837
|
/>
|
|
12838
|
+
|
|
12589
12839
|
{mounted &&
|
|
12590
12840
|
createPortal(
|
|
12591
12841
|
<AnimatePresence>
|
|
@@ -12626,6 +12876,7 @@ export function Select(allProps: SelectProps) {
|
|
|
12626
12876
|
{groupIndex > 0 && (
|
|
12627
12877
|
<div aria-hidden="true" className="-mx-1 my-1 h-px bg-border" />
|
|
12628
12878
|
)}
|
|
12879
|
+
|
|
12629
12880
|
{group.label && (
|
|
12630
12881
|
<div
|
|
12631
12882
|
aria-hidden="true"
|
|
@@ -12634,6 +12885,7 @@ export function Select(allProps: SelectProps) {
|
|
|
12634
12885
|
{group.label}
|
|
12635
12886
|
</div>
|
|
12636
12887
|
)}
|
|
12888
|
+
|
|
12637
12889
|
{group.options.map((option) => (
|
|
12638
12890
|
// biome-ignore lint/a11y/useKeyWithClickEvents: keys handled on the listbox (aria-activedescendant)
|
|
12639
12891
|
<div
|
|
@@ -12657,7 +12909,9 @@ export function Select(allProps: SelectProps) {
|
|
|
12657
12909
|
)}
|
|
12658
12910
|
>
|
|
12659
12911
|
{option.icon}
|
|
12912
|
+
|
|
12660
12913
|
{option.label}
|
|
12914
|
+
|
|
12661
12915
|
{option.value === value && (
|
|
12662
12916
|
<Check className="!text-current absolute right-2" aria-hidden="true" />
|
|
12663
12917
|
)}
|
|
@@ -12844,7 +13098,7 @@ export function SidebarLayout({
|
|
|
12844
13098
|
);
|
|
12845
13099
|
}
|
|
12846
13100
|
|
|
12847
|
-
type SidebarItem = {
|
|
13101
|
+
export type SidebarItem = {
|
|
12848
13102
|
/** What \`current\` and \`onSelect\` refer to. Default: \`href\`, else \`label\`. */
|
|
12849
13103
|
id?: string;
|
|
12850
13104
|
/** Visible text; the rail shows it as a tooltip. */
|
|
@@ -12857,7 +13111,7 @@ type SidebarItem = {
|
|
|
12857
13111
|
badge?: ReactNode;
|
|
12858
13112
|
};
|
|
12859
13113
|
|
|
12860
|
-
type SidebarGroup = {
|
|
13114
|
+
export type SidebarGroup = {
|
|
12861
13115
|
/** Heading above the items; fades out in the rail. */
|
|
12862
13116
|
label?: string;
|
|
12863
13117
|
/** The group's entries. */
|
|
@@ -12921,6 +13175,7 @@ export function Sidebar({
|
|
|
12921
13175
|
const body = (
|
|
12922
13176
|
<nav aria-label={label} className="flex min-h-0 flex-1 flex-col">
|
|
12923
13177
|
{header && <div className="border-b p-2">{header}</div>}
|
|
13178
|
+
|
|
12924
13179
|
<div className="flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto overflow-x-hidden p-2">
|
|
12925
13180
|
{groups.map((group, i) =>
|
|
12926
13181
|
group.collapsible && group.label ? (
|
|
@@ -12929,6 +13184,7 @@ export function Sidebar({
|
|
|
12929
13184
|
<CollapsibleTrigger className={cn("px-2.5 pb-1 text-muted-foreground text-xs", fade)}>
|
|
12930
13185
|
{group.label}
|
|
12931
13186
|
</CollapsibleTrigger>
|
|
13187
|
+
|
|
12932
13188
|
<CollapsibleContent>{list(group.items)}</CollapsibleContent>
|
|
12933
13189
|
</Collapsible>
|
|
12934
13190
|
) : (
|
|
@@ -12944,11 +13200,13 @@ export function Sidebar({
|
|
|
12944
13200
|
{group.label}
|
|
12945
13201
|
</p>
|
|
12946
13202
|
)}
|
|
13203
|
+
|
|
12947
13204
|
{list(group.items)}
|
|
12948
13205
|
</div>
|
|
12949
13206
|
),
|
|
12950
13207
|
)}
|
|
12951
13208
|
</div>
|
|
13209
|
+
|
|
12952
13210
|
{footer?.length ? <div className="border-t p-2">{list(footer)}</div> : null}
|
|
12953
13211
|
</nav>
|
|
12954
13212
|
);
|
|
@@ -12958,6 +13216,7 @@ export function Sidebar({
|
|
|
12958
13216
|
<Drawer open={openMobile} onOpenChange={setOpenMobile}>
|
|
12959
13217
|
<DrawerContent side="left" className={cn("bg-background pt-12 text-sm", className)}>
|
|
12960
13218
|
<DrawerTitle className="sr-only">{label}</DrawerTitle>
|
|
13219
|
+
|
|
12961
13220
|
{body}
|
|
12962
13221
|
</DrawerContent>
|
|
12963
13222
|
</Drawer>
|
|
@@ -12999,7 +13258,9 @@ function Entry({
|
|
|
12999
13258
|
const body = (
|
|
13000
13259
|
<>
|
|
13001
13260
|
{item.icon}
|
|
13261
|
+
|
|
13002
13262
|
<span className="min-w-0 flex-1 truncate">{item.label}</span>
|
|
13263
|
+
|
|
13003
13264
|
{item.badge != null && (
|
|
13004
13265
|
<span className="ml-auto text-muted-foreground text-xs tabular-nums">{item.badge}</span>
|
|
13005
13266
|
)}
|
|
@@ -13096,8 +13357,8 @@ import {
|
|
|
13096
13357
|
import { useDisclosure } from "@/lib/use-disclosure";
|
|
13097
13358
|
import { cn } from "@/lib/utils";
|
|
13098
13359
|
|
|
13099
|
-
type SiteLink = { label: string; href: string };
|
|
13100
|
-
type SiteGroup = {
|
|
13360
|
+
export type SiteLink = { label: string; href: string };
|
|
13361
|
+
export type SiteGroup = {
|
|
13101
13362
|
/** Trigger text for the panel. */
|
|
13102
13363
|
label: string;
|
|
13103
13364
|
/** Links in the panel, each with an optional description and icon. */
|
|
@@ -13162,6 +13423,7 @@ export function SiteHeader({
|
|
|
13162
13423
|
return (
|
|
13163
13424
|
<>
|
|
13164
13425
|
<span ref={sentinel} aria-hidden="true" className="block h-px" />
|
|
13426
|
+
|
|
13165
13427
|
<header
|
|
13166
13428
|
ref={header}
|
|
13167
13429
|
className={cn(
|
|
@@ -13178,6 +13440,7 @@ export function SiteHeader({
|
|
|
13178
13440
|
isGroup(link) ? (
|
|
13179
13441
|
<NavigationMenuItem key={link.label}>
|
|
13180
13442
|
<NavigationMenuTrigger>{link.label}</NavigationMenuTrigger>
|
|
13443
|
+
|
|
13181
13444
|
<NavigationMenuContent>
|
|
13182
13445
|
<ul
|
|
13183
13446
|
className={cn(
|
|
@@ -13219,6 +13482,7 @@ export function SiteHeader({
|
|
|
13219
13482
|
|
|
13220
13483
|
<div className="ml-auto flex items-center gap-2">
|
|
13221
13484
|
{actions}
|
|
13485
|
+
|
|
13222
13486
|
<Button
|
|
13223
13487
|
variant="ghost"
|
|
13224
13488
|
size="icon"
|
|
@@ -13238,6 +13502,7 @@ export function SiteHeader({
|
|
|
13238
13502
|
<DrawerHeader>
|
|
13239
13503
|
<DrawerTitle>Menu</DrawerTitle>
|
|
13240
13504
|
</DrawerHeader>
|
|
13505
|
+
|
|
13241
13506
|
<DrawerBody>
|
|
13242
13507
|
<nav aria-label={label} className="flex flex-col gap-5">
|
|
13243
13508
|
{nav.map((link) =>
|
|
@@ -13246,6 +13511,7 @@ export function SiteHeader({
|
|
|
13246
13511
|
<h2 className="px-2.5 font-medium text-muted-foreground text-xs">
|
|
13247
13512
|
{link.label}
|
|
13248
13513
|
</h2>
|
|
13514
|
+
|
|
13249
13515
|
<ul className="flex flex-col">
|
|
13250
13516
|
{link.items.map((i) => (
|
|
13251
13517
|
<li key={i.href}>
|
|
@@ -13307,6 +13573,7 @@ function MobileLink({
|
|
|
13307
13573
|
className="flex h-10 items-center gap-3 rounded-lg px-2.5 font-medium text-sm outline-none transition-colors hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-[current=page]:bg-foreground/[0.08] [&_svg]:size-4 [&_svg]:text-primary-ink"
|
|
13308
13574
|
>
|
|
13309
13575
|
{icon && <span aria-hidden="true">{icon}</span>}
|
|
13576
|
+
|
|
13310
13577
|
{children}
|
|
13311
13578
|
</a>
|
|
13312
13579
|
);
|
|
@@ -13355,6 +13622,7 @@ export function SkeletonGroup({
|
|
|
13355
13622
|
return (
|
|
13356
13623
|
<div role="status" aria-busy="true" className={className} {...props}>
|
|
13357
13624
|
<span className="sr-only">{label}</span>
|
|
13625
|
+
|
|
13358
13626
|
{children}
|
|
13359
13627
|
</div>
|
|
13360
13628
|
);
|
|
@@ -13372,7 +13640,7 @@ export function PriceFilter() {
|
|
|
13372
13640
|
<Slider value={price} onValueChange={setPrice} min={0} max={250} step={5} name="price" />
|
|
13373
13641
|
</Field>
|
|
13374
13642
|
);
|
|
13375
|
-
}` }], api: { entries: [{ kind: "component", name: "Slider", props: [{ name: "aria-describedby", type: "string", required: false, description: "Ids of hint or error text; Field fills it in." }, { name: "aria-invalid", type: "boolean", required: false, description: "Error styling; Field sets it from `error`." }, { name: "defaultValue", type: "number[]", required: false, description: "Values at first render (uncontrolled)." }, { name: "disabled", type: "boolean", required: false, description: "Blocks dragging and keys." }, { name: "id", type: "string", required: false, description: "Id of the first thumb; Field fills it in." }, { name: "labels", type: "string[]", required: false, description: 'Accessible names per thumb; defaults to "Minimum" / "Maximum" for a range.' }, { name: "max", type: "number", required: false, description: "Highest value." }, { name: "min", type: "number", required: false, description: "Lowest value." }, { name: "name", type: "string", required: false, description: "Submitted per thumb: `name` (one value) or `name` twice (range)." }, { name: "onValueChange", type: "(value: number[]) => void", required: false, description: "Called with every thumb's value while dragging." }, { name: "step", type: "number", required: false, description: "Increment for drag and arrow keys." }, { name: "value", type: "number[]", required: false, description: "One value, or two for a range (e.g. a price filter)." }], description: `Slider over native range inputs: keyboard, forms, touch and screen readers work as the
|
|
13643
|
+
}` }], api: { entries: [{ kind: "component", name: "Slider", props: [{ name: "aria-describedby", type: "string", required: false, description: "Ids of hint or error text; Field fills it in." }, { name: "aria-invalid", type: "boolean", required: false, description: "Error styling; Field sets it from `error`." }, { name: "defaultValue", type: "number[]", required: false, description: "Values at first render (uncontrolled)." }, { name: "disabled", type: "boolean", required: false, description: "Blocks dragging and keys; the values aren't submitted." }, { name: "id", type: "string", required: false, description: "Id of the first thumb; Field fills it in." }, { name: "labels", type: "string[]", required: false, description: 'Accessible names per thumb; defaults to "Minimum" / "Maximum" for a range.' }, { name: "max", type: "number", required: false, description: "Highest value." }, { name: "min", type: "number", required: false, description: "Lowest value." }, { name: "name", type: "string", required: false, description: "Submitted per thumb: `name` (one value) or `name` twice (range)." }, { name: "onValueChange", type: "(value: number[]) => void", required: false, description: "Called with every thumb's value while dragging." }, { name: "step", type: "number", required: false, description: "Increment for drag and arrow keys." }, { name: "value", type: "number[]", required: false, description: "One value, or two for a range (e.g. a price filter)." }], description: `Slider over native range inputs: keyboard, forms, touch and screen readers work as the
|
|
13376
13644
|
browser does. Thin accent track, white knob that lifts while held. Two values = range.` }], types: {} }, dependencies: ["motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/field.json", "https://kznlabs.github.io/labsUI/r/use-controllable-state.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/slider.tsx", type: "registry:ui", content: `"use client";
|
|
13377
13645
|
|
|
13378
13646
|
import { motion } from "motion/react";
|
|
@@ -13396,7 +13664,7 @@ type SliderProps = {
|
|
|
13396
13664
|
step?: number;
|
|
13397
13665
|
/** Submitted per thumb: \`name\` (one value) or \`name\` twice (range). */
|
|
13398
13666
|
name?: string;
|
|
13399
|
-
/** Blocks dragging and keys. */
|
|
13667
|
+
/** Blocks dragging and keys; the values aren't submitted. */
|
|
13400
13668
|
disabled?: boolean;
|
|
13401
13669
|
/** Accessible names per thumb; defaults to "Minimum" / "Maximum" for a range. */
|
|
13402
13670
|
labels?: string[];
|
|
@@ -13475,6 +13743,7 @@ export function Slider(allProps: SliderProps) {
|
|
|
13475
13743
|
}}
|
|
13476
13744
|
/>
|
|
13477
13745
|
</div>
|
|
13746
|
+
|
|
13478
13747
|
{values.map((value, i) => (
|
|
13479
13748
|
<motion.span
|
|
13480
13749
|
// biome-ignore lint/suspicious/noArrayIndexKey: thumbs are positional (min, max)
|
|
@@ -13489,6 +13758,7 @@ export function Slider(allProps: SliderProps) {
|
|
|
13489
13758
|
)}
|
|
13490
13759
|
/>
|
|
13491
13760
|
))}
|
|
13761
|
+
|
|
13492
13762
|
{values.map((thumbValue, i) => (
|
|
13493
13763
|
<input
|
|
13494
13764
|
// biome-ignore lint/suspicious/noArrayIndexKey: thumbs are positional (min, max)
|
|
@@ -13571,6 +13841,7 @@ export function Spinner({
|
|
|
13571
13841
|
{...props}
|
|
13572
13842
|
>
|
|
13573
13843
|
{label && <span className="sr-only">{label}</span>}
|
|
13844
|
+
|
|
13574
13845
|
{spokes.map((i) => (
|
|
13575
13846
|
<span
|
|
13576
13847
|
key={i}
|
|
@@ -13671,10 +13942,12 @@ export function StatTile({
|
|
|
13671
13942
|
<Card className={cn("gap-4 pb-4", className)}>
|
|
13672
13943
|
<CardHeader>
|
|
13673
13944
|
<CardDescription>{label}</CardDescription>
|
|
13945
|
+
|
|
13674
13946
|
<div className="flex flex-wrap items-center gap-2">
|
|
13675
13947
|
<CardTitle as="p" className="font-semibold text-3xl tabular-nums">
|
|
13676
13948
|
{value}
|
|
13677
13949
|
</CardTitle>
|
|
13950
|
+
|
|
13678
13951
|
{delta !== undefined && delta !== 0 && (
|
|
13679
13952
|
<Badge variant={good ? "success" : "destructive"} shape="pill">
|
|
13680
13953
|
<Icon aria-hidden="true" />
|
|
@@ -13683,12 +13956,14 @@ export function StatTile({
|
|
|
13683
13956
|
</Badge>
|
|
13684
13957
|
)}
|
|
13685
13958
|
</div>
|
|
13959
|
+
|
|
13686
13960
|
{period && (
|
|
13687
13961
|
<p aria-hidden="true" className="text-muted-foreground text-xs">
|
|
13688
13962
|
{period}
|
|
13689
13963
|
</p>
|
|
13690
13964
|
)}
|
|
13691
13965
|
</CardHeader>
|
|
13966
|
+
|
|
13692
13967
|
{data && data.length > 1 && (
|
|
13693
13968
|
<Chart
|
|
13694
13969
|
type="area"
|
|
@@ -13716,14 +13991,14 @@ export function LandingStats({ users, uptime }: { users: number; uptime: number
|
|
|
13716
13991
|
]}
|
|
13717
13992
|
/>
|
|
13718
13993
|
);
|
|
13719
|
-
}` }], api: { entries: [{ kind: "component", name: "StatsStrip", props: [{ name: "stats", type: "
|
|
13994
|
+
}` }], api: { entries: [{ kind: "component", name: "StatsStrip", props: [{ name: "stats", type: "StatsStripItem[]", required: true, description: "Numbers in display order." }], description: "A row of headline numbers: each card rises in turn, then its number springs up. Each card is a <dl>." }], types: { StatsStripItem: "{ label: string; value: number; prefix?: string; suffix?: string; decimals?: number; hint?: string; }" } }, dependencies: [], registryDependencies: ["https://kznlabs.github.io/labsUI/r/card.json", "https://kznlabs.github.io/labsUI/r/count-to.json", "https://kznlabs.github.io/labsUI/r/reveal.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/blocks/stats-strip.tsx", type: "registry:block", content: `import { Card } from "@/components/ui/card";
|
|
13720
13995
|
import { CountTo } from "@/components/ui/count-to";
|
|
13721
13996
|
import { Reveal } from "@/components/ui/reveal";
|
|
13722
13997
|
import { cn } from "@/lib/utils";
|
|
13723
13998
|
|
|
13724
13999
|
// Server-safe: CountTo and Reveal are the client parts.
|
|
13725
14000
|
|
|
13726
|
-
type
|
|
14001
|
+
export type StatsStripItem = {
|
|
13727
14002
|
/** What the number counts, e.g. "Active users". */
|
|
13728
14003
|
label: string;
|
|
13729
14004
|
/** Number to count up to. */
|
|
@@ -13744,7 +14019,7 @@ export function StatsStrip({
|
|
|
13744
14019
|
className,
|
|
13745
14020
|
}: {
|
|
13746
14021
|
/** Numbers in display order. */
|
|
13747
|
-
stats:
|
|
14022
|
+
stats: StatsStripItem[];
|
|
13748
14023
|
className?: string;
|
|
13749
14024
|
}) {
|
|
13750
14025
|
return (
|
|
@@ -13759,6 +14034,7 @@ export function StatsStrip({
|
|
|
13759
14034
|
<Card className="h-full px-5 py-4">
|
|
13760
14035
|
<dl className="flex flex-col gap-1">
|
|
13761
14036
|
<dt className="text-muted-foreground text-sm">{stat.label}</dt>
|
|
14037
|
+
|
|
13762
14038
|
<dd className="font-semibold text-3xl tabular-nums tracking-tight">
|
|
13763
14039
|
<CountTo
|
|
13764
14040
|
to={stat.value}
|
|
@@ -13767,6 +14043,7 @@ export function StatsStrip({
|
|
|
13767
14043
|
decimals={stat.decimals}
|
|
13768
14044
|
/>
|
|
13769
14045
|
</dd>
|
|
14046
|
+
|
|
13770
14047
|
{stat.hint && <dd className="text-muted-foreground text-xs">{stat.hint}</dd>}
|
|
13771
14048
|
</dl>
|
|
13772
14049
|
</Card>
|
|
@@ -13778,7 +14055,7 @@ export function StatsStrip({
|
|
|
13778
14055
|
` }] }, stepper: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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.", docs: "https://kznlabs.github.io/labsUI/components/stepper", usage: '<Stepper steps={[{ value: "server", title: "Server" }, { value: "bot", title: "Bot" }]} action={createBot}><StepperList /><StepperStep value="server"><Input name="serverId" required /></StepperStep><StepperStep value="bot">…</StepperStep><StepperFooter submitLabel="Create bot" /></Stepper>', examples: [{ name: "setup-wizard.tsx", code: `import { Field } from "@/components/ui/field";
|
|
13779
14056
|
import { Input } from "@/components/ui/input";
|
|
13780
14057
|
import { Stepper, StepperFooter, StepperList, StepperStep } from "@/components/ui/stepper";
|
|
13781
|
-
import { createBot } from "./actions";
|
|
14058
|
+
import { createBot } from "./actions";
|
|
13782
14059
|
|
|
13783
14060
|
const steps = [
|
|
13784
14061
|
{ value: "server", title: "Server", description: "Where the bot will live." },
|
|
@@ -13803,7 +14080,14 @@ export default function SetupPage() {
|
|
|
13803
14080
|
<StepperFooter submitLabel="Create bot" />
|
|
13804
14081
|
</Stepper>
|
|
13805
14082
|
);
|
|
13806
|
-
}` }
|
|
14083
|
+
}` }, { name: "actions.ts", code: `"use server";
|
|
14084
|
+
|
|
14085
|
+
export async function createBot(formData: FormData) {
|
|
14086
|
+
const serverId = String(formData.get("serverId"));
|
|
14087
|
+
const botName = String(formData.get("botName"));
|
|
14088
|
+
|
|
14089
|
+
console.log({ serverId, botName });
|
|
14090
|
+
}` }], api: { entries: [{ kind: "component", name: "Stepper", props: [{ name: "steps", type: "StepInfo[]", required: true, description: "Steps in order: `value`, `title` and an optional `description` for the header." }, { name: "defaultValue", type: "string", required: false, description: "Step shown at first render (uncontrolled)." }, { name: "onValueChange", type: "(value: string) => void", required: false, description: "Called with the new step's `value`." }, { name: "value", type: "string", required: false, description: "Current step's `value`. Controlled with `onValueChange`, or start at `defaultValue`." }], extends: "<form>", description: "Multi-step form. Every step stays mounted (hidden), so values survive Back and submit\ntogether; Continue checks only the current step's native constraints, and the last step\nsubmits the form (`onSubmit`, or a Server Action through `action`)." }, { kind: "component", name: "StepperList", props: [], extends: "<ol>", description: "Progress header. Finished steps are links back; the connector fills on a spring." }, { kind: "component", name: "StepperStep", props: [{ name: "children", type: "ReactNode", required: true, description: "The step's fields." }, { name: "value", type: "string", required: true, description: "Matches a step in `steps`." }, { name: "onNext", type: "StepGuard", required: false, description: "Runs on Continue after the native checks; return false to stay." }], description: "One step's fields. Hidden, not unmounted, while inactive. `onNext` runs after its native\nconstraints pass; return false (or resolve false) to stay, e.g. after a server check." }, { kind: "component", name: "StepperFooter", props: [{ name: "backLabel", type: "ReactNode", required: false, default: '"Back"', description: "Back button text." }, { name: "nextLabel", type: "ReactNode", required: false, default: '"Continue"', description: "Continue button text." }, { name: "submitLabel", type: "ReactNode", required: false, default: '"Submit"', description: "Submit button text on the last step." }], description: "Back and Continue; the last step's button submits. Continue shows a spinner while `onNext` runs." }], types: { StepInfo: "{ value: string; title: string; description?: string }", StepGuard: "() => boolean | undefined | Promise<boolean | undefined>" } }, 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/use-controllable-state.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/stepper.tsx", type: "registry:ui", content: `"use client";
|
|
13807
14091
|
|
|
13808
14092
|
import { Check } from "lucide-react";
|
|
13809
14093
|
import { motion } from "motion/react";
|
|
@@ -13820,8 +14104,8 @@ import { Button } from "@/components/ui/button";
|
|
|
13820
14104
|
import { useControllableState } from "@/lib/use-controllable-state";
|
|
13821
14105
|
import { cn, spring } from "@/lib/utils";
|
|
13822
14106
|
|
|
13823
|
-
type StepInfo = { value: string; title: string; description?: string };
|
|
13824
|
-
type
|
|
14107
|
+
export type StepInfo = { value: string; title: string; description?: string };
|
|
14108
|
+
export type StepGuard = () => boolean | undefined | Promise<boolean | undefined>;
|
|
13825
14109
|
|
|
13826
14110
|
type StepperCtx = {
|
|
13827
14111
|
steps: StepInfo[];
|
|
@@ -13830,7 +14114,7 @@ type StepperCtx = {
|
|
|
13830
14114
|
next: () => Promise<void>;
|
|
13831
14115
|
back: () => void;
|
|
13832
14116
|
fieldsets: Map<string, HTMLFieldSetElement>;
|
|
13833
|
-
guards: Map<string,
|
|
14117
|
+
guards: Map<string, StepGuard>;
|
|
13834
14118
|
};
|
|
13835
14119
|
|
|
13836
14120
|
const StepperContext = createContext<StepperCtx | null>(null);
|
|
@@ -13893,7 +14177,7 @@ export function Stepper({
|
|
|
13893
14177
|
steps.findIndex((step) => step.value === value),
|
|
13894
14178
|
);
|
|
13895
14179
|
const fieldsets = useRef(new Map<string, HTMLFieldSetElement>()).current;
|
|
13896
|
-
const guards = useRef(new Map<string,
|
|
14180
|
+
const guards = useRef(new Map<string, StepGuard>()).current;
|
|
13897
14181
|
const moved = useRef(false);
|
|
13898
14182
|
|
|
13899
14183
|
const go = (i: number) => {
|
|
@@ -14006,15 +14290,19 @@ export function StepperList({ className, ...props }: ComponentProps<"ol">) {
|
|
|
14006
14290
|
className="flex cursor-pointer items-center gap-2 rounded-full pr-1 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
|
14007
14291
|
>
|
|
14008
14292
|
{mark}
|
|
14293
|
+
|
|
14009
14294
|
{label}
|
|
14295
|
+
|
|
14010
14296
|
<span className="sr-only"> (completed, go back)</span>
|
|
14011
14297
|
</button>
|
|
14012
14298
|
) : (
|
|
14013
14299
|
<span className="flex items-center gap-2 pr-1">
|
|
14014
14300
|
{mark}
|
|
14301
|
+
|
|
14015
14302
|
{label}
|
|
14016
14303
|
</span>
|
|
14017
14304
|
)}
|
|
14305
|
+
|
|
14018
14306
|
{i < steps.length - 1 && (
|
|
14019
14307
|
<span aria-hidden="true" className="relative mx-2 h-px flex-1 bg-border">
|
|
14020
14308
|
<motion.span
|
|
@@ -14029,6 +14317,7 @@ export function StepperList({ className, ...props }: ComponentProps<"ol">) {
|
|
|
14029
14317
|
);
|
|
14030
14318
|
})}
|
|
14031
14319
|
</ol>
|
|
14320
|
+
|
|
14032
14321
|
{/* Small screens hide the titles; this keeps the current one (and announces moves). */}
|
|
14033
14322
|
<p aria-live="polite" className="text-muted-foreground text-xs sm:sr-only">
|
|
14034
14323
|
Step {index + 1} of {steps.length}
|
|
@@ -14051,7 +14340,7 @@ export function StepperStep({
|
|
|
14051
14340
|
/** Matches a step in \`steps\`. */
|
|
14052
14341
|
value: string;
|
|
14053
14342
|
/** Runs on Continue after the native checks; return false to stay. */
|
|
14054
|
-
onNext?:
|
|
14343
|
+
onNext?: StepGuard;
|
|
14055
14344
|
className?: string;
|
|
14056
14345
|
/** The step's fields. */
|
|
14057
14346
|
children: ReactNode;
|
|
@@ -14087,11 +14376,13 @@ export function StepperStep({
|
|
|
14087
14376
|
{step && (
|
|
14088
14377
|
<legend className="mb-4 grid gap-1">
|
|
14089
14378
|
<span className="font-semibold text-lg leading-none">{step.title}</span>
|
|
14379
|
+
|
|
14090
14380
|
{step.description && (
|
|
14091
14381
|
<span className="text-muted-foreground text-sm">{step.description}</span>
|
|
14092
14382
|
)}
|
|
14093
14383
|
</legend>
|
|
14094
14384
|
)}
|
|
14385
|
+
|
|
14095
14386
|
{children}
|
|
14096
14387
|
</motion.fieldset>
|
|
14097
14388
|
);
|
|
@@ -14120,6 +14411,7 @@ export function StepperFooter({
|
|
|
14120
14411
|
<Button variant="ghost" onClick={back} className={cn(index === 0 && "invisible")}>
|
|
14121
14412
|
{backLabel}
|
|
14122
14413
|
</Button>
|
|
14414
|
+
|
|
14123
14415
|
{last ? (
|
|
14124
14416
|
<Button type="submit">{submitLabel}</Button>
|
|
14125
14417
|
) : (
|
|
@@ -14552,7 +14844,7 @@ export function StepperFooter({
|
|
|
14552
14844
|
display: inline !important;
|
|
14553
14845
|
}
|
|
14554
14846
|
}
|
|
14555
|
-
` }] }, switch: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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.", docs: "https://kznlabs.github.io/labsUI/components/forms", usage: '<Label htmlFor="email">Email</Label><Input id="email" type="email" aria-invalid={err} />', examples: [{ name: "sign-up.tsx", code: `import { Input } from "@/components/ui/input";
|
|
14847
|
+
` }] }, switch: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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.", 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";
|
|
14556
14848
|
import { Label } from "@/components/ui/label";
|
|
14557
14849
|
import { Switch } from "@/components/ui/switch";
|
|
14558
14850
|
import { Textarea } from "@/components/ui/textarea";
|
|
@@ -14613,6 +14905,7 @@ export function Switch({
|
|
|
14613
14905
|
className="peer absolute inset-x-0 -inset-y-1 z-10 cursor-pointer appearance-none rounded-full outline-none disabled:cursor-not-allowed"
|
|
14614
14906
|
{...props}
|
|
14615
14907
|
/>
|
|
14908
|
+
|
|
14616
14909
|
<span
|
|
14617
14910
|
aria-hidden="true"
|
|
14618
14911
|
className={cn(
|
|
@@ -14705,6 +14998,7 @@ export function Table({ striped = false, className, containerClassName, ...props
|
|
|
14705
14998
|
)}
|
|
14706
14999
|
{...props}
|
|
14707
15000
|
/>
|
|
15001
|
+
|
|
14708
15002
|
<OverflowFocus />
|
|
14709
15003
|
</div>
|
|
14710
15004
|
);
|
|
@@ -14767,6 +15061,7 @@ export function TableHead({ sort, onSort, className, children, ...props }: Table
|
|
|
14767
15061
|
className="group -mx-1.5 inline-flex h-7 items-center gap-1 rounded-md px-1.5 outline-none transition-colors hover:bg-accent hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 in-[.text-right]:flex-row-reverse"
|
|
14768
15062
|
>
|
|
14769
15063
|
{children}
|
|
15064
|
+
|
|
14770
15065
|
<ArrowUp
|
|
14771
15066
|
aria-hidden="true"
|
|
14772
15067
|
className={cn(
|
|
@@ -14933,6 +15228,7 @@ export function TabsList({ className, children, ...props }: ComponentProps<"div"
|
|
|
14933
15228
|
)}
|
|
14934
15229
|
/>
|
|
14935
15230
|
)}
|
|
15231
|
+
|
|
14936
15232
|
{children}
|
|
14937
15233
|
</div>
|
|
14938
15234
|
);
|
|
@@ -15010,7 +15306,7 @@ export function TabsContent({
|
|
|
15010
15306
|
</motion.div>
|
|
15011
15307
|
);
|
|
15012
15308
|
}
|
|
15013
|
-
` }] }, "tag-input": { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", name: "tag-input", type: "registry:ui", title: "Tag Input", description: "Free-form tags: Enter or a separator adds, Backspace removes the last, pasting a list adds them all. Duplicates, a max and a validate function keep bad tags in the input.
|
|
15309
|
+
` }] }, "tag-input": { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", name: "tag-input", type: "registry:ui", title: "Tag Input", description: "Free-form tags: Enter or a separator adds, Backspace removes the last, 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.", docs: "https://kznlabs.github.io/labsUI/components/tag-input", usage: "<TagInput value={tags} onValueChange={setTags} max={5} validate={(t) => t.length <= 24} />", examples: [{ name: "clip-tags.tsx", code: `"use client";
|
|
15014
15310
|
|
|
15015
15311
|
import { useState } from "react";
|
|
15016
15312
|
import { Field } from "@/components/ui/field";
|
|
@@ -15030,12 +15326,13 @@ export function ClipTags() {
|
|
|
15030
15326
|
/>
|
|
15031
15327
|
</Field>
|
|
15032
15328
|
);
|
|
15033
|
-
}` }], api: { entries: [{ kind: "component", name: "Tag", props: [{ name: "children", type: "ReactNode", required: true, description: "Tag text." }, { name: "disabled", type: "boolean", required: false, description: "Hides the remove button." }, { name: "onRemove", type: "() => void", required: false, description: 'Shows a remove button; its accessible name is "Remove <children>".' }], description: 'Removable chip. Springs in and out when rendered inside AnimatePresence (`mode="popLayout"`).' }, { kind: "component", name: "TagInput", props: [{ name: "defaultValue", type: "string[]", required: false, default: "[]", description: "Tags at first render (uncontrolled)." }, { name: "max", type: "number", required: false, description: "Most tags allowed; typing stops at the limit." }, { name: "onValueChange", type: "(tags: string[]) => void", required: false, description: "Called with every tag after an add or remove." }, { name: "separators", type: "string[]", required: false, default: '[","]', description: "Keys that commit the typed text, besides Enter. Pasted text is split on these too." }, { name: "validate", type: "(tag: string) => boolean", required: false, description: "Return false to reject a tag (it stays in the input). Runs after trimming." }, { name: "value", type: "string[]", required: false, description: "Controlled tags; pair with `onValueChange`." }], extends: "<input>", description: "Free-form tags: type and press Enter or a separator; paste a list to add many; Backspace in an\nempty input removes the last tag. `name` submits one
|
|
15329
|
+
}` }], api: { entries: [{ kind: "component", name: "Tag", props: [{ name: "children", type: "ReactNode", required: true, description: "Tag text." }, { name: "disabled", type: "boolean", required: false, description: "Hides the remove button." }, { name: "onRemove", type: "() => void", required: false, description: 'Shows a remove button; its accessible name is "Remove <children>".' }], description: 'Removable chip. Springs in and out when rendered inside AnimatePresence (`mode="popLayout"`).' }, { kind: "component", name: "TagInput", props: [{ name: "defaultValue", type: "string[]", required: false, default: "[]", description: "Tags at first render (uncontrolled)." }, { name: "max", type: "number", required: false, description: "Most tags allowed; typing stops at the limit." }, { name: "onValueChange", type: "(tags: string[]) => void", required: false, description: "Called with every tag after an add or remove." }, { name: "separators", type: "string[]", required: false, default: '[","]', description: "Keys that commit the typed text, besides Enter. Pasted text is split on these too." }, { name: "validate", type: "(tag: string) => boolean", required: false, description: "Return false to reject a tag (it stays in the input). Runs after trimming." }, { name: "value", type: "string[]", required: false, description: "Controlled tags; pair with `onValueChange`." }], extends: "<input>", description: "Free-form tags: type and press Enter or a separator; paste a list to add many; Backspace in an\nempty input removes the last tag. `name` submits one value per tag; `required` needs at least one." }], types: {} }, dependencies: ["lucide-react@1.63.0", "motion@14.7.1"], registryDependencies: ["https://kznlabs.github.io/labsUI/r/field.json", "https://kznlabs.github.io/labsUI/r/form-value.json", "https://kznlabs.github.io/labsUI/r/use-controllable-state.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/tag-input.tsx", type: "registry:ui", content: `"use client";
|
|
15034
15330
|
|
|
15035
15331
|
import { X } from "lucide-react";
|
|
15036
15332
|
import { AnimatePresence, motion } from "motion/react";
|
|
15037
15333
|
import { type ComponentProps, type ReactNode, useRef, useState } from "react";
|
|
15038
15334
|
import { useFieldControl } from "@/components/ui/field";
|
|
15335
|
+
import { FormValue } from "@/lib/form-value";
|
|
15039
15336
|
import { useControllableState } from "@/lib/use-controllable-state";
|
|
15040
15337
|
import { cn, spring } from "@/lib/utils";
|
|
15041
15338
|
|
|
@@ -15072,6 +15369,7 @@ export function Tag({
|
|
|
15072
15369
|
)}
|
|
15073
15370
|
>
|
|
15074
15371
|
<span className="truncate">{children}</span>
|
|
15372
|
+
|
|
15075
15373
|
{onRemove && (
|
|
15076
15374
|
<button
|
|
15077
15375
|
type="button"
|
|
@@ -15111,7 +15409,7 @@ type TagInputProps = Omit<
|
|
|
15111
15409
|
|
|
15112
15410
|
/**
|
|
15113
15411
|
* Free-form tags: type and press Enter or a separator; paste a list to add many; Backspace in an
|
|
15114
|
-
* empty input removes the last tag. \`name\` submits one
|
|
15412
|
+
* empty input removes the last tag. \`name\` submits one value per tag; \`required\` needs at least one.
|
|
15115
15413
|
*/
|
|
15116
15414
|
export function TagInput({
|
|
15117
15415
|
value: valueProp,
|
|
@@ -15128,7 +15426,8 @@ export function TagInput({
|
|
|
15128
15426
|
onBlur,
|
|
15129
15427
|
...rest
|
|
15130
15428
|
}: TagInputProps) {
|
|
15131
|
-
const props = useFieldControl(rest);
|
|
15429
|
+
const { required, ...props } = useFieldControl(rest);
|
|
15430
|
+
const [invalid, setInvalid] = useState(false);
|
|
15132
15431
|
const input = useRef<HTMLInputElement>(null);
|
|
15133
15432
|
const [tags, set] = useControllableState(valueProp, defaultValue, onValueChange);
|
|
15134
15433
|
const [draft, setDraft] = useState("");
|
|
@@ -15177,11 +15476,14 @@ export function TagInput({
|
|
|
15177
15476
|
</Tag>
|
|
15178
15477
|
))}
|
|
15179
15478
|
</AnimatePresence>
|
|
15479
|
+
|
|
15180
15480
|
<input
|
|
15181
15481
|
ref={input}
|
|
15182
15482
|
type="text"
|
|
15183
15483
|
data-slot="tag-input"
|
|
15184
15484
|
{...props}
|
|
15485
|
+
aria-required={required}
|
|
15486
|
+
aria-invalid={props["aria-invalid"] || (invalid && !tags.length) || undefined}
|
|
15185
15487
|
// readOnly, not disabled: Backspace still has to remove tags at the limit.
|
|
15186
15488
|
readOnly={full || props.readOnly}
|
|
15187
15489
|
placeholder={full ? undefined : placeholder}
|
|
@@ -15216,7 +15518,19 @@ export function TagInput({
|
|
|
15216
15518
|
}}
|
|
15217
15519
|
className="h-6 min-w-20 flex-1 bg-transparent px-1 outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed"
|
|
15218
15520
|
/>
|
|
15219
|
-
|
|
15521
|
+
|
|
15522
|
+
{name &&
|
|
15523
|
+
tags.map((tag) => (
|
|
15524
|
+
<input key={tag} type="hidden" name={name} value={tag} disabled={props.disabled} />
|
|
15525
|
+
))}
|
|
15526
|
+
|
|
15527
|
+
<FormValue
|
|
15528
|
+
value={tags.join(",")}
|
|
15529
|
+
required={required}
|
|
15530
|
+
disabled={props.disabled}
|
|
15531
|
+
anchor={input}
|
|
15532
|
+
onInvalid={() => setInvalid(true)}
|
|
15533
|
+
/>
|
|
15220
15534
|
</div>
|
|
15221
15535
|
);
|
|
15222
15536
|
}
|
|
@@ -15318,15 +15632,19 @@ function Line({
|
|
|
15318
15632
|
{line.time && (
|
|
15319
15633
|
<span className="mr-3 text-muted-foreground tabular-nums select-none">{line.time}</span>
|
|
15320
15634
|
)}
|
|
15635
|
+
|
|
15321
15636
|
{line.kind === "command" && (
|
|
15322
15637
|
<span className="mr-2 text-muted-foreground select-none">{prompt}</span>
|
|
15323
15638
|
)}
|
|
15639
|
+
|
|
15324
15640
|
{kind.glyph && (
|
|
15325
15641
|
<span aria-hidden="true" className={cn("mr-2", kind.glyphClass)}>
|
|
15326
15642
|
{kind.glyph}
|
|
15327
15643
|
</span>
|
|
15328
15644
|
)}
|
|
15645
|
+
|
|
15329
15646
|
{text}
|
|
15647
|
+
|
|
15330
15648
|
{cursor && <Cursor />}
|
|
15331
15649
|
</div>
|
|
15332
15650
|
);
|
|
@@ -15458,6 +15776,7 @@ export function Terminal({
|
|
|
15458
15776
|
<Line key={key(item, i)} line={item} prompt={prompt} />
|
|
15459
15777
|
))}
|
|
15460
15778
|
</div>
|
|
15779
|
+
|
|
15461
15780
|
<div aria-hidden="true" data-typewriter-animated="">
|
|
15462
15781
|
{lines
|
|
15463
15782
|
.slice(0, at.line + 1)
|
|
@@ -15474,8 +15793,10 @@ export function Terminal({
|
|
|
15474
15793
|
/>
|
|
15475
15794
|
) : null,
|
|
15476
15795
|
)}
|
|
15796
|
+
|
|
15477
15797
|
{done && <Line line={{ kind: "command", text: "" }} prompt={prompt} cursor />}
|
|
15478
15798
|
</div>
|
|
15799
|
+
|
|
15479
15800
|
<div aria-hidden="true" data-typewriter-fallback="" className="hidden">
|
|
15480
15801
|
{lines.map((item, i) => (
|
|
15481
15802
|
<Line key={key(item, i)} line={item} prompt={prompt} />
|
|
@@ -15497,6 +15818,7 @@ export function Terminal({
|
|
|
15497
15818
|
lines.map((item, i) => <Line key={key(item, i)} line={item} prompt={prompt} />)
|
|
15498
15819
|
)}
|
|
15499
15820
|
</div>
|
|
15821
|
+
|
|
15500
15822
|
<AnimatePresence>
|
|
15501
15823
|
{log && behind && (
|
|
15502
15824
|
<motion.button
|
|
@@ -15582,9 +15904,11 @@ export function Testimonial({
|
|
|
15582
15904
|
>
|
|
15583
15905
|
{children}
|
|
15584
15906
|
</Quote>
|
|
15907
|
+
|
|
15585
15908
|
{others && others.length > 0 && (
|
|
15586
15909
|
<div className="flex flex-wrap items-center gap-3 border-t pt-5">
|
|
15587
15910
|
<AvatarGroup users={others} max={5} size="sm" />
|
|
15911
|
+
|
|
15588
15912
|
{othersLabel && <p className="text-muted-foreground text-sm">{othersLabel}</p>}
|
|
15589
15913
|
</div>
|
|
15590
15914
|
)}
|
|
@@ -15666,11 +15990,13 @@ export function TextSwap({
|
|
|
15666
15990
|
aria-live={isControlled ? "polite" : undefined}
|
|
15667
15991
|
>
|
|
15668
15992
|
<span className="sr-only">{currentItem}</span>
|
|
15993
|
+
|
|
15669
15994
|
{list.map((item) => (
|
|
15670
15995
|
<span key={item} aria-hidden="true" className="invisible [grid-area:1/1]">
|
|
15671
15996
|
{item}
|
|
15672
15997
|
</span>
|
|
15673
15998
|
))}
|
|
15999
|
+
|
|
15674
16000
|
<AnimatePresence mode="popLayout" initial={false}>
|
|
15675
16001
|
<motion.span
|
|
15676
16002
|
key={\`\${activeIndex}-\${currentItem}\`}
|
|
@@ -15695,7 +16021,7 @@ export function TextSwap({
|
|
|
15695
16021
|
</span>
|
|
15696
16022
|
);
|
|
15697
16023
|
}
|
|
15698
|
-
` }] }, 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} />', examples: [{ name: "sign-up.tsx", code: `import { Input } from "@/components/ui/input";
|
|
16024
|
+
` }] }, 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";
|
|
15699
16025
|
import { Label } from "@/components/ui/label";
|
|
15700
16026
|
import { Switch } from "@/components/ui/switch";
|
|
15701
16027
|
import { Textarea } from "@/components/ui/textarea";
|
|
@@ -15763,6 +16089,7 @@ export function Textarea({ className, showCount, onChange, ...rest }: TextareaPr
|
|
|
15763
16089
|
return (
|
|
15764
16090
|
<span className="relative block w-full min-w-0">
|
|
15765
16091
|
{textarea}
|
|
16092
|
+
|
|
15766
16093
|
<span
|
|
15767
16094
|
id={countId}
|
|
15768
16095
|
className={cn(
|
|
@@ -15780,7 +16107,7 @@ export function Textarea({ className, showCount, onChange, ...rest }: TextareaPr
|
|
|
15780
16107
|
</span>
|
|
15781
16108
|
);
|
|
15782
16109
|
}
|
|
15783
|
-
` }] }, theme: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", name: "theme", type: "registry:lib", description: "Retints primary, focus rings, selection and switches app-wide. Persists like the theme.", api: { entries: [{ kind: "function", name: "isDark", signature: "isDark() => boolean" }, { kind: "function", name: "setTheme", signature: "setTheme(theme: Theme) => void" }, { kind: "function", name: "subscribeTheme", signature: "subscribeTheme(callback: () => void) => () => void" }, { kind: "function", name: "getAccent", signature: 'getAccent() => "blue" | "green" | "orange" | "pink" | "purple" | "red" | "slate" | "yellow"' }, { kind: "function", name: "setAccent", signature: 'setAccent(accent: "blue" | "green" | "orange" | "pink" | "purple" | "red" | "slate" | "yellow") => void', description: "Retints primary, focus rings, selection and switches app-wide. Persists like the theme." }], types: { Theme: '"light" | "dark"' } }, dependencies: [], registryDependencies: [], files: [{ path: "lib/theme.ts", type: "registry:lib", content: `type Theme = "light" | "dark";
|
|
16110
|
+
` }] }, theme: { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", name: "theme", type: "registry:lib", description: "Retints primary, focus rings, selection and switches app-wide. Persists like the theme.", api: { entries: [{ kind: "function", name: "isDark", signature: "isDark() => boolean" }, { kind: "function", name: "setTheme", signature: "setTheme(theme: Theme) => void" }, { kind: "function", name: "subscribeTheme", signature: "subscribeTheme(callback: () => void) => () => void" }, { kind: "function", name: "getAccent", signature: 'getAccent() => "blue" | "green" | "orange" | "pink" | "purple" | "red" | "slate" | "yellow"' }, { kind: "function", name: "setAccent", signature: 'setAccent(accent: "blue" | "green" | "orange" | "pink" | "purple" | "red" | "slate" | "yellow") => void', description: "Retints primary, focus rings, selection and switches app-wide. Persists like the theme." }], types: { Theme: '"light" | "dark"' } }, dependencies: [], registryDependencies: [], files: [{ path: "lib/theme.ts", type: "registry:lib", content: `export type Theme = "light" | "dark";
|
|
15784
16111
|
|
|
15785
16112
|
const KEY = "theme";
|
|
15786
16113
|
const EVENT = "themechange";
|
|
@@ -15975,6 +16302,7 @@ export function TimelineItem({
|
|
|
15975
16302
|
aria-hidden="true"
|
|
15976
16303
|
className="absolute top-7 bottom-1 left-3 w-px -translate-x-1/2 bg-border group-last/item:hidden"
|
|
15977
16304
|
/>
|
|
16305
|
+
|
|
15978
16306
|
<span
|
|
15979
16307
|
aria-hidden="true"
|
|
15980
16308
|
className={cn(
|
|
@@ -15986,6 +16314,7 @@ export function TimelineItem({
|
|
|
15986
16314
|
>
|
|
15987
16315
|
{icon}
|
|
15988
16316
|
</span>
|
|
16317
|
+
|
|
15989
16318
|
<div className="grid min-w-0 gap-1 pt-0.5">
|
|
15990
16319
|
{time &&
|
|
15991
16320
|
(dateTime ? (
|
|
@@ -15995,7 +16324,9 @@ export function TimelineItem({
|
|
|
15995
16324
|
) : (
|
|
15996
16325
|
<span className="text-muted-foreground text-xs tabular-nums">{time}</span>
|
|
15997
16326
|
))}
|
|
16327
|
+
|
|
15998
16328
|
<p className="font-medium text-sm leading-snug">{title}</p>
|
|
16329
|
+
|
|
15999
16330
|
{children && (
|
|
16000
16331
|
<div className="text-muted-foreground text-sm leading-relaxed">{children}</div>
|
|
16001
16332
|
)}
|
|
@@ -16004,12 +16335,12 @@ export function TimelineItem({
|
|
|
16004
16335
|
);
|
|
16005
16336
|
}
|
|
16006
16337
|
` }] }, 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.", 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";
|
|
16007
|
-
export function notifySaved() {
|
|
16338
|
+
export function notifySaved(undo: () => void) {
|
|
16008
16339
|
toast.success("Changes saved", {
|
|
16009
16340
|
description: "All changes synced",
|
|
16010
16341
|
action: { label: "Undo", onClick: undo },
|
|
16011
16342
|
});
|
|
16012
|
-
}` }], 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." }], types: { ToastData: "{ id: number; title: ReactNode; description?: ReactNode; variant:
|
|
16343
|
+
}` }], 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." }], 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/use-dismiss.json", "https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "components/ui/toast.tsx", type: "registry:ui", content: `"use client";
|
|
16013
16344
|
|
|
16014
16345
|
import { CircleAlert, CircleCheck, Info, Loader2, X } from "lucide-react";
|
|
16015
16346
|
import { AnimatePresence, animate, motion, type PanInfo, useMotionValue } from "motion/react";
|
|
@@ -16026,12 +16357,12 @@ import { cn, material, spring } from "@/lib/utils";
|
|
|
16026
16357
|
|
|
16027
16358
|
/* ---------- store (module-level, no provider needed) ---------- */
|
|
16028
16359
|
|
|
16029
|
-
type
|
|
16030
|
-
type ToastData = {
|
|
16360
|
+
export type ToastVariant = "default" | "success" | "error" | "info" | "loading";
|
|
16361
|
+
export type ToastData = {
|
|
16031
16362
|
id: number;
|
|
16032
16363
|
title: ReactNode;
|
|
16033
16364
|
description?: ReactNode;
|
|
16034
|
-
variant:
|
|
16365
|
+
variant: ToastVariant;
|
|
16035
16366
|
/** ms; Infinity = sticky. */
|
|
16036
16367
|
duration: number;
|
|
16037
16368
|
action?: { label: string; onClick: () => void };
|
|
@@ -16301,13 +16632,16 @@ function ToastItem({ toast: data, index, y, expanded, frontHeight, total, onHeig
|
|
|
16301
16632
|
className="flex items-start gap-3 p-4"
|
|
16302
16633
|
>
|
|
16303
16634
|
{icons[data.variant] && <span className="mt-0.5">{icons[data.variant]}</span>}
|
|
16635
|
+
|
|
16304
16636
|
<div
|
|
16305
16637
|
className="flex min-w-0 flex-1 flex-col gap-0.5"
|
|
16306
16638
|
role={data.variant === "error" ? "alert" : undefined}
|
|
16307
16639
|
>
|
|
16308
16640
|
<p className="font-medium text-sm">{data.title}</p>
|
|
16641
|
+
|
|
16309
16642
|
{data.description && <p className="text-muted-foreground text-sm">{data.description}</p>}
|
|
16310
16643
|
</div>
|
|
16644
|
+
|
|
16311
16645
|
{data.action && (
|
|
16312
16646
|
<button
|
|
16313
16647
|
type="button"
|
|
@@ -16320,12 +16654,14 @@ function ToastItem({ toast: data, index, y, expanded, frontHeight, total, onHeig
|
|
|
16320
16654
|
{data.action.label}
|
|
16321
16655
|
</button>
|
|
16322
16656
|
)}
|
|
16657
|
+
|
|
16323
16658
|
<button
|
|
16324
16659
|
type="button"
|
|
16325
16660
|
onClick={() => dismiss(data.id)}
|
|
16326
16661
|
className="-mt-1 -mr-1 shrink-0 cursor-pointer rounded-md p-1 text-muted-foreground opacity-0 outline-none transition-opacity hover:text-foreground focus-visible:opacity-100 focus-visible:ring-[3px] focus-visible:ring-ring/50 group-hover:opacity-100"
|
|
16327
16662
|
>
|
|
16328
16663
|
<X className="size-3.5" aria-hidden="true" />
|
|
16664
|
+
|
|
16329
16665
|
<span className="sr-only">Dismiss</span>
|
|
16330
16666
|
</button>
|
|
16331
16667
|
</motion.div>
|
|
@@ -16471,6 +16807,7 @@ export function ToggleGroup(props: ToggleGroupProps) {
|
|
|
16471
16807
|
className="-z-10 absolute inset-y-0.5 left-0 rounded-md bg-raised shadow-sm ring-[0.5px] ring-raised-ring"
|
|
16472
16808
|
/>
|
|
16473
16809
|
)}
|
|
16810
|
+
|
|
16474
16811
|
{children}
|
|
16475
16812
|
</div>
|
|
16476
16813
|
</GroupContext>
|
|
@@ -16675,9 +17012,11 @@ export function Tooltip({
|
|
|
16675
17012
|
{...touchHandlers}
|
|
16676
17013
|
>
|
|
16677
17014
|
{children}
|
|
17015
|
+
|
|
16678
17016
|
<span id={id} hidden>
|
|
16679
17017
|
{content}
|
|
16680
17018
|
</span>
|
|
17019
|
+
|
|
16681
17020
|
{mounted &&
|
|
16682
17021
|
createPortal(
|
|
16683
17022
|
<AnimatePresence>
|
|
@@ -16850,12 +17189,15 @@ export function Typewriter({
|
|
|
16850
17189
|
return (
|
|
16851
17190
|
<span className={cn("inline-flex items-baseline", className)}>
|
|
16852
17191
|
<span className="sr-only">{fullText}</span>
|
|
17192
|
+
|
|
16853
17193
|
<span aria-hidden="true" data-typewriter-animated="">
|
|
16854
17194
|
{displayed}
|
|
16855
17195
|
</span>
|
|
17196
|
+
|
|
16856
17197
|
<span aria-hidden="true" data-typewriter-fallback="" className="hidden">
|
|
16857
17198
|
{phrases[0]}
|
|
16858
17199
|
</span>
|
|
17200
|
+
|
|
16859
17201
|
{cursor && (
|
|
16860
17202
|
<span
|
|
16861
17203
|
aria-hidden="true"
|
|
@@ -16874,24 +17216,24 @@ export function Typewriter({
|
|
|
16874
17216
|
flipped when it doesn't fit, clamped to the viewport, kept in place on scroll and resize.
|
|
16875
17217
|
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 }?: {
|
|
16876
17218
|
align?: "center" | "end" | "start" | undefined;
|
|
16877
|
-
side?:
|
|
17219
|
+
side?: AnchorSide | undefined;
|
|
16878
17220
|
offset?: number | undefined;
|
|
16879
|
-
}) =>
|
|
17221
|
+
}) => AnchorPlacement`, description: `Fixed-position coordinates for a floating panel next to its anchor: on the preferred side,
|
|
16880
17222
|
flipped when it doesn't fit, clamped to the viewport, kept in place on scroll and resize.
|
|
16881
|
-
Measures before paint, so nothing jumps.` }], types: {
|
|
17223
|
+
Measures before paint, so nothing jumps.` }], types: { AnchorSide: '"bottom" | "top"', AnchorPlacement: "{ top: number; left: number; width: number; side: AnchorSide; }" } }, dependencies: [], registryDependencies: ["https://kznlabs.github.io/labsUI/r/utils.json"], files: [{ path: "lib/use-anchor.ts", type: "registry:hook", content: `"use client";
|
|
16882
17224
|
|
|
16883
17225
|
import { type RefObject, useLayoutEffect, useState } from "react";
|
|
16884
17226
|
import { clamp } from "@/lib/utils";
|
|
16885
17227
|
|
|
16886
|
-
type
|
|
17228
|
+
export type AnchorSide = "bottom" | "top";
|
|
16887
17229
|
|
|
16888
|
-
type
|
|
17230
|
+
export type AnchorPlacement = {
|
|
16889
17231
|
top: number;
|
|
16890
17232
|
left: number;
|
|
16891
17233
|
/** Anchor width, for \`min-width\` matching (Select). */
|
|
16892
17234
|
width: number;
|
|
16893
17235
|
/** Side actually used: the preferred one, or the other for lack of room. */
|
|
16894
|
-
side:
|
|
17236
|
+
side: AnchorSide;
|
|
16895
17237
|
};
|
|
16896
17238
|
|
|
16897
17239
|
const MARGIN = 8;
|
|
@@ -16909,9 +17251,9 @@ export function useAnchor(
|
|
|
16909
17251
|
align = "start",
|
|
16910
17252
|
side: preferred = "bottom",
|
|
16911
17253
|
offset = 6,
|
|
16912
|
-
}: { align?: "start" | "center" | "end"; side?:
|
|
17254
|
+
}: { align?: "start" | "center" | "end"; side?: AnchorSide; offset?: number } = {},
|
|
16913
17255
|
) {
|
|
16914
|
-
const [pos, setPos] = useState<
|
|
17256
|
+
const [pos, setPos] = useState<AnchorPlacement>({
|
|
16915
17257
|
top: -9999,
|
|
16916
17258
|
left: -9999,
|
|
16917
17259
|
width: 0,
|
|
@@ -16934,7 +17276,7 @@ export function useAnchor(
|
|
|
16934
17276
|
const above = anchorRect.top - offset - height;
|
|
16935
17277
|
const fitsBelow = below + height <= innerHeight - MARGIN;
|
|
16936
17278
|
const fitsAbove = above >= MARGIN;
|
|
16937
|
-
const side:
|
|
17279
|
+
const side: AnchorSide =
|
|
16938
17280
|
preferred === "bottom"
|
|
16939
17281
|
? fitsBelow || !fitsAbove
|
|
16940
17282
|
? "bottom"
|
|
@@ -17021,17 +17363,17 @@ export function useDisclosure(defaultOpen = false) {
|
|
|
17021
17363
|
props: { open, onOpenChange: setOpen },
|
|
17022
17364
|
};
|
|
17023
17365
|
}
|
|
17024
|
-
` }] }, "use-dismiss": { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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`.", api: { entries: [{ kind: "function", name: "isWithin", signature: "isWithin(refs:
|
|
17366
|
+
` }] }, "use-dismiss": { $schema: "https://kznlabs.github.io/labsUI/r/registry-item.schema.json", 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`.", api: { entries: [{ kind: "function", name: "isWithin", signature: "isWithin(refs: DismissRefs, target: Node | null) => boolean", 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`." }, { kind: "function", name: "useDismiss", signature: "useDismiss(open: boolean, refs: DismissRefs, onDismiss: () => void) => void", description: "Closes a floating panel on a pointerdown outside it, its trigger and its floating children." }], types: { DismissRefs: "readonly RefObject<HTMLElement | null>[]" } }, dependencies: [], registryDependencies: [], files: [{ path: "lib/use-dismiss.ts", type: "registry:hook", content: `"use client";
|
|
17025
17367
|
|
|
17026
17368
|
import { type RefObject, useEffect, useEffectEvent } from "react";
|
|
17027
17369
|
|
|
17028
|
-
type
|
|
17370
|
+
export type DismissRefs = readonly RefObject<HTMLElement | null>[];
|
|
17029
17371
|
|
|
17030
17372
|
/**
|
|
17031
17373
|
* Inside one of \`refs\` (panel first, then its trigger), or inside a floating child opened from
|
|
17032
17374
|
* the panel: a Select list or menu portals out, but it is marked \`data-floating\`.
|
|
17033
17375
|
*/
|
|
17034
|
-
export function isWithin(refs:
|
|
17376
|
+
export function isWithin(refs: DismissRefs, target: Node | null) {
|
|
17035
17377
|
if (!target) return false;
|
|
17036
17378
|
if (refs.some((ref) => ref.current?.contains(target))) return true;
|
|
17037
17379
|
|
|
@@ -17041,7 +17383,7 @@ export function isWithin(refs: Refs, target: Node | null) {
|
|
|
17041
17383
|
}
|
|
17042
17384
|
|
|
17043
17385
|
/** Closes a floating panel on a pointerdown outside it, its trigger and its floating children. */
|
|
17044
|
-
export function useDismiss(open: boolean, refs:
|
|
17386
|
+
export function useDismiss(open: boolean, refs: DismissRefs, onDismiss: () => void) {
|
|
17045
17387
|
// Effect events, an inline refs array or callback can't re-subscribe every render.
|
|
17046
17388
|
const outside = useEffectEvent((event: PointerEvent) => {
|
|
17047
17389
|
if (!isWithin(refs, event.target as Node)) onDismiss();
|
|
@@ -17075,7 +17417,7 @@ export function matchesHotkey(
|
|
|
17075
17417
|
return mod ? event.metaKey || event.ctrlKey : !event.metaKey && !event.ctrlKey;
|
|
17076
17418
|
}
|
|
17077
17419
|
|
|
17078
|
-
type HotkeyOptions = {
|
|
17420
|
+
export type HotkeyOptions = {
|
|
17079
17421
|
/** Ignore the shortcut while typing in a field. Default: only for single keys. */
|
|
17080
17422
|
skipInFields?: boolean;
|
|
17081
17423
|
};
|
|
@@ -17248,9 +17590,9 @@ export const spring = { type: "spring", stiffness: 400, damping: 30 } as const;
|
|
|
17248
17590
|
export const clamp = (value: number, min: number, max: number) =>
|
|
17249
17591
|
Math.min(Math.max(value, min), max);
|
|
17250
17592
|
` }] } },
|
|
17251
|
-
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", "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", "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 through hidden inputs.", exports: ["Combobox"], keywords: ["accessible", "and", "aria", "backspace", "blocks", "called", "can", "choices", "closes", "controlled", "defaultvalue", "describedby", "disabled", "down", "emptytext", "enter", "error", "esc", "every", "field", "fills", "filtered", "filters", "first", "form", "from", "hidden", "hint", "ids", "input", "inputs", "into", "invalid", "keeps", "label", "last", "list", "matches", "move", "multiple", "name", "needs", "nothing", "one", "onvaluechange", "open", "opening", "options", "pair", "per", "pick", "picked", "picks", "placeholder", "removable", "removes", "render", "required", "select", "sets", "show", "shown", "shows", "single", "stay", "styling", "submit", "submits", "tags", "text", "the", "there", "through", "type", "types", "typing", "uncontrolled", "user", "value", "values", "visible", "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 through a hidden input. 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", "hidden", "hint", "htmlfor", "ids", "input", "invalid", "isdisabled", "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", "sunday", "text", "that", "the", "then", "there", "these", "through", "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", "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", "Field"], keywords: ["and", "aria", "automatically", "beside", "checkbox", "children", "control", "controls", "describedby", "description", "dims", "disabled", "disables", "enclosing", "error", "explicit", "field", "fills", "first", "for", "form", "from", "hint", "horizontal", "ids", "invalid", "its", "label", "makes", "marks", "match", "must", "one", "orientation", "out", "outside", "own", "picks", "props", "renders", "required", "returns", "shown", "springs", "state", "switch", "the", "unchanged", "under", "unit", "use", "when", "win", "wired", "with"], registryDependencies: ["label", "utils"] }, { 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"], keywords: ["anchor", "and", "blocks", "browser", "called", "can", "control", "custom", "empty", "field", "finds", "focus", "focuses", "form", "gets", "hidden", "input", "invalid", "message", "name", "native", "nothing", "oninvalid", "only", "part", "picked", "required", "shows", "submit", "submits", "takes", "that", "the", "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", "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: ["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: ["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
|
|
17593
|
+
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", "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", "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", "there", "type", "types", "typing", "uncontrolled", "user", "value", "values", "visible", "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", "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", "Field"], keywords: ["and", "aria", "automatically", "beside", "checkbox", "children", "control", "controls", "describedby", "description", "dims", "disabled", "disables", "enclosing", "error", "explicit", "field", "fills", "first", "for", "form", "from", "hint", "horizontal", "ids", "invalid", "its", "label", "makes", "marks", "match", "must", "one", "orientation", "out", "outside", "own", "picks", "props", "renders", "required", "returns", "shown", "springs", "state", "switch", "the", "unchanged", "under", "unit", "use", "when", "win", "wired", "with"], registryDependencies: ["label", "utils"] }, { 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"], keywords: ["anchor", "and", "blocks", "browser", "called", "can", "control", "custom", "disabled", "empty", "field", "finds", "focus", "focuses", "form", "gets", "hidden", "input", "invalid", "leaves", "like", "message", "name", "native", "nothing", "oninvalid", "only", "out", "part", "picked", "required", "shows", "skips", "submit", "submits", "takes", "that", "the", "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", "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: ["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: ["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
|
|
17252
17594
|
(tabIndex 0) so arrow keys can scroll it, and it leaves the tab order again once it fits.
|
|
17253
|
-
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: ["action", "align", "alignment", "also", "anchored", "and", "any", "aria", "armed", "before", "blur", "both", "button", "called", "can", "check", "children", "click", "clicks", "closes", "confirm", "confirmlabel", "confirmtimeout", "content", "controlled", "default", "defaultopen", "defaults", "destructive", "done", "edits", "emphasis", "end", "enter", "esc", "feedback", "fill", "filters", "first", "flips", "focus", "focusable", "for", "form", "give", "height", "icon", "ignore", "ignores", "irreversible", "its", "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", "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", "variant", "view", "when", "where", "while", "width", "with"], registryDependencies: ["button", "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", "entered", "expected", "field", "fieldset", "first", "form", "format", "generated", "group", "indicates", "invalid", "item", "items", "its", "keys", "label", "labels", "lay", "legend", "line", "muted", "name", "native", "not", "omitted", "one", "onvaluechange", "option", "orientation", "pair", "picked", "radios", "render", "rendered", "row", "selectable", "shown", "stack", "submit", "submitted", "the", "them", "uncontrolled", "under", "value", "visible", "when", "with", "work"], registryDependencies: ["field", "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", "arrow", "called", "chosen", "controlled", "defaultvalue", "display", "field", "filled", "first", "for", "form", "forms", "fractions", "group", "hidden", "hover", "image", "included", "inputs", "interactive", "keys", "label", "lift", "max", "name", "native", "number", "only", "onvaluechange", "pair", "partially", "picked", "press", "preview", "radios", "rating", "readers", "readonly", "render", "renders", "score", "screen", "size", "spring", "star", "stars", "the", "uncontrolled", "value", "visually", "with"], registryDependencies: ["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 via a hidden input.", 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", "form", "from", "groups", "hidden", "hint", "home", "htmlfor", "ids", "input", "invalid", "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", "submits", "text", "the", "there", "through", "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", "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", "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", "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", "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, pasting a list adds them all. Duplicates, a max and a validate function keep bad tags in the input. One hidden input per tag.", exports: ["tagFieldClass", "Tag", "TagInput"], keywords: ["accessible", "add", "after", "allowed", "and", "animatepresence", "backspace", "besides", "button", "called", "children", "chip", "commit", "controlled", "defaultvalue", "disabled", "empty", "enter", "every", "false", "first", "form", "free", "hidden", "hides", "input", "inside", "its", "keys", "last", "limit", "list", "many", "max", "mode", "most", "name", "one", "onremove", "onvaluechange", "out", "pair", "paste", "pasted", "per", "poplayout", "press", "reject", "removable", "remove", "removes", "render", "rendered", "return", "runs", "separator", "separators", "shows", "split", "springs", "stays", "stops", "submits", "tag", "tags", "text", "that", "the", "these", "too", "trimming", "type", "typed", "typing", "uncontrolled", "validate", "value", "when", "with"], registryDependencies: ["field", "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", "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.", exports: ["toast", "Toaster"], keywords: ["action", "description", "dismiss", "dismisses", "duration", "error", "expands", "focus", "hover", "layout", "loading", "mount", "once", "promise", "root", "saved", "stacked", "success", "swipe", "tap", "title", "toast", "toasts"], registryDependencies: ["use-dismiss", "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,
|
|
17595
|
+
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: ["action", "align", "alignment", "also", "anchored", "and", "any", "aria", "armed", "before", "blur", "both", "button", "called", "can", "check", "children", "click", "clicks", "closes", "confirm", "confirmlabel", "confirmtimeout", "content", "controlled", "default", "defaultopen", "defaults", "destructive", "done", "edits", "emphasis", "end", "enter", "esc", "feedback", "fill", "filters", "first", "flips", "focus", "focusable", "for", "form", "give", "height", "icon", "ignore", "ignores", "irreversible", "its", "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", "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", "variant", "view", "when", "where", "while", "width", "with"], registryDependencies: ["button", "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", "entered", "expected", "field", "fieldset", "first", "form", "format", "generated", "group", "indicates", "invalid", "item", "items", "its", "keys", "label", "labels", "lay", "legend", "line", "muted", "name", "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", "work"], registryDependencies: ["field", "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", "defaultvalue", "describedby", "disabled", "display", "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", "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", "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", "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", "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, 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", "TagInput"], keywords: ["accessible", "add", "after", "allowed", "and", "animatepresence", "backspace", "besides", "button", "called", "children", "chip", "commit", "controlled", "defaultvalue", "disabled", "empty", "enter", "every", "false", "first", "form", "free", "hides", "input", "inside", "its", "keys", "last", "least", "limit", "list", "many", "max", "mode", "most", "name", "needs", "one", "onremove", "onvaluechange", "out", "pair", "paste", "pasted", "per", "poplayout", "press", "reject", "removable", "remove", "removes", "render", "rendered", "required", "return", "runs", "separator", "separators", "shows", "split", "springs", "stays", "stops", "submits", "tag", "tags", "text", "that", "the", "these", "too", "trimming", "type", "typed", "typing", "uncontrolled", "validate", "value", "when", "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", "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.", exports: ["toast", "Toaster"], keywords: ["action", "description", "dismiss", "dismisses", "duration", "error", "expands", "focus", "hover", "layout", "loading", "mount", "once", "promise", "root", "saved", "stacked", "success", "swipe", "tap", "title", "toast", "toasts"], registryDependencies: ["use-dismiss", "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,
|
|
17254
17596
|
flipped when it doesn't fit, clamped to the viewport, kept in place on scroll and resize.
|
|
17255
17597
|
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,
|
|
17256
17598
|
ToggleGroup). Re-measures when the item or its row resizes (font swap, wrap), or the pill
|
|
@@ -17350,7 +17692,7 @@ async function fetchAllItems(source) {
|
|
|
17350
17692
|
// jsr.json
|
|
17351
17693
|
var jsr_default = {
|
|
17352
17694
|
name: "@kznlabs/labsui",
|
|
17353
|
-
version: "0.1.
|
|
17695
|
+
version: "0.1.7",
|
|
17354
17696
|
license: "MIT",
|
|
17355
17697
|
exports: "./src/index.ts",
|
|
17356
17698
|
publish: {
|