@krizaka/ui 2.0.0-beta.2 → 2.0.0-beta.3

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.
Files changed (184) hide show
  1. package/README.md +30 -0
  2. package/dist/alert.d.ts +2 -0
  3. package/dist/alert.js.map +1 -1
  4. package/dist/avatar.d.ts +5 -0
  5. package/dist/avatar.js.map +1 -1
  6. package/dist/badge.d.ts +12 -0
  7. package/dist/badge.js.map +1 -1
  8. package/dist/{button-G5qY4qSY.d.ts → button-8La7l0V4.d.ts} +15 -0
  9. package/dist/button.d.ts +19 -0
  10. package/dist/button.js.map +1 -1
  11. package/dist/card.d.ts +24 -1
  12. package/dist/card.js.map +1 -1
  13. package/dist/chip.d.ts +23 -0
  14. package/dist/chip.js.map +1 -1
  15. package/dist/{chunk-5FRYKFTO.js → chunk-3X5MVH54.js} +2 -2
  16. package/dist/chunk-3X5MVH54.js.map +1 -0
  17. package/dist/{chunk-QRG5RYEI.js → chunk-ZUD4U22H.js} +1 -1
  18. package/dist/chunk-ZUD4U22H.js.map +1 -0
  19. package/dist/command.d.ts +4 -1
  20. package/dist/command.js +2 -2
  21. package/dist/command.js.map +1 -1
  22. package/dist/confirm-button.d.ts +1 -1
  23. package/dist/confirm-button.js +1 -1
  24. package/dist/countdown.d.ts +3 -0
  25. package/dist/countdown.js.map +1 -1
  26. package/dist/{dialog-DGBY2grk.d.ts → dialog-GQ4Nvuyf.d.ts} +10 -0
  27. package/dist/dialog.d.ts +9 -1
  28. package/dist/dialog.js +2 -2
  29. package/dist/dialog.js.map +1 -1
  30. package/dist/dropdown-menu.d.ts +1 -0
  31. package/dist/dropdown-menu.js.map +1 -1
  32. package/dist/empty-state.d.ts +2 -0
  33. package/dist/empty-state.js.map +1 -1
  34. package/dist/kbd.d.ts +7 -1
  35. package/dist/kbd.js.map +1 -1
  36. package/dist/page-header.d.ts +2 -0
  37. package/dist/page-header.js.map +1 -1
  38. package/dist/progress.d.ts +5 -0
  39. package/dist/progress.js.map +1 -1
  40. package/dist/skeleton.d.ts +7 -1
  41. package/dist/skeleton.js.map +1 -1
  42. package/dist/slider.d.ts +2 -0
  43. package/dist/slider.js.map +1 -1
  44. package/dist/spinner.d.ts +5 -0
  45. package/dist/spinner.js.map +1 -1
  46. package/dist/stat.d.ts +2 -0
  47. package/dist/stat.js.map +1 -1
  48. package/dist/switch.d.ts +7 -3
  49. package/dist/switch.js.map +1 -1
  50. package/dist/tabs.d.ts +4 -1
  51. package/dist/tabs.js.map +1 -1
  52. package/dist/theme.d.ts +10 -2
  53. package/dist/theme.js +4 -2
  54. package/dist/theme.js.map +1 -1
  55. package/dist/tooltip.d.ts +3 -0
  56. package/dist/tooltip.js.map +1 -1
  57. package/package.json +14 -3
  58. package/registry/alert.json +59 -0
  59. package/registry/avatar.json +114 -0
  60. package/registry/badge.json +59 -0
  61. package/registry/button.json +115 -0
  62. package/registry/card.json +154 -0
  63. package/registry/checkbox.json +46 -0
  64. package/registry/chip.json +164 -0
  65. package/registry/cn.json +21 -0
  66. package/registry/command.json +162 -0
  67. package/registry/confirm-button.json +104 -0
  68. package/registry/countdown.json +84 -0
  69. package/registry/demos/alert.d.ts +5 -0
  70. package/registry/demos/alert.js +12 -0
  71. package/registry/demos/alert.tsx +15 -0
  72. package/registry/demos/avatar.d.ts +5 -0
  73. package/registry/demos/avatar.js +14 -0
  74. package/registry/demos/avatar.tsx +13 -0
  75. package/registry/demos/badge.d.ts +5 -0
  76. package/registry/demos/badge.js +11 -0
  77. package/registry/demos/badge.tsx +5 -0
  78. package/registry/demos/button.d.ts +5 -0
  79. package/registry/demos/button.js +11 -0
  80. package/registry/demos/button.tsx +5 -0
  81. package/registry/demos/card.d.ts +5 -0
  82. package/registry/demos/card.js +30 -0
  83. package/registry/demos/card.tsx +37 -0
  84. package/registry/demos/checkbox.d.ts +5 -0
  85. package/registry/demos/checkbox.js +11 -0
  86. package/registry/demos/checkbox.tsx +5 -0
  87. package/registry/demos/chip.d.ts +5 -0
  88. package/registry/demos/chip.js +15 -0
  89. package/registry/demos/chip.tsx +11 -0
  90. package/registry/demos/cn.d.ts +7 -0
  91. package/registry/demos/cn.js +17 -0
  92. package/registry/demos/cn.tsx +9 -0
  93. package/registry/demos/command.d.ts +5 -0
  94. package/registry/demos/command.js +32 -0
  95. package/registry/demos/command.tsx +28 -0
  96. package/registry/demos/confirm-button.d.ts +5 -0
  97. package/registry/demos/confirm-button.js +12 -0
  98. package/registry/demos/confirm-button.tsx +13 -0
  99. package/registry/demos/countdown.d.ts +5 -0
  100. package/registry/demos/countdown.js +13 -0
  101. package/registry/demos/countdown.tsx +10 -0
  102. package/registry/demos/dialog.d.ts +7 -0
  103. package/registry/demos/dialog.js +29 -0
  104. package/registry/demos/dialog.tsx +31 -0
  105. package/registry/demos/dropdown-menu.d.ts +7 -0
  106. package/registry/demos/dropdown-menu.js +36 -0
  107. package/registry/demos/dropdown-menu.tsx +27 -0
  108. package/registry/demos/empty-state.d.ts +5 -0
  109. package/registry/demos/empty-state.js +15 -0
  110. package/registry/demos/empty-state.tsx +12 -0
  111. package/registry/demos/field.d.ts +5 -0
  112. package/registry/demos/field.js +15 -0
  113. package/registry/demos/field.tsx +11 -0
  114. package/registry/demos/kbd.d.ts +5 -0
  115. package/registry/demos/kbd.js +11 -0
  116. package/registry/demos/kbd.tsx +5 -0
  117. package/registry/demos/page-header.d.ts +5 -0
  118. package/registry/demos/page-header.js +11 -0
  119. package/registry/demos/page-header.tsx +5 -0
  120. package/registry/demos/popover.d.ts +7 -0
  121. package/registry/demos/popover.js +25 -0
  122. package/registry/demos/popover.tsx +31 -0
  123. package/registry/demos/progress.d.ts +5 -0
  124. package/registry/demos/progress.js +11 -0
  125. package/registry/demos/progress.tsx +5 -0
  126. package/registry/demos/radio-group.d.ts +5 -0
  127. package/registry/demos/radio-group.js +16 -0
  128. package/registry/demos/radio-group.tsx +14 -0
  129. package/registry/demos/separator.d.ts +5 -0
  130. package/registry/demos/separator.js +15 -0
  131. package/registry/demos/separator.tsx +11 -0
  132. package/registry/demos/skeleton.d.ts +5 -0
  133. package/registry/demos/skeleton.js +20 -0
  134. package/registry/demos/skeleton.tsx +16 -0
  135. package/registry/demos/slider.d.ts +5 -0
  136. package/registry/demos/slider.js +11 -0
  137. package/registry/demos/slider.tsx +5 -0
  138. package/registry/demos/slot.d.ts +5 -0
  139. package/registry/demos/slot.js +14 -0
  140. package/registry/demos/slot.tsx +13 -0
  141. package/registry/demos/spinner.d.ts +5 -0
  142. package/registry/demos/spinner.js +11 -0
  143. package/registry/demos/spinner.tsx +5 -0
  144. package/registry/demos/stat.d.ts +5 -0
  145. package/registry/demos/stat.js +11 -0
  146. package/registry/demos/stat.tsx +5 -0
  147. package/registry/demos/switch.d.ts +5 -0
  148. package/registry/demos/switch.js +11 -0
  149. package/registry/demos/switch.tsx +5 -0
  150. package/registry/demos/tabs.d.ts +5 -0
  151. package/registry/demos/tabs.js +22 -0
  152. package/registry/demos/tabs.tsx +28 -0
  153. package/registry/demos/theme.d.ts +5 -0
  154. package/registry/demos/theme.js +11 -0
  155. package/registry/demos/theme.tsx +9 -0
  156. package/registry/demos/toast.d.ts +8 -0
  157. package/registry/demos/toast.js +29 -0
  158. package/registry/demos/toast.tsx +31 -0
  159. package/registry/demos/tooltip.d.ts +7 -0
  160. package/registry/demos/tooltip.js +15 -0
  161. package/registry/demos/tooltip.tsx +15 -0
  162. package/registry/dialog.json +218 -0
  163. package/registry/dropdown-menu.json +63 -0
  164. package/registry/empty-state.json +59 -0
  165. package/registry/field.json +66 -0
  166. package/registry/index.json +320 -0
  167. package/registry/kbd.json +38 -0
  168. package/registry/page-header.json +66 -0
  169. package/registry/popover.json +44 -0
  170. package/registry/progress.json +80 -0
  171. package/registry/radio-group.json +64 -0
  172. package/registry/separator.json +25 -0
  173. package/registry/skeleton.json +38 -0
  174. package/registry/slider.json +95 -0
  175. package/registry/slot.json +31 -0
  176. package/registry/spinner.json +45 -0
  177. package/registry/stat.json +66 -0
  178. package/registry/switch.json +53 -0
  179. package/registry/tabs.json +44 -0
  180. package/registry/theme.json +78 -0
  181. package/registry/toast.json +54 -0
  182. package/registry/tooltip.json +91 -0
  183. package/dist/chunk-5FRYKFTO.js.map +0 -1
  184. package/dist/chunk-QRG5RYEI.js.map +0 -1
