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

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 (187) hide show
  1. package/README.md +98 -6
  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/native.d.ts +471 -3
  37. package/dist/native.js +1005 -36
  38. package/dist/native.js.map +1 -1
  39. package/dist/page-header.d.ts +2 -0
  40. package/dist/page-header.js.map +1 -1
  41. package/dist/progress.d.ts +5 -0
  42. package/dist/progress.js.map +1 -1
  43. package/dist/skeleton.d.ts +7 -1
  44. package/dist/skeleton.js.map +1 -1
  45. package/dist/slider.d.ts +2 -0
  46. package/dist/slider.js.map +1 -1
  47. package/dist/spinner.d.ts +5 -0
  48. package/dist/spinner.js.map +1 -1
  49. package/dist/stat.d.ts +2 -0
  50. package/dist/stat.js.map +1 -1
  51. package/dist/switch.d.ts +7 -3
  52. package/dist/switch.js.map +1 -1
  53. package/dist/tabs.d.ts +4 -1
  54. package/dist/tabs.js.map +1 -1
  55. package/dist/theme.d.ts +10 -2
  56. package/dist/theme.js +4 -2
  57. package/dist/theme.js.map +1 -1
  58. package/dist/tooltip.d.ts +3 -0
  59. package/dist/tooltip.js.map +1 -1
  60. package/package.json +31 -8
  61. package/registry/alert.json +59 -0
  62. package/registry/avatar.json +114 -0
  63. package/registry/badge.json +59 -0
  64. package/registry/button.json +115 -0
  65. package/registry/card.json +154 -0
  66. package/registry/checkbox.json +46 -0
  67. package/registry/chip.json +164 -0
  68. package/registry/cn.json +21 -0
  69. package/registry/command.json +162 -0
  70. package/registry/confirm-button.json +104 -0
  71. package/registry/countdown.json +84 -0
  72. package/registry/demos/alert.d.ts +5 -0
  73. package/registry/demos/alert.js +12 -0
  74. package/registry/demos/alert.tsx +15 -0
  75. package/registry/demos/avatar.d.ts +5 -0
  76. package/registry/demos/avatar.js +14 -0
  77. package/registry/demos/avatar.tsx +13 -0
  78. package/registry/demos/badge.d.ts +5 -0
  79. package/registry/demos/badge.js +11 -0
  80. package/registry/demos/badge.tsx +5 -0
  81. package/registry/demos/button.d.ts +5 -0
  82. package/registry/demos/button.js +11 -0
  83. package/registry/demos/button.tsx +5 -0
  84. package/registry/demos/card.d.ts +5 -0
  85. package/registry/demos/card.js +30 -0
  86. package/registry/demos/card.tsx +37 -0
  87. package/registry/demos/checkbox.d.ts +5 -0
  88. package/registry/demos/checkbox.js +11 -0
  89. package/registry/demos/checkbox.tsx +5 -0
  90. package/registry/demos/chip.d.ts +5 -0
  91. package/registry/demos/chip.js +15 -0
  92. package/registry/demos/chip.tsx +11 -0
  93. package/registry/demos/cn.d.ts +7 -0
  94. package/registry/demos/cn.js +17 -0
  95. package/registry/demos/cn.tsx +9 -0
  96. package/registry/demos/command.d.ts +5 -0
  97. package/registry/demos/command.js +32 -0
  98. package/registry/demos/command.tsx +28 -0
  99. package/registry/demos/confirm-button.d.ts +5 -0
  100. package/registry/demos/confirm-button.js +12 -0
  101. package/registry/demos/confirm-button.tsx +13 -0
  102. package/registry/demos/countdown.d.ts +5 -0
  103. package/registry/demos/countdown.js +13 -0
  104. package/registry/demos/countdown.tsx +10 -0
  105. package/registry/demos/dialog.d.ts +7 -0
  106. package/registry/demos/dialog.js +29 -0
  107. package/registry/demos/dialog.tsx +31 -0
  108. package/registry/demos/dropdown-menu.d.ts +7 -0
  109. package/registry/demos/dropdown-menu.js +36 -0
  110. package/registry/demos/dropdown-menu.tsx +27 -0
  111. package/registry/demos/empty-state.d.ts +5 -0
  112. package/registry/demos/empty-state.js +15 -0
  113. package/registry/demos/empty-state.tsx +12 -0
  114. package/registry/demos/field.d.ts +5 -0
  115. package/registry/demos/field.js +15 -0
  116. package/registry/demos/field.tsx +11 -0
  117. package/registry/demos/kbd.d.ts +5 -0
  118. package/registry/demos/kbd.js +11 -0
  119. package/registry/demos/kbd.tsx +5 -0
  120. package/registry/demos/page-header.d.ts +5 -0
  121. package/registry/demos/page-header.js +11 -0
  122. package/registry/demos/page-header.tsx +5 -0
  123. package/registry/demos/popover.d.ts +7 -0
  124. package/registry/demos/popover.js +25 -0
  125. package/registry/demos/popover.tsx +31 -0
  126. package/registry/demos/progress.d.ts +5 -0
  127. package/registry/demos/progress.js +11 -0
  128. package/registry/demos/progress.tsx +5 -0
  129. package/registry/demos/radio-group.d.ts +5 -0
  130. package/registry/demos/radio-group.js +16 -0
  131. package/registry/demos/radio-group.tsx +14 -0
  132. package/registry/demos/separator.d.ts +5 -0
  133. package/registry/demos/separator.js +15 -0
  134. package/registry/demos/separator.tsx +11 -0
  135. package/registry/demos/skeleton.d.ts +5 -0
  136. package/registry/demos/skeleton.js +20 -0
  137. package/registry/demos/skeleton.tsx +16 -0
  138. package/registry/demos/slider.d.ts +5 -0
  139. package/registry/demos/slider.js +11 -0
  140. package/registry/demos/slider.tsx +5 -0
  141. package/registry/demos/slot.d.ts +5 -0
  142. package/registry/demos/slot.js +14 -0
  143. package/registry/demos/slot.tsx +13 -0
  144. package/registry/demos/spinner.d.ts +5 -0
  145. package/registry/demos/spinner.js +11 -0
  146. package/registry/demos/spinner.tsx +5 -0
  147. package/registry/demos/stat.d.ts +5 -0
  148. package/registry/demos/stat.js +11 -0
  149. package/registry/demos/stat.tsx +5 -0
  150. package/registry/demos/switch.d.ts +5 -0
  151. package/registry/demos/switch.js +11 -0
  152. package/registry/demos/switch.tsx +5 -0
  153. package/registry/demos/tabs.d.ts +5 -0
  154. package/registry/demos/tabs.js +22 -0
  155. package/registry/demos/tabs.tsx +28 -0
  156. package/registry/demos/theme.d.ts +5 -0
  157. package/registry/demos/theme.js +11 -0
  158. package/registry/demos/theme.tsx +9 -0
  159. package/registry/demos/toast.d.ts +8 -0
  160. package/registry/demos/toast.js +29 -0
  161. package/registry/demos/toast.tsx +31 -0
  162. package/registry/demos/tooltip.d.ts +7 -0
  163. package/registry/demos/tooltip.js +15 -0
  164. package/registry/demos/tooltip.tsx +15 -0
  165. package/registry/dialog.json +218 -0
  166. package/registry/dropdown-menu.json +63 -0
  167. package/registry/empty-state.json +59 -0
  168. package/registry/field.json +66 -0
  169. package/registry/index.json +320 -0
  170. package/registry/kbd.json +38 -0
  171. package/registry/page-header.json +66 -0
  172. package/registry/popover.json +44 -0
  173. package/registry/progress.json +80 -0
  174. package/registry/radio-group.json +64 -0
  175. package/registry/separator.json +25 -0
  176. package/registry/skeleton.json +38 -0
  177. package/registry/slider.json +95 -0
  178. package/registry/slot.json +31 -0
  179. package/registry/spinner.json +45 -0
  180. package/registry/stat.json +66 -0
  181. package/registry/switch.json +53 -0
  182. package/registry/tabs.json +44 -0
  183. package/registry/theme.json +78 -0
  184. package/registry/toast.json +54 -0
  185. package/registry/tooltip.json +91 -0
  186. package/dist/chunk-5FRYKFTO.js.map +0 -1
  187. package/dist/chunk-QRG5RYEI.js.map +0 -1
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "popover",
3
+ "type": "primitive",
4
+ "description": "`Popover.*`. Client (\"use client\").",
5
+ "files": [
6
+ {
7
+ "path": "floating.ts",
8
+ "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"
9
+ },
10
+ {
11
+ "path": "popover/index.ts",
12
+ "content": "/** @krizaka/ui/popover — `Popover.*`. Client (\"use client\"). */\nexport { Popover, PopoverContent, type PopoverContentProps } from \"./popover\";\n"
13
+ },
14
+ {
15
+ "path": "popover/popover.tsx",
16
+ "content": "// Client (a portal). Radix places the content, traps nothing, closes on Escape and on an outside click, and returns\n// the focus to the trigger.\nimport { Popover as PopoverPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\n\nimport { cn } from \"../cn\";\nimport { floating } from \"../floating\";\n\nexport type PopoverContentProps = React.ComponentProps<typeof PopoverPrimitive.Content> & {\n /** The element the portal renders into (default: document.body). */\n container?: HTMLElement | null;\n};\n\nexport function PopoverContent({ sideOffset = 8, collisionPadding = 8, container, className, ...props }: PopoverContentProps) {\n return (\n <PopoverPrimitive.Portal container={container}>\n <PopoverPrimitive.Content\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n className={cn(floating, \"w-72 max-w-(--radix-popover-content-available-width) p-4 origin-(--radix-popover-content-transform-origin)\", className)}\n {...props}\n />\n </PopoverPrimitive.Portal>\n );\n}\n\n/** `Popover.Root/Trigger/Anchor/Content/Close`. `open`/`defaultOpen` + `onOpenChange`. */\nexport const Popover = {\n Root: PopoverPrimitive.Root,\n Trigger: PopoverPrimitive.Trigger,\n Anchor: PopoverPrimitive.Anchor,\n Close: PopoverPrimitive.Close,\n Content: PopoverContent,\n};\n"
17
+ }
18
+ ],
19
+ "dependencies": [
20
+ "radix-ui@^1.7.0"
21
+ ],
22
+ "registryDependencies": [
23
+ "cn"
24
+ ],
25
+ "demo": {
26
+ "path": "demos/popover.tsx",
27
+ "content": "import { Button } from \"@krizaka/ui/button\";\nimport { Field, Input } from \"@krizaka/ui/field\";\nimport { Popover } from \"@krizaka/ui/popover\";\n\nexport default function PopoverDemo({ defaultOpen = false }: { defaultOpen?: boolean }) {\n return (\n <Popover.Root defaultOpen={defaultOpen}>\n <Popover.Trigger asChild>\n <Button variant=\"outline\">Set a goal</Button>\n </Popover.Trigger>\n <Popover.Content aria-label=\"Set a goal\" align=\"center\">\n <div className=\"flex flex-col gap-3\">\n <Field.Root>\n <Field.Label htmlFor=\"goal-amount\">Amount</Field.Label>\n <Input id=\"goal-amount\" defaultValue=\"250\" />\n </Field.Root>\n <div className=\"flex justify-end gap-2\">\n <Popover.Close asChild>\n <Button size=\"sm\" variant=\"ghost\">\n Cancel\n </Button>\n </Popover.Close>\n <Button size=\"sm\" variant=\"primary\">\n Save\n </Button>\n </div>\n </div>\n </Popover.Content>\n </Popover.Root>\n );\n}\n"
28
+ },
29
+ "props": [
30
+ {
31
+ "component": "PopoverContent",
32
+ "description": "",
33
+ "props": [
34
+ {
35
+ "name": "container",
36
+ "type": "HTMLElement | null",
37
+ "default": null,
38
+ "description": "The element the portal renders into (default: document.body).",
39
+ "required": false
40
+ }
41
+ ]
42
+ }
43
+ ]
44
+ }
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "progress",
3
+ "type": "primitive",
4
+ "description": "`Progress` (bar · ring, indeterminate), `progress` (the slots). Server-safe.",
5
+ "files": [
6
+ {
7
+ "path": "progress/index.ts",
8
+ "content": "/** @krizaka/ui/progress — `Progress` (bar · ring, indeterminate), `progress` (the slots). Server-safe. */\nexport { Progress, progress, type ProgressProps, type ProgressVariants } from \"./progress\";\n"
9
+ },
10
+ {
11
+ "path": "progress/progress.tsx",
12
+ "content": "// Server-safe: no hook, no context. role=\"progressbar\" with aria-valuenow / aria-valuemax (none when indeterminate),\n// named by `label`. A bar or a ring; the ring can hold content in its centre (an amount, a percentage).\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nexport const progress = tv({\n slots: {\n root: \"relative\",\n track: \"\",\n indicator: \"\",\n center: \"absolute inset-0 flex flex-col items-center justify-center text-center\",\n },\n variants: {\n variant: {\n bar: {\n root: \"w-full overflow-hidden rounded-full bg-surface-3\",\n indicator:\n \"kz-progress h-full rounded-full bg-linear-to-r from-accent to-accent-2 transition-[width] duration-500 motion-reduce:transition-none \" +\n \"data-[state=indeterminate]:w-2/5\",\n },\n ring: {\n root: \"inline-flex shrink-0 items-center justify-center\",\n track: \"stroke-border-default\",\n indicator:\n \"stroke-accent transition-[stroke-dashoffset] duration-1000 motion-reduce:transition-none \" +\n \"data-[state=indeterminate]:origin-center data-[state=indeterminate]:animate-spin motion-reduce:data-[state=indeterminate]:animate-none\",\n },\n },\n size: { sm: {}, md: {}, lg: {} },\n },\n compoundVariants: [\n { variant: \"bar\", size: \"sm\", class: { root: \"h-1\" } },\n { variant: \"bar\", size: \"md\", class: { root: \"h-2\" } },\n { variant: \"bar\", size: \"lg\", class: { root: \"h-3\" } },\n { variant: \"ring\", size: \"sm\", class: { root: \"h-10 w-10\" } },\n { variant: \"ring\", size: \"md\", class: { root: \"h-20 w-20\" } },\n { variant: \"ring\", size: \"lg\", class: { root: \"h-36 w-36\" } },\n ],\n defaultVariants: { variant: \"bar\", size: \"md\" },\n});\n\nexport type ProgressVariants = VariantProps<typeof progress>;\n\nexport type ProgressProps = Omit<React.ComponentProps<\"div\">, \"children\"> &\n ProgressVariants & {\n /** bar · ring (a ring holds its `children` in the centre). */\n variant?: ProgressVariants[\"variant\"];\n /** sm · md · lg: the bar's thickness, the ring's diameter. */\n size?: ProgressVariants[\"size\"];\n /** How far, between 0 and `max`. `null` or absent: indeterminate (a wait of unknown length). */\n value?: number | null;\n /** The value that means complete. */\n max?: number;\n /** The accessible name — passed translated (\"Upload\", \"Raised towards the goal\"). */\n label: string;\n /** The value in words for assistive technology (\"$420 of $1,000\") — default: the percentage. */\n valueText?: string;\n /** A ring's centre: an amount, a percentage, an icon. */\n children?: React.ReactNode;\n };\n\nconst STROKE = { sm: 12, md: 9, lg: 7 } as const;\n\n/** A progress bar or ring: `value` / `max`, indeterminate without a value. State: `data-state` (loading · complete · indeterminate). */\nexport function Progress({ variant = \"bar\", size = \"md\", value, max = 100, label, valueText, className, children, ...props }: ProgressProps) {\n const s = progress({ variant, size });\n const known = typeof value === \"number\" && Number.isFinite(value);\n const ratio = known ? Math.min(1, Math.max(0, value / (max || 1))) : 0;\n const percent = Math.round(ratio * 100);\n const state = !known ? \"indeterminate\" : ratio >= 1 ? \"complete\" : \"loading\";\n const aria = {\n role: \"progressbar\" as const,\n \"aria-label\": label,\n \"aria-valuemin\": known ? 0 : undefined,\n \"aria-valuemax\": known ? max : undefined,\n \"aria-valuenow\": known ? Math.min(max, Math.max(0, value)) : undefined,\n \"aria-valuetext\": known ? (valueText ?? `${percent}%`) : valueText,\n \"data-state\": state,\n \"data-value\": known ? value : undefined,\n \"data-variant\": variant,\n };\n if (variant === \"ring\") {\n const stroke = STROKE[size];\n const r = 50 - stroke / 2;\n return (\n <div {...aria} className={s.root({ className })} {...props}>\n <svg viewBox=\"0 0 100 100\" className=\"h-full w-full -rotate-90\" aria-hidden>\n <circle cx=\"50\" cy=\"50\" r={r} fill=\"none\" strokeWidth={stroke} className={s.track()} />\n <circle\n cx=\"50\"\n cy=\"50\"\n r={r}\n fill=\"none\"\n strokeWidth={stroke}\n strokeLinecap=\"round\"\n pathLength={100}\n strokeDasharray={100}\n strokeDashoffset={known ? 100 - percent : 75}\n data-state={state}\n className={s.indicator()}\n />\n </svg>\n {children !== undefined && <div className={s.center()}>{children}</div>}\n </div>\n );\n }\n return (\n <div {...aria} className={s.root({ className })} {...props}>\n <div data-state={state} className={s.indicator()} style={known ? { width: `${ratio * 100}%` } : undefined} />\n </div>\n );\n}\n"
13
+ }
14
+ ],
15
+ "dependencies": [
16
+ "tailwind-variants@^3.3.1"
17
+ ],
18
+ "registryDependencies": [],
19
+ "demo": {
20
+ "path": "demos/progress.tsx",
21
+ "content": "import { Progress } from \"@krizaka/ui/progress\";\n\nexport default function ProgressDemo() {\n return <Progress label=\"Upload\" value={64} />;\n}\n"
22
+ },
23
+ "props": [
24
+ {
25
+ "component": "Progress",
26
+ "description": "A progress bar or ring: `value` / `max`, indeterminate without a value. State: `data-state` (loading · complete · indeterminate).",
27
+ "props": [
28
+ {
29
+ "name": "label",
30
+ "type": "string",
31
+ "default": null,
32
+ "description": "The accessible name — passed translated (\"Upload\", \"Raised towards the goal\").",
33
+ "required": true
34
+ },
35
+ {
36
+ "name": "children",
37
+ "type": "ReactNode",
38
+ "default": null,
39
+ "description": "A ring's centre: an amount, a percentage, an icon.",
40
+ "required": false
41
+ },
42
+ {
43
+ "name": "max",
44
+ "type": "number",
45
+ "default": "100",
46
+ "description": "The value that means complete.",
47
+ "required": false
48
+ },
49
+ {
50
+ "name": "size",
51
+ "type": "\"sm\" | \"md\" | \"lg\"",
52
+ "default": "md",
53
+ "description": "sm · md · lg: the bar's thickness, the ring's diameter.",
54
+ "required": false
55
+ },
56
+ {
57
+ "name": "value",
58
+ "type": "number | null",
59
+ "default": null,
60
+ "description": "How far, between 0 and `max`. `null` or absent: indeterminate (a wait of unknown length).",
61
+ "required": false
62
+ },
63
+ {
64
+ "name": "valueText",
65
+ "type": "string",
66
+ "default": null,
67
+ "description": "The value in words for assistive technology (\"$420 of $1,000\") — default: the percentage.",
68
+ "required": false
69
+ },
70
+ {
71
+ "name": "variant",
72
+ "type": "\"bar\" | \"ring\"",
73
+ "default": "bar",
74
+ "description": "bar · ring (a ring holds its `children` in the centre).",
75
+ "required": false
76
+ }
77
+ ]
78
+ }
79
+ ]
80
+ }
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "radio-group",
3
+ "type": "primitive",
4
+ "description": "`RadioGroup.Root/Item/Card`, `radioGroup` (the slots). Client (\"use client\").",
5
+ "files": [
6
+ {
7
+ "path": "radio-group/index.ts",
8
+ "content": "/** @krizaka/ui/radio-group — `RadioGroup.Root/Item/Card`, `radioGroup` (the slots). Client (\"use client\"). */\nexport {\n RadioGroup,\n radioGroup,\n RadioGroupCard,\n RadioGroupItem,\n type RadioGroupItemProps,\n RadioGroupRoot,\n type RadioGroupRootProps,\n} from \"./radio-group\";\n"
9
+ },
10
+ {
11
+ "path": "radio-group/radio-group.tsx",
12
+ "content": "// Client (a context). Radix RadioGroup: role=\"radiogroup\" / \"radio\", one tab stop, the arrows move and select, a\n// hidden input inside a form. Two looks: `RadioGroup.Item` (a dot and its words) and `RadioGroup.Card` (a whole card\n// is the radio: an amount, a pack, a payment method, a plan).\nimport { RadioGroup as RadioGroupPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nexport const radioGroup = tv({\n slots: {\n root: \"grid gap-2 data-[orientation=horizontal]:flex data-[orientation=horizontal]:flex-wrap\",\n item:\n \"peer inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full border border-border-strong bg-surface-1 transition-colors \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface-0 \" +\n \"hover:border-accent disabled:cursor-not-allowed disabled:opacity-40 data-[state=checked]:border-accent aria-invalid:border-danger\",\n dot: \"h-2.5 w-2.5 rounded-full bg-accent\",\n label: \"flex cursor-pointer items-start gap-3 text-sm text-fg has-disabled:cursor-not-allowed has-disabled:opacity-60\",\n card:\n \"relative flex min-w-0 flex-col items-start gap-1 rounded-xl border border-border-default bg-surface-1 p-3 text-left text-sm text-fg transition-colors \" +\n \"hover:border-border-strong focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring \" +\n \"disabled:cursor-not-allowed disabled:opacity-40 data-[state=checked]:border-accent data-[state=checked]:bg-accent-soft \" +\n \"data-[state=checked]:shadow-sm\",\n },\n});\nconst s = radioGroup();\n\nexport type RadioGroupRootProps = React.ComponentProps<typeof RadioGroupPrimitive.Root> & {\n /** The group's accessible name — passed translated (or `aria-labelledby` a visible heading). */\n label?: string;\n /** Marks every radio invalid: aria-invalid (pair it with a `Field.Error`). */\n invalid?: boolean;\n};\n\n/** `value`/`defaultValue` + `onValueChange`, `orientation`, `required`, `name` for a form. */\nexport function RadioGroupRoot({ label, invalid, className, ...props }: RadioGroupRootProps) {\n return (\n <RadioGroupPrimitive.Root\n aria-label={label}\n aria-invalid={invalid || undefined}\n data-invalid={invalid ? \"\" : undefined}\n className={s.root({ className })}\n {...props}\n />\n );\n}\n\nexport type RadioGroupItemProps = Omit<React.ComponentProps<typeof RadioGroupPrimitive.Item>, \"children\"> & {\n /** The words next to the dot, clickable. Otherwise name it with `Field.Label htmlFor` or `aria-label`. */\n children?: React.ReactNode;\n};\n\n/** A radio: a dot, and its words when given. */\nexport function RadioGroupItem({ className, children, ...props }: RadioGroupItemProps) {\n const item = (\n <RadioGroupPrimitive.Item className={s.item({ className: children ? \"mt-0.5\" : className })} {...props}>\n <RadioGroupPrimitive.Indicator className={s.dot()} />\n </RadioGroupPrimitive.Item>\n );\n if (!children) return item;\n return (\n <label className={s.label({ className })}>\n {item}\n <span className=\"min-w-0\">{children}</span>\n </label>\n );\n}\n\n/** A whole card as the radio: its content is the radio's name. `data-state=\"checked\"` styles the chosen one. */\nexport function RadioGroupCard({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {\n return <RadioGroupPrimitive.Item className={s.card({ className })} {...props} />;\n}\n\n/** `RadioGroup.Root/Item/Card`. */\nexport const RadioGroup = { Root: RadioGroupRoot, Item: RadioGroupItem, Card: RadioGroupCard };\n"
13
+ }
14
+ ],
15
+ "dependencies": [
16
+ "radix-ui@^1.7.0",
17
+ "tailwind-variants@^3.3.1"
18
+ ],
19
+ "registryDependencies": [],
20
+ "demo": {
21
+ "path": "demos/radio-group.tsx",
22
+ "content": "import { RadioGroup } from \"@krizaka/ui/radio-group\";\n\nexport default function RadioGroupDemo() {\n return (\n <RadioGroup.Root label=\"Notification frequency\" defaultValue=\"daily\">\n <RadioGroup.Item value=\"instant\">Instantly</RadioGroup.Item>\n <RadioGroup.Item value=\"daily\">Once a day</RadioGroup.Item>\n <RadioGroup.Item value=\"weekly\">Once a week</RadioGroup.Item>\n <RadioGroup.Item value=\"never\" disabled>\n Never\n </RadioGroup.Item>\n </RadioGroup.Root>\n );\n}\n"
23
+ },
24
+ "props": [
25
+ {
26
+ "component": "RadioGroupRoot",
27
+ "description": "`value`/`defaultValue` + `onValueChange`, `orientation`, `required`, `name` for a form.",
28
+ "props": [
29
+ {
30
+ "name": "invalid",
31
+ "type": "boolean",
32
+ "default": null,
33
+ "description": "Marks every radio invalid: aria-invalid (pair it with a `Field.Error`).",
34
+ "required": false
35
+ },
36
+ {
37
+ "name": "label",
38
+ "type": "string",
39
+ "default": null,
40
+ "description": "The group's accessible name — passed translated (or `aria-labelledby` a visible heading).",
41
+ "required": false
42
+ }
43
+ ]
44
+ },
45
+ {
46
+ "component": "RadioGroupItem",
47
+ "description": "A radio: a dot, and its words when given.",
48
+ "props": [
49
+ {
50
+ "name": "children",
51
+ "type": "ReactNode",
52
+ "default": null,
53
+ "description": "The words next to the dot, clickable. Otherwise name it with `Field.Label htmlFor` or `aria-label`.",
54
+ "required": false
55
+ }
56
+ ]
57
+ },
58
+ {
59
+ "component": "RadioGroupCard",
60
+ "description": "A whole card as the radio: its content is the radio's name. `data-state=\"checked\"` styles the chosen one.",
61
+ "props": []
62
+ }
63
+ ]
64
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "separator",
3
+ "type": "primitive",
4
+ "description": "`Separator`, `separatorVariants` (Radix). Server-safe.",
5
+ "files": [
6
+ {
7
+ "path": "separator/index.ts",
8
+ "content": "/** @krizaka/ui/separator — `Separator`, `separatorVariants` (Radix). Server-safe. */\nexport { Separator, type SeparatorProps, separatorVariants } from \"./separator\";\n"
9
+ },
10
+ {
11
+ "path": "separator/separator.tsx",
12
+ "content": "// Server-safe. A line between groups: decorative by default (hidden from assistive technology), or a real\n// `role=\"separator\"` with `decorative={false}`.\nimport { Separator as SeparatorPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nexport const separatorVariants = tv({\n base: \"shrink-0 bg-border-default\",\n variants: { orientation: { horizontal: \"h-px w-full\", vertical: \"h-full w-px self-stretch\" } },\n defaultVariants: { orientation: \"horizontal\" },\n});\n\nexport type SeparatorProps = React.ComponentProps<typeof SeparatorPrimitive.Root>;\n\nexport function Separator({ orientation = \"horizontal\", decorative = true, className, ...props }: SeparatorProps) {\n return <SeparatorPrimitive.Root orientation={orientation} decorative={decorative} className={separatorVariants({ orientation, className })} {...props} />;\n}\n"
13
+ }
14
+ ],
15
+ "dependencies": [
16
+ "radix-ui@^1.7.0",
17
+ "tailwind-variants@^3.3.1"
18
+ ],
19
+ "registryDependencies": [],
20
+ "demo": {
21
+ "path": "demos/separator.tsx",
22
+ "content": "import { Separator } from \"@krizaka/ui/separator\";\n\nexport default function SeparatorDemo() {\n return (\n <div className=\"flex w-72 flex-col gap-3 text-sm text-fg\">\n <p>Account</p>\n <Separator />\n <p className=\"text-fg-secondary\">Notifications</p>\n </div>\n );\n}\n"
23
+ },
24
+ "props": []
25
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "skeleton",
3
+ "type": "primitive",
4
+ "description": "`Skeleton`, `skeletonVariants`. Server-safe.",
5
+ "files": [
6
+ {
7
+ "path": "skeleton/index.ts",
8
+ "content": "/** @krizaka/ui/skeleton — `Skeleton`, `skeletonVariants`. Server-safe. */\nexport { Skeleton, type SkeletonProps, skeletonVariants } from \"./skeleton\";\n"
9
+ },
10
+ {
11
+ "path": "skeleton/skeleton.tsx",
12
+ "content": "// Server-safe. A placeholder for content on its way: decorative, so hidden from assistive technology.\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nexport const skeletonVariants = tv({\n base: \"block bg-surface-3 motion-safe:animate-pulse\",\n variants: {\n shape: { text: \"h-3 w-full rounded-sm\", circle: \"aspect-square h-10 rounded-full\", rect: \"h-24 w-full rounded-lg\" },\n },\n defaultVariants: { shape: \"text\" },\n});\n\nexport type SkeletonProps = React.ComponentProps<\"span\"> & {\n /**\n * text (a line) · circle (an avatar; give it a height) · rect (a media; give it a height).\n * @default \"text\"\n */\n shape?: VariantProps<typeof skeletonVariants>[\"shape\"];\n};\n\n/** A pulsing shape the size of what is loading. Announce the wait elsewhere (aria-busy on the region, a Spinner). */\nexport function Skeleton({ shape, className, ...props }: SkeletonProps) {\n return <span aria-hidden data-shape={shape ?? \"text\"} className={skeletonVariants({ shape, className })} {...props} />;\n}\n"
13
+ }
14
+ ],
15
+ "dependencies": [
16
+ "tailwind-variants@^3.3.1"
17
+ ],
18
+ "registryDependencies": [],
19
+ "demo": {
20
+ "path": "demos/skeleton.tsx",
21
+ "content": "import { Skeleton } from \"@krizaka/ui/skeleton\";\n\nexport default function SkeletonDemo() {\n return (\n <div className=\"flex flex-col gap-3 rounded-xl border border-border-default bg-surface-1 p-4\">\n <Skeleton shape=\"rect\" className=\"h-32\" />\n <div className=\"flex items-center gap-3\">\n <Skeleton shape=\"circle\" className=\"h-8\" />\n <div className=\"flex flex-1 flex-col gap-2\">\n <Skeleton />\n <Skeleton className=\"w-2/3\" />\n </div>\n </div>\n </div>\n );\n}\n"
22
+ },
23
+ "props": [
24
+ {
25
+ "component": "Skeleton",
26
+ "description": "A pulsing shape the size of what is loading. Announce the wait elsewhere (aria-busy on the region, a Spinner).",
27
+ "props": [
28
+ {
29
+ "name": "shape",
30
+ "type": "\"circle\" | \"rect\" | \"text\"",
31
+ "default": "text",
32
+ "description": "text (a line) · circle (an avatar; give it a height) · rect (a media; give it a height).",
33
+ "required": false
34
+ }
35
+ ]
36
+ }
37
+ ]
38
+ }
@@ -0,0 +1,95 @@
1
+ {
2
+ "name": "slider",
3
+ "type": "primitive",
4
+ "description": "`Slider` (one value or a range), `slider` (the slots). Client (\"use client\").",
5
+ "files": [
6
+ {
7
+ "path": "slider/index.ts",
8
+ "content": "/** @krizaka/ui/slider — `Slider` (one value or a range), `slider` (the slots). Client (\"use client\"). */\nexport { Slider, slider, type SliderProps, type SliderValue } from \"./slider\";\n"
9
+ },
10
+ {
11
+ "path": "slider/slider.tsx",
12
+ "content": "// Client. Radix Slider: role=\"slider\" on each thumb, arrows / Page Up / Page Down / Home / End, a range with two\n// thumbs. The words arrive as props: `label` names the thumb(s), `formatValue` gives the aria-valuetext and the value\n// shown next to a visible label.\nimport { Slider as SliderPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nexport const slider = tv({\n slots: {\n root: \"flex flex-col gap-2\",\n header: \"flex items-baseline justify-between gap-3 text-xs font-semibold text-fg-secondary\",\n value: \"font-mono text-[11px] tabular-nums text-fg-secondary\",\n control:\n \"relative flex h-6 w-full touch-none select-none items-center data-disabled:cursor-not-allowed data-disabled:opacity-40\",\n track: \"relative h-1.5 w-full grow overflow-hidden rounded-full bg-surface-3\",\n range: \"absolute h-full rounded-full bg-linear-to-r from-accent to-accent-2\",\n thumb:\n \"block h-5 w-5 cursor-grab rounded-full border border-border-strong bg-fg-on-media shadow-md transition-[box-shadow] \" +\n \"focus-visible:outline-hidden focus-visible:ring-4 focus-visible:ring-ring/50 active:cursor-grabbing\",\n },\n});\n\nexport type SliderValue = number | [number, number];\n\nexport type SliderProps<V extends SliderValue = number> = Omit<\n React.ComponentProps<typeof SliderPrimitive.Root>,\n \"value\" | \"defaultValue\" | \"onValueChange\" | \"onValueCommit\" | \"children\"\n> & {\n /** One number (a thumb) or two (a range). Controlled with `onValueChange`. */\n value?: V;\n /** The value at first, uncontrolled: one number, or two for a range. */\n defaultValue?: V;\n /** Called on every move, with one number or two. */\n onValueChange?: (value: V) => void;\n /** Called once the drag or the key press ends. */\n onValueCommit?: (value: V) => void;\n /** The accessible name — of the thumb, or of both thumbs of a range — passed translated. */\n label: string;\n /** For a range: each thumb's own name ([\"Minimum\", \"Maximum\"]), passed translated. */\n thumbLabels?: [string, string];\n /** The value as words (aria-valuetext) and as shown: \"1.5×\", \"−12 %\", \"3 min\". */\n formatValue?: (value: number) => string;\n /** Shows the label and the value above the track. Without it, `label` only names the thumb. */\n showLabel?: boolean;\n /** Fills from this value instead of the minimum (a ±range from 0: brightness, balance). One thumb only. */\n origin?: number;\n};\n\nconst toArray = (v: SliderValue | undefined) => (v === undefined ? undefined : typeof v === \"number\" ? [v] : v);\n\n/** A slider on one value or a range: `value`/`defaultValue` + `onValueChange`, `min`, `max`, `step`. */\nexport function Slider<V extends SliderValue = number>({\n value,\n defaultValue,\n onValueChange,\n onValueCommit,\n label,\n thumbLabels,\n formatValue = String,\n showLabel,\n origin,\n min = 0,\n max = 100,\n className,\n ...props\n}: SliderProps<V>) {\n const s = slider();\n const range = Array.isArray(value ?? defaultValue);\n const [inner, setInner] = React.useState(() => toArray(defaultValue) ?? [min]);\n const current = toArray(value) ?? inner;\n const out = (next: number[]) => (range ? next : next[0]) as V;\n const pct = (v: number) => ((v - min) / (max - min || 1)) * 100;\n const fill =\n origin !== undefined && !range\n ? { left: `${Math.min(pct(origin), pct(current[0]))}%`, right: `${100 - Math.max(pct(origin), pct(current[0]))}%` }\n : undefined;\n const id = React.useId();\n return (\n <div className={s.root({ className })} data-range={range ? \"\" : undefined}>\n {showLabel && (\n <div className={s.header()}>\n <span id={`${id}-label`}>{label}</span>\n <span className={s.value()} aria-hidden>\n {current.map(formatValue).join(\" – \")}\n </span>\n </div>\n )}\n <SliderPrimitive.Root\n min={min}\n max={max}\n value={current}\n onValueChange={(next) => {\n if (value === undefined) setInner(next);\n onValueChange?.(out(next));\n }}\n onValueCommit={onValueCommit && ((next) => onValueCommit(out(next)))}\n className={s.control()}\n {...props}\n >\n <SliderPrimitive.Track className={s.track()}>\n {fill ? <span data-origin=\"\" className={s.range()} style={fill} /> : <SliderPrimitive.Range className={s.range()} />}\n </SliderPrimitive.Track>\n {current.map((v, i) => (\n <SliderPrimitive.Thumb\n key={i}\n aria-label={range ? (thumbLabels?.[i] ?? label) : showLabel ? undefined : label}\n aria-labelledby={!range && showLabel ? `${id}-label` : undefined}\n aria-valuetext={formatValue(v)}\n className={s.thumb()}\n />\n ))}\n </SliderPrimitive.Root>\n </div>\n );\n}\n"
13
+ }
14
+ ],
15
+ "dependencies": [
16
+ "radix-ui@^1.7.0",
17
+ "tailwind-variants@^3.3.1"
18
+ ],
19
+ "registryDependencies": [],
20
+ "demo": {
21
+ "path": "demos/slider.tsx",
22
+ "content": "import { Slider } from \"@krizaka/ui/slider\";\n\nexport default function SliderDemo() {\n return <Slider label=\"Volume\" defaultValue={60} />;\n}\n"
23
+ },
24
+ "props": [
25
+ {
26
+ "component": "Slider",
27
+ "description": "A slider on one value or a range: `value`/`defaultValue` + `onValueChange`, `min`, `max`, `step`.",
28
+ "props": [
29
+ {
30
+ "name": "label",
31
+ "type": "string",
32
+ "default": null,
33
+ "description": "The accessible name — of the thumb, or of both thumbs of a range — passed translated.",
34
+ "required": true
35
+ },
36
+ {
37
+ "name": "defaultValue",
38
+ "type": "SliderValue",
39
+ "default": null,
40
+ "description": "The value at first, uncontrolled: one number, or two for a range.",
41
+ "required": false
42
+ },
43
+ {
44
+ "name": "formatValue",
45
+ "type": "((value: number) => string)",
46
+ "default": null,
47
+ "description": "The value as words (aria-valuetext) and as shown: \"1.5×\", \"−12 %\", \"3 min\".",
48
+ "required": false
49
+ },
50
+ {
51
+ "name": "onValueChange",
52
+ "type": "((value: V) => void)",
53
+ "default": null,
54
+ "description": "Called on every move, with one number or two.",
55
+ "required": false
56
+ },
57
+ {
58
+ "name": "onValueCommit",
59
+ "type": "((value: V) => void)",
60
+ "default": null,
61
+ "description": "Called once the drag or the key press ends.",
62
+ "required": false
63
+ },
64
+ {
65
+ "name": "origin",
66
+ "type": "number",
67
+ "default": null,
68
+ "description": "Fills from this value instead of the minimum (a ±range from 0: brightness, balance). One thumb only.",
69
+ "required": false
70
+ },
71
+ {
72
+ "name": "showLabel",
73
+ "type": "boolean",
74
+ "default": null,
75
+ "description": "Shows the label and the value above the track. Without it, `label` only names the thumb.",
76
+ "required": false
77
+ },
78
+ {
79
+ "name": "thumbLabels",
80
+ "type": "[string, string]",
81
+ "default": null,
82
+ "description": "For a range: each thumb's own name ([\"Minimum\", \"Maximum\"]), passed translated.",
83
+ "required": false
84
+ },
85
+ {
86
+ "name": "value",
87
+ "type": "SliderValue",
88
+ "default": null,
89
+ "description": "One number (a thumb) or two (a range). Controlled with `onValueChange`.",
90
+ "required": false
91
+ }
92
+ ]
93
+ }
94
+ ]
95
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "slot",
3
+ "type": "primitive",
4
+ "description": "`Slot` (what `asChild` renders) and `VisuallyHidden` (a name for assistive technology only), from Radix.",
5
+ "files": [
6
+ {
7
+ "path": "slot/index.ts",
8
+ "content": "/** @krizaka/ui/slot — `Slot` (what `asChild` renders) and `VisuallyHidden` (a name for assistive technology only), from Radix. */\nimport { Slot as SlotPrimitive, VisuallyHidden as VisuallyHiddenPrimitive } from \"radix-ui\";\n\n/** Merges its props, `className` and `ref` into its only child: the engine of every `asChild`. */\nexport const Slot = SlotPrimitive.Root;\n/** Hidden on screen, read by assistive technology. */\nexport const VisuallyHidden = VisuallyHiddenPrimitive.Root;\n"
9
+ }
10
+ ],
11
+ "dependencies": [
12
+ "radix-ui@^1.7.0"
13
+ ],
14
+ "registryDependencies": [],
15
+ "demo": {
16
+ "path": "demos/slot.tsx",
17
+ "content": "import { Slot, VisuallyHidden } from \"@krizaka/ui/slot\";\n\nexport default function SlotDemo() {\n // Slot merges its props and classes into its only child: the link is rendered, styled by the Slot.\n return (\n <Slot className=\"text-sm font-semibold text-fg underline underline-offset-4\">\n <a href=\"#docs\">\n Read the docs\n <VisuallyHidden> about Slot</VisuallyHidden>\n </a>\n </Slot>\n );\n}\n"
18
+ },
19
+ "props": [
20
+ {
21
+ "component": "Slot",
22
+ "description": "Merges its props, `className` and `ref` into its only child: the engine of every `asChild`.",
23
+ "props": []
24
+ },
25
+ {
26
+ "component": "VisuallyHidden",
27
+ "description": "Hidden on screen, read by assistive technology.",
28
+ "props": []
29
+ }
30
+ ]
31
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "spinner",
3
+ "type": "primitive",
4
+ "description": "`Spinner`, `spinnerVariants`. Server-safe.",
5
+ "files": [
6
+ {
7
+ "path": "spinner/index.ts",
8
+ "content": "/** @krizaka/ui/spinner — `Spinner`, `spinnerVariants`. Server-safe. */\nexport { Spinner, type SpinnerProps, spinnerVariants } from \"./spinner\";\n"
9
+ },
10
+ {
11
+ "path": "spinner/spinner.tsx",
12
+ "content": "// Server-safe. An indeterminate wait: an SVG ring, announced as a status with its `label`.\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nexport const spinnerVariants = tv({\n base: \"shrink-0 text-accent motion-safe:animate-spin\",\n variants: { size: { sm: \"h-4 w-4\", md: \"h-6 w-6\", lg: \"h-10 w-10\" } },\n defaultVariants: { size: \"md\" },\n});\n\nexport type SpinnerProps = Omit<React.ComponentProps<\"svg\">, \"children\"> &\n VariantProps<typeof spinnerVariants> & {\n /**\n * sm · md · lg.\n * @default \"md\"\n */\n size?: VariantProps<typeof spinnerVariants>[\"size\"];\n /** The accessible name of the wait, e.g. \"Loading\" — passed translated. */\n label: string;\n };\n\n/** A turning ring with `role=\"status\"` (it stops turning under prefers-reduced-motion). */\nexport function Spinner({ label, size, className, ...props }: SpinnerProps) {\n return (\n <svg\n role=\"status\"\n aria-label={label}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n data-size={size ?? \"md\"}\n className={spinnerVariants({ size, className })}\n {...props}\n >\n <circle cx=\"12\" cy=\"12\" r=\"9\" stroke=\"currentColor\" strokeOpacity=\"0.2\" strokeWidth=\"3\" />\n <path d=\"M21 12a9 9 0 0 0-9-9\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" />\n </svg>\n );\n}\n"
13
+ }
14
+ ],
15
+ "dependencies": [
16
+ "tailwind-variants@^3.3.1"
17
+ ],
18
+ "registryDependencies": [],
19
+ "demo": {
20
+ "path": "demos/spinner.tsx",
21
+ "content": "import { Spinner } from \"@krizaka/ui/spinner\";\n\nexport default function SpinnerDemo() {\n return <Spinner label=\"Loading\" />;\n}\n"
22
+ },
23
+ "props": [
24
+ {
25
+ "component": "Spinner",
26
+ "description": "A turning ring with `role=\"status\"` (it stops turning under prefers-reduced-motion).",
27
+ "props": [
28
+ {
29
+ "name": "label",
30
+ "type": "string",
31
+ "default": null,
32
+ "description": "The accessible name of the wait, e.g. \"Loading\" — passed translated.",
33
+ "required": true
34
+ },
35
+ {
36
+ "name": "size",
37
+ "type": "\"sm\" | \"md\" | \"lg\"",
38
+ "default": "md",
39
+ "description": "sm · md · lg.",
40
+ "required": false
41
+ }
42
+ ]
43
+ }
44
+ ]
45
+ }
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "stat",
3
+ "type": "primitive",
4
+ "description": "`Stat`, `stat` (the slots). Server-safe.",
5
+ "files": [
6
+ {
7
+ "path": "stat/index.ts",
8
+ "content": "/** @krizaka/ui/stat — `Stat`, `stat` (the slots). Server-safe. */\nexport { Stat, stat, type StatProps, type StatTrend } from \"./stat\";\n"
9
+ },
10
+ {
11
+ "path": "stat/stat.tsx",
12
+ "content": "// Server-safe. A key figure: a label, the value in tabular digits, a hint, a trend. Every word is a prop.\nimport type * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nexport const stat = tv({\n slots: {\n root: \"flex min-w-0 flex-col gap-1\",\n label: \"text-xs font-semibold uppercase tracking-wider text-fg-secondary\",\n value: \"font-display text-2xl font-black tabular-nums text-fg\",\n meta: \"flex flex-wrap items-center gap-x-2 gap-y-1 text-xs\",\n // The status tokens are too light for small text on a light surface: the words stay a text role, the arrow carries\n // the direction's colour.\n trend: \"inline-flex items-center gap-1 font-semibold text-fg [&>svg]:h-3.5 [&>svg]:w-3.5 [&>svg]:shrink-0\",\n hint: \"text-fg-secondary\",\n },\n variants: {\n trend: {\n up: { trend: \"[&>svg]:text-success\" },\n down: { trend: \"[&>svg]:text-danger\" },\n flat: { trend: \"[&>svg]:text-fg-secondary\" },\n },\n },\n});\n\nexport type StatTrend = \"up\" | \"down\" | \"flat\";\n\nconst ARROW: Record<StatTrend, string> = { up: \"M7 17 17 7M8 7h9v9\", down: \"M7 7l10 10M17 8v9H8\", flat: \"M5 12h14M13 6l6 6-6 6\" };\n\nexport type StatProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n /** What the figure is (\"Revenue\"), above it. */\n label: React.ReactNode;\n /** The figure, formatted by the app, in tabular digits. */\n value: React.ReactNode;\n /** A line under the value: the period, the comparison. */\n hint?: React.ReactNode;\n /** The direction of the change; its arrow is decorative. */\n trend?: StatTrend;\n /** The change, in words, as read and shown (e.g. \"+12 % vs last week\") — the arrow alone says nothing. */\n trendLabel?: React.ReactNode;\n};\n\nexport function Stat({ label, value, hint, trend, trendLabel, className, ...props }: StatProps) {\n const s = stat({ trend });\n return (\n <div data-trend={trend} className={s.root({ className })} {...props}>\n <p className={s.label()}>{label}</p>\n <p className={s.value()}>{value}</p>\n {(trendLabel || hint) && (\n <p className={s.meta()}>\n {trend && trendLabel && (\n <span className={s.trend()}>\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d={ARROW[trend]} />\n </svg>\n {trendLabel}\n </span>\n )}\n {hint && <span className={s.hint()}>{hint}</span>}\n </p>\n )}\n </div>\n );\n}\n"
13
+ }
14
+ ],
15
+ "dependencies": [
16
+ "tailwind-variants@^3.3.1"
17
+ ],
18
+ "registryDependencies": [],
19
+ "demo": {
20
+ "path": "demos/stat.tsx",
21
+ "content": "import { Stat } from \"@krizaka/ui/stat\";\n\nexport default function StatDemo() {\n return <Stat label=\"Revenue\" value=\"€4,812\" hint=\"vs last month\" trend=\"up\" trendLabel=\"+12 %\" />;\n}\n"
22
+ },
23
+ "props": [
24
+ {
25
+ "component": "Stat",
26
+ "description": "",
27
+ "props": [
28
+ {
29
+ "name": "label",
30
+ "type": "ReactNode",
31
+ "default": null,
32
+ "description": "What the figure is (\"Revenue\"), above it.",
33
+ "required": true
34
+ },
35
+ {
36
+ "name": "value",
37
+ "type": "ReactNode",
38
+ "default": null,
39
+ "description": "The figure, formatted by the app, in tabular digits.",
40
+ "required": true
41
+ },
42
+ {
43
+ "name": "hint",
44
+ "type": "ReactNode",
45
+ "default": null,
46
+ "description": "A line under the value: the period, the comparison.",
47
+ "required": false
48
+ },
49
+ {
50
+ "name": "trend",
51
+ "type": "StatTrend",
52
+ "default": null,
53
+ "description": "The direction of the change; its arrow is decorative.",
54
+ "required": false
55
+ },
56
+ {
57
+ "name": "trendLabel",
58
+ "type": "ReactNode",
59
+ "default": null,
60
+ "description": "The change, in words, as read and shown (e.g. \"+12 % vs last week\") — the arrow alone says nothing.",
61
+ "required": false
62
+ }
63
+ ]
64
+ }
65
+ ]
66
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "switch",
3
+ "type": "primitive",
4
+ "description": "`Switch`, `switchVariants`. Client (\"use client\").",
5
+ "files": [
6
+ {
7
+ "path": "switch/index.ts",
8
+ "content": "/** @krizaka/ui/switch — `Switch`, `switchVariants`. Client (\"use client\"). */\nexport { Switch, type SwitchProps, type SwitchVariants,switchVariants } from \"./switch\";\n"
9
+ },
10
+ {
11
+ "path": "switch/switch.tsx",
12
+ "content": "// Client. Radix Switch: role=\"switch\", aria-checked, Space and Enter toggle, a hidden input inside a form.\nimport { Switch as SwitchPrimitive } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nexport const switchVariants = tv({\n slots: {\n root:\n \"peer relative inline-flex shrink-0 cursor-pointer items-center rounded-full border border-transparent bg-fg-muted transition-colors \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface-0 \" +\n \"disabled:cursor-not-allowed disabled:opacity-40 data-[state=checked]:bg-accent aria-invalid:border-danger\",\n thumb:\n \"pointer-events-none block rounded-full bg-fg-on-media shadow-sm transition-transform data-[state=unchecked]:translate-x-0.5 motion-reduce:transition-none\",\n },\n variants: {\n size: {\n sm: { root: \"h-5 w-9\", thumb: \"h-4 w-4 data-[state=checked]:translate-x-[1.125rem]\" },\n md: { root: \"h-6 w-11\", thumb: \"h-5 w-5 data-[state=checked]:translate-x-[1.375rem]\" },\n },\n },\n defaultVariants: { size: \"md\" },\n});\n\nexport type SwitchVariants = VariantProps<typeof switchVariants>;\n\ntype Named =\n | {\n /** The accessible name — required when no visible label names it, passed translated. */\n label: string;\n }\n /** Named by a visible `<label htmlFor={id}>` (e.g. `Field.Label`). */\n | { label?: undefined; id: string }\n /** Named by another element. */\n | { label?: undefined; \"aria-labelledby\": string };\n\nexport type SwitchProps = Omit<React.ComponentProps<typeof SwitchPrimitive.Root>, \"children\"> &\n SwitchVariants &\n Named & {\n /**\n * sm · md.\n * @default \"md\"\n */\n size?: SwitchVariants[\"size\"];\n /** Marks it invalid: aria-invalid and data-invalid. */\n invalid?: boolean;\n };\n\n/** An on/off switch: `checked`/`defaultChecked` + `onCheckedChange`. */\nexport function Switch({ label, size, invalid, className, ...props }: SwitchProps) {\n const s = switchVariants({ size });\n return (\n <SwitchPrimitive.Root\n aria-label={label}\n aria-invalid={invalid || undefined}\n data-invalid={invalid ? \"\" : undefined}\n className={s.root({ className })}\n {...props}\n >\n <SwitchPrimitive.Thumb className={s.thumb()} />\n </SwitchPrimitive.Root>\n );\n}\n"
13
+ }
14
+ ],
15
+ "dependencies": [
16
+ "radix-ui@^1.7.0",
17
+ "tailwind-variants@^3.3.1"
18
+ ],
19
+ "registryDependencies": [],
20
+ "demo": {
21
+ "path": "demos/switch.tsx",
22
+ "content": "import { Switch } from \"@krizaka/ui/switch\";\n\nexport default function SwitchDemo() {\n return <Switch label=\"Autoplay\" />;\n}\n"
23
+ },
24
+ "props": [
25
+ {
26
+ "component": "Switch",
27
+ "description": "An on/off switch: `checked`/`defaultChecked` + `onCheckedChange`.",
28
+ "props": [
29
+ {
30
+ "name": "invalid",
31
+ "type": "boolean",
32
+ "default": null,
33
+ "description": "Marks it invalid: aria-invalid and data-invalid.",
34
+ "required": false
35
+ },
36
+ {
37
+ "name": "label",
38
+ "type": "string",
39
+ "default": null,
40
+ "description": "The accessible name — required when no visible label names it, passed translated.",
41
+ "required": false
42
+ },
43
+ {
44
+ "name": "size",
45
+ "type": "\"sm\" | \"md\"",
46
+ "default": "md",
47
+ "description": "sm · md.",
48
+ "required": false
49
+ }
50
+ ]
51
+ }
52
+ ]
53
+ }