@@ -0,0 +1,5 @@
1
+ import { Switch } from "@krizaka/ui/switch";
2
+
3
+ export default function SwitchDemo() {
4
+ return <Switch label="Autoplay" />;
5
+ }
@@ -0,0 +1,5 @@
1
+ import * as react from 'react';
2
+
3
+ declare function TabsDemo(): react.JSX.Element;
4
+
5
+ export { TabsDemo as default };
@@ -0,0 +1,22 @@
1
+ "use client";
2
+
3
+ // registry/demos/tabs.tsx
4
+ import { Tabs } from "@krizaka/ui/tabs";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ function TabsDemo() {
7
+ return /* @__PURE__ */ jsxs(Tabs.Root, { defaultValue: "videos", children: [
8
+ /* @__PURE__ */ jsxs(Tabs.List, { "aria-label": "Profile", children: [
9
+ /* @__PURE__ */ jsx(Tabs.Trigger, { value: "videos", children: "Videos" }),
10
+ /* @__PURE__ */ jsx(Tabs.Trigger, { value: "stories", children: "Stories" }),
11
+ /* @__PURE__ */ jsx(Tabs.Trigger, { value: "collections", children: "Collections" }),
12
+ /* @__PURE__ */ jsx(Tabs.Trigger, { value: "about", disabled: true, children: "About" })
13
+ ] }),
14
+ /* @__PURE__ */ jsx(Tabs.Content, { value: "videos", className: "text-sm text-fg-secondary", children: "Twelve videos, newest first." }),
15
+ /* @__PURE__ */ jsx(Tabs.Content, { value: "stories", className: "text-sm text-fg-secondary", children: "Stories of the last 24 hours." }),
16
+ /* @__PURE__ */ jsx(Tabs.Content, { value: "collections", className: "text-sm text-fg-secondary", children: "Three public collections." }),
17
+ /* @__PURE__ */ jsx(Tabs.Content, { value: "about", className: "text-sm text-fg-secondary", children: "About this channel." })
18
+ ] });
19
+ }
20
+ export {
21
+ TabsDemo as default
22
+ };
@@ -0,0 +1,28 @@
1
+ import { Tabs } from "@krizaka/ui/tabs";
2
+
3
+ export default function TabsDemo() {
4
+ return (
5
+ <Tabs.Root defaultValue="videos">
6
+ <Tabs.List aria-label="Profile">
7
+ <Tabs.Trigger value="videos">Videos</Tabs.Trigger>
8
+ <Tabs.Trigger value="stories">Stories</Tabs.Trigger>
9
+ <Tabs.Trigger value="collections">Collections</Tabs.Trigger>
10
+ <Tabs.Trigger value="about" disabled>
11
+ About
12
+ </Tabs.Trigger>
13
+ </Tabs.List>
14
+ <Tabs.Content value="videos" className="text-sm text-fg-secondary">
15
+ Twelve videos, newest first.
16
+ </Tabs.Content>
17
+ <Tabs.Content value="stories" className="text-sm text-fg-secondary">
18
+ Stories of the last 24 hours.
19
+ </Tabs.Content>
20
+ <Tabs.Content value="collections" className="text-sm text-fg-secondary">
21
+ Three public collections.
22
+ </Tabs.Content>
23
+ <Tabs.Content value="about" className="text-sm text-fg-secondary">
24
+ About this channel.
25
+ </Tabs.Content>
26
+ </Tabs.Root>
27
+ );
28
+ }
@@ -0,0 +1,5 @@
1
+ import * as react from 'react';
2
+
3
+ declare function ThemeDemo(): react.JSX.Element;
4
+
5
+ export { ThemeDemo as default };
@@ -0,0 +1,11 @@
1
+ "use client";
2
+
3
+ // registry/demos/theme.tsx
4
+ import { ThemeProvider, ThemeToggle } from "@krizaka/ui/theme";
5
+ import { jsx } from "react/jsx-runtime";
6
+ function ThemeDemo() {
7
+ return /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(ThemeToggle, { label: "Change the theme" }) });
8
+ }
9
+ export {
10
+ ThemeDemo as default
11
+ };
@@ -0,0 +1,9 @@
1
+ import { ThemeProvider, ThemeToggle } from "@krizaka/ui/theme";
2
+
3
+ export default function ThemeDemo() {
4
+ return (
5
+ <ThemeProvider>
6
+ <ThemeToggle label="Change the theme" />
7
+ </ThemeProvider>
8
+ );
9
+ }
@@ -0,0 +1,8 @@
1
+ import * as react from 'react';
2
+
3
+ /** `defaultOpen`: the toasts show at once and stay (the screenshot); otherwise the button shows them. */
4
+ declare function ToastDemo({ defaultOpen }: {
5
+ defaultOpen?: boolean;
6
+ }): react.JSX.Element;
7
+
8
+ export { ToastDemo as default };
@@ -0,0 +1,29 @@
1
+ "use client";
2
+
3
+ // registry/demos/toast.tsx
4
+ import { Button } from "@krizaka/ui/button";
5
+ import { toast, Toaster } from "@krizaka/ui/toast";
6
+ import { useEffect } from "react";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ var toasterId = "tones";
9
+ var show = () => {
10
+ toast("Draft saved", { toasterId, description: "Autosaved a moment ago." });
11
+ toast.success("Payment received", { toasterId, description: "\u20AC12.00 from @maya." });
12
+ toast.warning("Your balance is low", { toasterId, description: "Two tips left before a top-up." });
13
+ toast.error("Upload failed", { toasterId, description: "The connection dropped at 64 %.", action: { label: "Retry", onClick: () => {
14
+ } } });
15
+ };
16
+ function ToastDemo({ defaultOpen = false }) {
17
+ useEffect(() => {
18
+ if (!defaultOpen) return;
19
+ toast.dismiss();
20
+ show();
21
+ }, [defaultOpen]);
22
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
23
+ /* @__PURE__ */ jsx(Toaster, { id: toasterId, label: "Notifications", closeLabel: "Dismiss", position: "bottom-right", expand: true, visibleToasts: 4, duration: defaultOpen ? Number.POSITIVE_INFINITY : void 0 }),
24
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: show, children: "Show again" })
25
+ ] });
26
+ }
27
+ export {
28
+ ToastDemo as default
29
+ };
@@ -0,0 +1,31 @@
1
+ "use client";
2
+
3
+ import { Button } from "@krizaka/ui/button";
4
+ import { toast, Toaster } from "@krizaka/ui/toast";
5
+ import { useEffect } from "react";
6
+
7
+ // Each toaster has its own `id`: the toasts of this demo stay in this demo's toaster.
8
+ const toasterId = "tones";
9
+ const show = () => {
10
+ toast("Draft saved", { toasterId, description: "Autosaved a moment ago." });
11
+ toast.success("Payment received", { toasterId, description: "€12.00 from @maya." });
12
+ toast.warning("Your balance is low", { toasterId, description: "Two tips left before a top-up." });
13
+ toast.error("Upload failed", { toasterId, description: "The connection dropped at 64 %.", action: { label: "Retry", onClick: () => {} } });
14
+ };
15
+
16
+ /** `defaultOpen`: the toasts show at once and stay (the screenshot); otherwise the button shows them. */
17
+ export default function ToastDemo({ defaultOpen = false }: { defaultOpen?: boolean }) {
18
+ useEffect(() => {
19
+ if (!defaultOpen) return;
20
+ toast.dismiss();
21
+ show();
22
+ }, [defaultOpen]);
23
+ return (
24
+ <>
25
+ <Toaster id={toasterId} label="Notifications" closeLabel="Dismiss" position="bottom-right" expand visibleToasts={4} duration={defaultOpen ? Number.POSITIVE_INFINITY : undefined} />
26
+ <Button variant="outline" onClick={show}>
27
+ Show again
28
+ </Button>
29
+ </>
30
+ );
31
+ }
@@ -0,0 +1,7 @@
1
+ import * as react from 'react';
2
+
3
+ declare function TooltipDemo({ defaultOpen }: {
4
+ defaultOpen?: boolean;
5
+ }): react.JSX.Element;
6
+
7
+ export { TooltipDemo as default };
@@ -0,0 +1,15 @@
1
+ "use client";
2
+
3
+ // registry/demos/tooltip.tsx
4
+ import { IconButton } from "@krizaka/ui/button";
5
+ import { Tooltip } from "@krizaka/ui/tooltip";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ function TooltipDemo({ defaultOpen = false }) {
8
+ return /* @__PURE__ */ jsx(Tooltip, { content: "Copy the link", defaultOpen, children: /* @__PURE__ */ jsx(IconButton, { label: "Copy", variant: "outline", children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: [
9
+ /* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "12", height: "12", rx: "2" }),
10
+ /* @__PURE__ */ jsx("path", { d: "M5 15V5a2 2 0 0 1 2-2h10" })
11
+ ] }) }) });
12
+ }
13
+ export {
14
+ TooltipDemo as default
15
+ };
@@ -0,0 +1,15 @@
1
+ import { IconButton } from "@krizaka/ui/button";
2
+ import { Tooltip } from "@krizaka/ui/tooltip";
3
+
4
+ export default function TooltipDemo({ defaultOpen = false }: { defaultOpen?: boolean }) {
5
+ return (
6
+ <Tooltip content="Copy the link" defaultOpen={defaultOpen}>
7
+ <IconButton label="Copy" variant="outline">
8
+ <svg viewBox="0 0 24 24" className="h-4 w-4" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden>
9
+ <rect x="9" y="9" width="12" height="12" rx="2" />
10
+ <path d="M5 15V5a2 2 0 0 1 2-2h10" />
11
+ </svg>
12
+ </IconButton>
13
+ </Tooltip>
14
+ );
15
+ }
@@ -0,0 +1,218 @@
1
+ {
2
+ "name": "dialog",
3
+ "type": "primitive",
4
+ "description": "`Dialog.*`, `Sheet`, `AlertDialog`, `dialog` (the slots). Client (\"use client\").",
5
+ "files": [
6
+ {
7
+ "path": "dialog/alert-dialog.tsx",
8
+ "content": "// Client. A confirmation that interrupts: Radix AlertDialog (role=\"alertdialog\", no outside click, focus on Cancel).\n// `onConfirm` may be async: the dialog stays open with the confirm button loading until it settles, closes when it\n// resolves, and stays open when it rejects (the product shows the error).\nimport { AlertDialog as AlertDialogPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { Button } from \"../button/button\";\nimport { dialog } from \"./dialog\";\n\nconst s = dialog({ placement: \"center\", size: \"sm\" });\n\nexport type AlertDialogProps = {\n /** Open or closed (controlled), with `onOpenChange`. */\n open?: boolean;\n /** Open at first, uncontrolled. */\n defaultOpen?: boolean;\n /** Called when it opens or closes (never while `onConfirm` is pending). */\n onOpenChange?: (open: boolean) => void;\n /** The element that opens the dialog (rendered as is, `asChild`): a Button, an IconButton. */\n trigger?: React.ReactNode;\n /** The question asked (\"Delete this video?\"): the dialog's accessible name. */\n title: React.ReactNode;\n /** What the action does and what it costs: the dialog's description. */\n description?: React.ReactNode;\n /** More content between the description and the actions. */\n children?: React.ReactNode;\n /** The words of the confirm button — passed translated. */\n confirmLabel: string;\n /** The words of the cancel button (it takes the focus at open) — passed translated. */\n cancelLabel: string;\n /** `danger` for a destructive action (delete, leave, cancel a payment). */\n tone?: \"danger\" | \"primary\";\n /** Runs on confirm. A promise keeps the dialog open and the button loading until it settles. */\n onConfirm: () => void | Promise<unknown>;\n /** The element the portal renders into (default: document.body). */\n container?: HTMLElement | null;\n /** Classes of the dialog's content, merged last. */\n className?: string;\n};\n\nexport function AlertDialog({\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n trigger,\n title,\n description,\n children,\n confirmLabel,\n cancelLabel,\n tone = \"primary\",\n onConfirm,\n container,\n className,\n}: AlertDialogProps) {\n const [openState, setOpenState] = React.useState(defaultOpen);\n const [pending, setPending] = React.useState(false);\n const open = openProp ?? openState;\n\n const setOpen = (next: boolean) => {\n if (openProp === undefined) setOpenState(next);\n onOpenChange?.(next);\n };\n\n const confirm = async () => {\n setPending(true);\n try {\n await onConfirm();\n setOpen(false);\n } catch {\n // Stays open: the product reports the failure (a toast, an inline error) and the person may retry.\n } finally {\n setPending(false);\n }\n };\n\n return (\n <AlertDialogPrimitive.Root open={open} onOpenChange={(next) => (pending ? undefined : setOpen(next))}>\n {trigger && <AlertDialogPrimitive.Trigger asChild>{trigger}</AlertDialogPrimitive.Trigger>}\n <AlertDialogPrimitive.Portal container={container}>\n <AlertDialogPrimitive.Overlay className={s.overlay()} />\n <AlertDialogPrimitive.Content\n data-tone={tone}\n aria-busy={pending || undefined}\n className={s.content({ className })}\n onEscapeKeyDown={(event) => pending && event.preventDefault()}\n >\n <div className={s.header({ className: \"pr-5\" })}>\n <AlertDialogPrimitive.Title className={s.title()}>{title}</AlertDialogPrimitive.Title>\n {description && <AlertDialogPrimitive.Description className={s.description()}>{description}</AlertDialogPrimitive.Description>}\n </div>\n {children && <div className={s.body()}>{children}</div>}\n <div className={s.footer()}>\n <AlertDialogPrimitive.Cancel asChild>\n <Button variant=\"ghost\" disabled={pending}>\n {cancelLabel}\n </Button>\n </AlertDialogPrimitive.Cancel>\n <Button variant={tone === \"danger\" ? \"danger\" : \"primary\"} loading={pending} onClick={confirm}>\n {confirmLabel}\n </Button>\n </div>\n </AlertDialogPrimitive.Content>\n </AlertDialogPrimitive.Portal>\n </AlertDialogPrimitive.Root>\n );\n}\n"
9
+ },
10
+ {
11
+ "path": "dialog/dialog.tsx",
12
+ "content": "// Client (a portal). Radix does the focus trap, the scroll lock, Escape, the outside click, aria-modal, the\n// aria-labelledby / aria-describedby wiring and the focus return: this file only holds the shape.\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { IconButton } from \"../button/button\";\n\nexport const dialog = tv({\n slots: {\n overlay: \"kz-overlay fixed inset-0 z-50 bg-overlay backdrop-blur-xs\",\n content:\n \"kz-dialog fixed z-50 flex flex-col overflow-hidden border border-border-default bg-surface-1 text-fg shadow-lg focus:outline-hidden\",\n close: \"absolute right-3 top-3 h-9 w-9\",\n header: \"flex flex-col gap-1 px-5 pb-2 pr-14 pt-5\",\n title: \"text-base font-bold text-fg\",\n description: \"text-sm text-fg-secondary\",\n body: \"min-h-0 flex-1 overflow-y-auto px-5 pb-5\",\n footer:\n \"flex flex-wrap items-center justify-end gap-2 border-t border-border-subtle px-5 py-4 pb-[max(1rem,env(safe-area-inset-bottom))]\",\n },\n variants: {\n placement: {\n center: { content: \"left-1/2 top-1/2 max-h-[85dvh] w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 rounded-xl\" },\n /* A sheet: anchored at the bottom on a phone, centred from `sm` up. */\n bottom: {\n content:\n \"inset-x-0 bottom-0 max-h-[92dvh] rounded-t-[1.75rem] sm:bottom-auto sm:left-1/2 sm:top-1/2 sm:w-full \" +\n \"sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-[1.75rem]\",\n },\n right: { content: \"inset-y-0 right-0 h-full w-full max-w-md rounded-none border-y-0 border-r-0\" },\n },\n size: { sm: { content: \"sm:max-w-sm\" }, md: { content: \"sm:max-w-md\" }, lg: { content: \"sm:max-w-2xl\" } },\n },\n defaultVariants: { placement: \"center\", size: \"md\" },\n});\nconst s = dialog();\n\nexport type DialogVariants = VariantProps<typeof dialog>;\n\nfunction CloseIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n );\n}\n\ntype CloseButtonProps =\n | {\n /** The accessible name of the close button — passed translated. */\n closeLabel: string;\n /** Leaves the close button out (the content brings its own way out, or none: see `dismissible`). */\n hideClose?: false;\n }\n | { closeLabel?: string; hideClose: true };\n\nexport type DialogContentProps = React.ComponentProps<typeof DialogPrimitive.Content> &\n DialogVariants &\n CloseButtonProps & {\n /**\n * center · bottom (a sheet on a phone, centred from `sm` up) · right (a side panel, full height).\n * @default \"center\"\n */\n placement?: DialogVariants[\"placement\"];\n /**\n * sm · md · lg: the width of the dialog.\n * @default \"md\"\n */\n size?: DialogVariants[\"size\"];\n /**\n * `false`: Escape and a click outside do not close it (a gate the user must answer: age, terms). It still closes\n * through `open` / `onOpenChange` and any `Dialog.Close` inside. Default `true`.\n */\n dismissible?: boolean;\n /** The element the portal renders into (default: document.body). */\n container?: HTMLElement | null;\n };\n\nconst prevent = (event: Event) => event.preventDefault();\n\n/** The dialog itself, portalled over a dimmed overlay, with a close button. Give it a `Dialog.Title`. */\nexport function DialogContent({\n placement,\n size,\n closeLabel,\n hideClose,\n dismissible = true,\n container,\n className,\n children,\n onEscapeKeyDown,\n onPointerDownOutside,\n onInteractOutside,\n ...props\n}: DialogContentProps) {\n const v = dialog({ placement, size });\n return (\n <DialogPrimitive.Portal container={container}>\n <DialogPrimitive.Overlay className={v.overlay()} />\n <DialogPrimitive.Content\n data-placement={placement ?? \"center\"}\n data-dismissible={dismissible ? undefined : \"false\"}\n className={v.content({ className })}\n onEscapeKeyDown={dismissible ? onEscapeKeyDown : chain(onEscapeKeyDown, prevent)}\n onPointerDownOutside={dismissible ? onPointerDownOutside : chain(onPointerDownOutside, prevent)}\n onInteractOutside={dismissible ? onInteractOutside : chain(onInteractOutside, prevent)}\n {...props}\n >\n {children}\n {!hideClose && (\n <DialogPrimitive.Close asChild>\n <IconButton label={closeLabel as string} variant=\"ghost\" shape=\"pill\" className={v.close()}>\n <CloseIcon />\n </IconButton>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n );\n}\n\nfunction chain<E extends Event>(handler: ((event: E) => void) | undefined, then: (event: E) => void) {\n return (event: E) => {\n handler?.(event);\n then(event);\n };\n}\n\nexport function DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.header({ className })} {...props} />;\n}\n\nexport function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {\n return <DialogPrimitive.Title className={s.title({ className })} {...props} />;\n}\n\nexport function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {\n return <DialogPrimitive.Description className={s.description({ className })} {...props} />;\n}\n\nexport function DialogBody({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.body({ className })} {...props} />;\n}\n\nexport function DialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return <div className={s.footer({ className })} {...props} />;\n}\n\n/** `Dialog.Root/Trigger/Content/Header/Title/Description/Body/Footer/Close`. `open`/`defaultOpen` + `onOpenChange`. */\nexport const Dialog = {\n Root: DialogPrimitive.Root,\n Trigger: DialogPrimitive.Trigger,\n Close: DialogPrimitive.Close,\n Content: DialogContent,\n Header: DialogHeader,\n Title: DialogTitle,\n Description: DialogDescription,\n Body: DialogBody,\n Footer: DialogFooter,\n};\n\n/** `Omit` that keeps the close-button union (`closeLabel` required unless `hideClose`). */\ntype DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;\n\nexport type SheetProps = DistributiveOmit<DialogContentProps, \"placement\">;\n\n/** A sheet is a dialog anchored at the bottom (centred from `sm` up): `Dialog.Content placement=\"bottom\"`. */\nexport function Sheet(props: SheetProps) {\n return <DialogContent placement=\"bottom\" {...props} />;\n}\n"
13
+ },
14
+ {
15
+ "path": "dialog/index.ts",
16
+ "content": "/** @krizaka/ui/dialog — `Dialog.*`, `Sheet`, `AlertDialog`, `dialog` (the slots). Client (\"use client\"). */\nexport { AlertDialog, type AlertDialogProps } from \"./alert-dialog\";\nexport {\n Dialog,\n dialog,\n DialogBody,\n DialogContent,\n type DialogContentProps,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n type DialogVariants,\n Sheet,\n type SheetProps,\n} from \"./dialog\";\n"
17
+ }
18
+ ],
19
+ "dependencies": [
20
+ "radix-ui@^1.7.0",
21
+ "tailwind-variants@^3.3.1"
22
+ ],
23
+ "registryDependencies": [
24
+ "button"
25
+ ],
26
+ "demo": {
27
+ "path": "demos/dialog.tsx",
28
+ "content": "import { Button } from \"@krizaka/ui/button\";\nimport { Dialog } from \"@krizaka/ui/dialog\";\nimport { Field, Input } from \"@krizaka/ui/field\";\n\nexport default function DialogDemo({ defaultOpen = false }: { defaultOpen?: boolean }) {\n return (\n <Dialog.Root defaultOpen={defaultOpen}>\n <Dialog.Trigger asChild>\n <Button>Open</Button>\n </Dialog.Trigger>\n <Dialog.Content closeLabel=\"Close\">\n <Dialog.Header>\n <Dialog.Title>Invite a collaborator</Dialog.Title>\n <Dialog.Description>They get access to the drafts of this channel.</Dialog.Description>\n </Dialog.Header>\n <Dialog.Body>\n <Field.Root>\n <Field.Label htmlFor=\"invite-email\">Email</Field.Label>\n <Input id=\"invite-email\" type=\"email\" placeholder=\"name@example.com\" />\n </Field.Root>\n </Dialog.Body>\n <Dialog.Footer>\n <Dialog.Close asChild>\n <Button variant=\"ghost\">Cancel</Button>\n </Dialog.Close>\n <Button variant=\"primary\">Send the invitation</Button>\n </Dialog.Footer>\n </Dialog.Content>\n </Dialog.Root>\n );\n}\n"
29
+ },
30
+ "props": [
31
+ {
32
+ "component": "AlertDialog",
33
+ "description": "",
34
+ "props": [
35
+ {
36
+ "name": "cancelLabel",
37
+ "type": "string",
38
+ "default": null,
39
+ "description": "The words of the cancel button (it takes the focus at open) — passed translated.",
40
+ "required": true
41
+ },
42
+ {
43
+ "name": "confirmLabel",
44
+ "type": "string",
45
+ "default": null,
46
+ "description": "The words of the confirm button — passed translated.",
47
+ "required": true
48
+ },
49
+ {
50
+ "name": "onConfirm",
51
+ "type": "() => void | Promise<unknown>",
52
+ "default": null,
53
+ "description": "Runs on confirm. A promise keeps the dialog open and the button loading until it settles.",
54
+ "required": true
55
+ },
56
+ {
57
+ "name": "title",
58
+ "type": "ReactNode",
59
+ "default": null,
60
+ "description": "The question asked (\"Delete this video?\"): the dialog's accessible name.",
61
+ "required": true
62
+ },
63
+ {
64
+ "name": "children",
65
+ "type": "ReactNode",
66
+ "default": null,
67
+ "description": "More content between the description and the actions.",
68
+ "required": false
69
+ },
70
+ {
71
+ "name": "className",
72
+ "type": "string",
73
+ "default": null,
74
+ "description": "Classes of the dialog's content, merged last.",
75
+ "required": false
76
+ },
77
+ {
78
+ "name": "container",
79
+ "type": "HTMLElement | null",
80
+ "default": null,
81
+ "description": "The element the portal renders into (default: document.body).",
82
+ "required": false
83
+ },
84
+ {
85
+ "name": "defaultOpen",
86
+ "type": "boolean",
87
+ "default": "false",
88
+ "description": "Open at first, uncontrolled.",
89
+ "required": false
90
+ },
91
+ {
92
+ "name": "description",
93
+ "type": "ReactNode",
94
+ "default": null,
95
+ "description": "What the action does and what it costs: the dialog's description.",
96
+ "required": false
97
+ },
98
+ {
99
+ "name": "onOpenChange",
100
+ "type": "((open: boolean) => void)",
101
+ "default": null,
102
+ "description": "Called when it opens or closes (never while `onConfirm` is pending).",
103
+ "required": false
104
+ },
105
+ {
106
+ "name": "open",
107
+ "type": "boolean",
108
+ "default": null,
109
+ "description": "Open or closed (controlled), with `onOpenChange`.",
110
+ "required": false
111
+ },
112
+ {
113
+ "name": "tone",
114
+ "type": "\"danger\" | \"primary\"",
115
+ "default": "primary",
116
+ "description": "`danger` for a destructive action (delete, leave, cancel a payment).",
117
+ "required": false
118
+ },
119
+ {
120
+ "name": "trigger",
121
+ "type": "ReactNode",
122
+ "default": null,
123
+ "description": "The element that opens the dialog (rendered as is, `asChild`): a Button, an IconButton.",
124
+ "required": false
125
+ }
126
+ ]
127
+ },
128
+ {
129
+ "component": "DialogContent",
130
+ "description": "The dialog itself, portalled over a dimmed overlay, with a close button. Give it a `Dialog.Title`.",
131
+ "props": [
132
+ {
133
+ "name": "closeLabel",
134
+ "type": "string",
135
+ "default": null,
136
+ "description": "The accessible name of the close button — passed translated.",
137
+ "required": false
138
+ },
139
+ {
140
+ "name": "container",
141
+ "type": "HTMLElement | null",
142
+ "default": null,
143
+ "description": "The element the portal renders into (default: document.body).",
144
+ "required": false
145
+ },
146
+ {
147
+ "name": "dismissible",
148
+ "type": "boolean",
149
+ "default": "true",
150
+ "description": "`false`: Escape and a click outside do not close it (a gate the user must answer: age, terms). It still closes\nthrough `open` / `onOpenChange` and any `Dialog.Close` inside. Default `true`.",
151
+ "required": false
152
+ },
153
+ {
154
+ "name": "hideClose",
155
+ "type": "boolean",
156
+ "default": null,
157
+ "description": "Leaves the close button out (the content brings its own way out, or none: see `dismissible`).",
158
+ "required": false
159
+ },
160
+ {
161
+ "name": "placement",
162
+ "type": "\"center\" | \"bottom\" | \"right\"",
163
+ "default": "center",
164
+ "description": "center · bottom (a sheet on a phone, centred from `sm` up) · right (a side panel, full height).",
165
+ "required": false
166
+ },
167
+ {
168
+ "name": "size",
169
+ "type": "\"sm\" | \"md\" | \"lg\"",
170
+ "default": "md",
171
+ "description": "sm · md · lg: the width of the dialog.",
172
+ "required": false
173
+ }
174
+ ]
175
+ },
176
+ {
177
+ "component": "Sheet",
178
+ "description": "A sheet is a dialog anchored at the bottom (centred from `sm` up): `Dialog.Content placement=\"bottom\"`.",
179
+ "props": [
180
+ {
181
+ "name": "closeLabel",
182
+ "type": "string",
183
+ "default": null,
184
+ "description": "The accessible name of the close button — passed translated.",
185
+ "required": false
186
+ },
187
+ {
188
+ "name": "container",
189
+ "type": "HTMLElement | null",
190
+ "default": null,
191
+ "description": "The element the portal renders into (default: document.body).",
192
+ "required": false
193
+ },
194
+ {
195
+ "name": "dismissible",
196
+ "type": "boolean",
197
+ "default": null,
198
+ "description": "`false`: Escape and a click outside do not close it (a gate the user must answer: age, terms). It still closes\nthrough `open` / `onOpenChange` and any `Dialog.Close` inside. Default `true`.",
199
+ "required": false
200
+ },
201
+ {
202
+ "name": "hideClose",
203
+ "type": "boolean",
204
+ "default": null,
205
+ "description": "Leaves the close button out (the content brings its own way out, or none: see `dismissible`).",
206
+ "required": false
207
+ },
208
+ {
209
+ "name": "size",
210
+ "type": "\"sm\" | \"md\" | \"lg\"",
211
+ "default": "md",
212
+ "description": "sm · md · lg: the width of the dialog.",
213
+ "required": false
214
+ }
215
+ ]
216
+ }
217
+ ]
218
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "dropdown-menu",
3
+ "type": "primitive",
4
+ "description": "`DropdownMenu.*`, `menu` (the slots). Client (\"use client\").",
5
+ "files": [
6
+ {
7
+ "path": "dropdown-menu/dropdown-menu.tsx",
8
+ "content": "// Client (a portal). Radix does the roving focus, the arrows, Home/End, typeahead, Escape and the focus return.\nimport { DropdownMenu as MenuPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { floating } from \"../floating\";\n\nexport const menu = tv({\n slots: {\n content: cn(floating, \"min-w-48 max-h-(--radix-dropdown-menu-content-available-height) overflow-y-auto p-1 origin-(--radix-dropdown-menu-content-transform-origin)\"),\n item:\n \"relative flex cursor-pointer select-none items-center gap-2 rounded-md px-2.5 py-2 text-sm text-fg outline-hidden transition-colors \" +\n \"data-highlighted:bg-surface-3 data-disabled:pointer-events-none data-disabled:opacity-40 [&>svg]:h-4 [&>svg]:w-4 [&>svg]:shrink-0 \" +\n \"[&>svg]:text-fg-secondary\",\n indicator: \"ml-auto flex h-4 w-4 items-center justify-center text-accent\",\n label: \"px-2.5 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-wider text-fg-secondary\",\n separator: \"-mx-1 my-1 h-px bg-border-subtle\",\n },\n variants: {\n // The danger token is too light for small text on a light surface: the label stays a text role, the icon and the\n // highlight carry the danger.\n tone: { default: {}, danger: { item: \"data-highlighted:bg-danger/15 [&>svg]:text-danger\" } },\n },\n defaultVariants: { tone: \"default\" },\n});\nconst s = menu();\n\nexport type DropdownMenuContentProps = React.ComponentProps<typeof MenuPrimitive.Content> & {\n /** The element the portal renders into (default: document.body). */\n container?: HTMLElement | null;\n};\n\nexport function DropdownMenuContent({ sideOffset = 6, align = \"end\", collisionPadding = 8, container, className, ...props }: DropdownMenuContentProps) {\n return (\n <MenuPrimitive.Portal container={container}>\n <MenuPrimitive.Content sideOffset={sideOffset} align={align} collisionPadding={collisionPadding} className={s.content({ className })} {...props} />\n </MenuPrimitive.Portal>\n );\n}\n\nexport type DropdownMenuItemProps = React.ComponentProps<typeof MenuPrimitive.Item> & {\n /** `danger` for a destructive action: the icon and the highlight carry the danger, the label stays legible. */\n tone?: \"default\" | \"danger\";\n};\n\n/** An action. `tone=\"danger\"` for a destructive one; `onSelect` runs it (the menu closes). */\nexport function DropdownMenuItem({ tone, className, ...props }: DropdownMenuItemProps) {\n return <MenuPrimitive.Item data-tone={tone ?? \"default\"} className={menu({ tone }).item({ className })} {...props} />;\n}\n\n/** A toggled option: `checked` + `onCheckedChange`. */\nexport function DropdownMenuCheckboxItem({ className, children, ...props }: React.ComponentProps<typeof MenuPrimitive.CheckboxItem>) {\n return (\n <MenuPrimitive.CheckboxItem className={s.item({ className })} {...props}>\n {children}\n <MenuPrimitive.ItemIndicator className={s.indicator()}>\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </MenuPrimitive.ItemIndicator>\n </MenuPrimitive.CheckboxItem>\n );\n}\n\nexport function DropdownMenuLabel({ className, ...props }: React.ComponentProps<typeof MenuPrimitive.Label>) {\n return <MenuPrimitive.Label className={s.label({ className })} {...props} />;\n}\n\nexport function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof MenuPrimitive.Separator>) {\n return <MenuPrimitive.Separator className={s.separator({ className })} {...props} />;\n}\n\n/** `DropdownMenu.Root/Trigger/Content/Item/CheckboxItem/Label/Separator/Group`. `open`/`defaultOpen` + `onOpenChange`. */\nexport const DropdownMenu = {\n Root: MenuPrimitive.Root,\n Trigger: MenuPrimitive.Trigger,\n Content: DropdownMenuContent,\n Item: DropdownMenuItem,\n CheckboxItem: DropdownMenuCheckboxItem,\n Label: DropdownMenuLabel,\n Separator: DropdownMenuSeparator,\n Group: MenuPrimitive.Group,\n};\n"
9
+ },
10
+ {
11
+ "path": "dropdown-menu/index.ts",
12
+ "content": "/** @krizaka/ui/dropdown-menu — `DropdownMenu.*`, `menu` (the slots). Client (\"use client\"). */\nexport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n type DropdownMenuContentProps,\n DropdownMenuItem,\n type DropdownMenuItemProps,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n menu,\n} from \"./dropdown-menu\";\n"
13
+ },
14
+ {
15
+ "path": "floating.ts",
16
+ "content": "// The one surface of everything that floats next to its trigger (popover, menu, tooltip): a raised surface, a border,\n// the large shadow and the `kz-pop` entrance from the side Radix measured (`--radix-*-transform-origin`).\nexport const floating =\n \"kz-pop z-50 rounded-lg border border-border-default bg-surface-2 text-fg shadow-lg outline-hidden\";\n"
17
+ }
18
+ ],
19
+ "dependencies": [
20
+ "radix-ui@^1.7.0",
21
+ "tailwind-variants@^3.3.1"
22
+ ],
23
+ "registryDependencies": [
24
+ "cn"
25
+ ],
26
+ "demo": {
27
+ "path": "demos/dropdown-menu.tsx",
28
+ "content": "import { Button } from \"@krizaka/ui/button\";\nimport { DropdownMenu } from \"@krizaka/ui/dropdown-menu\";\n\nconst icon = (d: string) => (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d={d} />\n </svg>\n);\n\nexport default function DropdownMenuDemo({ defaultOpen = false }: { defaultOpen?: boolean }) {\n return (\n <DropdownMenu.Root defaultOpen={defaultOpen} modal={false}>\n <DropdownMenu.Trigger asChild>\n <Button variant=\"outline\">Actions</Button>\n </DropdownMenu.Trigger>\n <DropdownMenu.Content align=\"center\">\n <DropdownMenu.Label>Video</DropdownMenu.Label>\n <DropdownMenu.Item>{icon(\"M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z\")}Edit</DropdownMenu.Item>\n <DropdownMenu.Item>{icon(\"M4 12v8h16v-8M16 6l-4-4-4 4M12 2v13\")}Share</DropdownMenu.Item>\n <DropdownMenu.Item disabled>{icon(\"M12 3v12M7 10l5 5 5-5M5 21h14\")}Download</DropdownMenu.Item>\n <DropdownMenu.CheckboxItem checked>Pinned to the profile</DropdownMenu.CheckboxItem>\n <DropdownMenu.Separator />\n <DropdownMenu.Item tone=\"danger\">{icon(\"M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6\")}Delete</DropdownMenu.Item>\n </DropdownMenu.Content>\n </DropdownMenu.Root>\n );\n}\n"
29
+ },
30
+ "props": [
31
+ {
32
+ "component": "DropdownMenuContent",
33
+ "description": "",
34
+ "props": [
35
+ {
36
+ "name": "container",
37
+ "type": "HTMLElement | null",
38
+ "default": null,
39
+ "description": "The element the portal renders into (default: document.body).",
40
+ "required": false
41
+ }
42
+ ]
43
+ },
44
+ {
45
+ "component": "DropdownMenuItem",
46
+ "description": "An action. `tone=\"danger\"` for a destructive one; `onSelect` runs it (the menu closes).",
47
+ "props": [
48
+ {
49
+ "name": "tone",
50
+ "type": "\"danger\" | \"default\"",
51
+ "default": null,
52
+ "description": "`danger` for a destructive action: the icon and the highlight carry the danger, the label stays legible.",
53
+ "required": false
54
+ }
55
+ ]
56
+ },
57
+ {
58
+ "component": "DropdownMenuCheckboxItem",
59
+ "description": "A toggled option: `checked` + `onCheckedChange`.",
60
+ "props": []
61
+ }
62
+ ]
63
+ }
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "empty-state",
3
+ "type": "primitive",
4
+ "description": "`EmptyState`. Server-safe.",
5
+ "files": [
6
+ {
7
+ "path": "empty-state/empty-state.tsx",
8
+ "content": "// Server-safe. What a list, a search or a panel says when it has nothing to show — every word is a prop.\nimport type * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nexport const emptyState = tv({\n slots: {\n root: \"flex flex-col items-center justify-center gap-3 rounded-xl border border-dashed border-border-default px-6 py-10 text-center\",\n icon: \"flex h-12 w-12 items-center justify-center rounded-full bg-surface-2 text-fg-secondary [&>svg]:h-6 [&>svg]:w-6\",\n title: \"text-sm font-semibold text-fg\",\n description: \"max-w-sm text-sm text-fg-secondary\",\n action: \"mt-1 flex flex-wrap items-center justify-center gap-2\",\n },\n});\nconst s = emptyState();\n\nexport type EmptyStateProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n /** A decorative icon (hidden from assistive technology). */\n icon?: React.ReactNode;\n /** What is empty, in a few words (\"No videos yet\"). */\n title: React.ReactNode;\n /** Why, or what will fill it. */\n description?: React.ReactNode;\n /** What to do next: a Button, a link. */\n action?: React.ReactNode;\n};\n\nexport function EmptyState({ icon, title, description, action, className, ...props }: EmptyStateProps) {\n return (\n <div role=\"status\" className={s.root({ className })} {...props}>\n {icon && (\n <div aria-hidden className={s.icon()}>\n {icon}\n </div>\n )}\n <p className={s.title()}>{title}</p>\n {description && <p className={s.description()}>{description}</p>}\n {action && <div className={s.action()}>{action}</div>}\n </div>\n );\n}\n"
9
+ },
10
+ {
11
+ "path": "empty-state/index.ts",
12
+ "content": "/** @krizaka/ui/empty-state — `EmptyState`. Server-safe. */\nexport { EmptyState, emptyState, type EmptyStateProps } from \"./empty-state\";\n"
13
+ }
14
+ ],
15
+ "dependencies": [
16
+ "tailwind-variants@^3.3.1"
17
+ ],
18
+ "registryDependencies": [],
19
+ "demo": {
20
+ "path": "demos/empty-state.tsx",
21
+ "content": "import { EmptyState } from \"@krizaka/ui/empty-state\";\n\nconst ICON = (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <path d=\"m10 9 5 3-5 3z\" />\n </svg>\n);\n\nexport default function EmptyStateDemo() {\n return <EmptyState icon={ICON} title=\"No videos yet\" description=\"The videos you publish appear here.\" />;\n}\n"
22
+ },
23
+ "props": [
24
+ {
25
+ "component": "EmptyState",
26
+ "description": "",
27
+ "props": [
28
+ {
29
+ "name": "title",
30
+ "type": "ReactNode",
31
+ "default": null,
32
+ "description": "What is empty, in a few words (\"No videos yet\").",
33
+ "required": true
34
+ },
35
+ {
36
+ "name": "action",
37
+ "type": "ReactNode",
38
+ "default": null,
39
+ "description": "What to do next: a Button, a link.",
40
+ "required": false
41
+ },
42
+ {
43
+ "name": "description",
44
+ "type": "ReactNode",
45
+ "default": null,
46
+ "description": "Why, or what will fill it.",
47
+ "required": false
48
+ },
49
+ {
50
+ "name": "icon",
51
+ "type": "ReactNode",
52
+ "default": null,
53
+ "description": "A decorative icon (hidden from assistive technology).",
54
+ "required": false
55
+ }
56
+ ]
57
+ }
58
+ ]
59
+ }