@krizaka/ui 3.8.0 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/README.md +10 -8
  2. package/dist/action-bar.js +2 -1
  3. package/dist/action-bar.js.map +1 -1
  4. package/dist/app-shell.d.ts +81 -3
  5. package/dist/app-shell.js +163 -35
  6. package/dist/app-shell.js.map +1 -1
  7. package/dist/audio-player.js +1 -1
  8. package/dist/badge.d.ts +36 -39
  9. package/dist/badge.js +11 -2
  10. package/dist/badge.js.map +1 -1
  11. package/dist/blocks.css +56 -4
  12. package/dist/button-BmCrLCq7.d.ts +114 -0
  13. package/dist/button.d.ts +48 -48
  14. package/dist/button.js +2 -1
  15. package/dist/card.d.ts +111 -111
  16. package/dist/card.js +10 -2
  17. package/dist/card.js.map +1 -1
  18. package/dist/chip.d.ts +24 -24
  19. package/dist/chip.js +10 -2
  20. package/dist/chip.js.map +1 -1
  21. package/dist/{chunk-UOMP6VOZ.js → chunk-276PTQMQ.js} +10 -4
  22. package/dist/chunk-276PTQMQ.js.map +1 -0
  23. package/dist/{chunk-Z6FUPB3Q.js → chunk-2NB27LM7.js} +11 -3
  24. package/dist/chunk-2NB27LM7.js.map +1 -0
  25. package/dist/chunk-2VNYHG4E.js +9 -0
  26. package/dist/chunk-2VNYHG4E.js.map +1 -0
  27. package/dist/{chunk-ND3L5VHT.js → chunk-5Z7FWCV2.js} +2 -2
  28. package/dist/{chunk-YH4LMWRU.js → chunk-7UCPGXKN.js} +11 -3
  29. package/dist/chunk-7UCPGXKN.js.map +1 -0
  30. package/dist/{chunk-AZPKXP3O.js → chunk-AFY35UCB.js} +6 -1
  31. package/dist/{chunk-AZPKXP3O.js.map → chunk-AFY35UCB.js.map} +1 -1
  32. package/dist/{chunk-LCZXS6AK.js → chunk-CKSHLSDH.js} +5 -5
  33. package/dist/chunk-CKSHLSDH.js.map +1 -0
  34. package/dist/chunk-D3T54RSL.js +11 -0
  35. package/dist/chunk-D3T54RSL.js.map +1 -0
  36. package/dist/{chunk-ZKVQGXLD.js → chunk-FKBHWS4N.js} +2 -2
  37. package/dist/{chunk-BRKMQ6JV.js → chunk-FZBWEDQI.js} +1 -23
  38. package/dist/chunk-FZBWEDQI.js.map +1 -0
  39. package/dist/{chunk-HCNOOPZE.js → chunk-KVDB4GXV.js} +2 -2
  40. package/dist/{chunk-RUPNPMP3.js → chunk-OWC2LZD2.js} +11 -3
  41. package/dist/chunk-OWC2LZD2.js.map +1 -0
  42. package/dist/{chunk-FDCMBWUU.js → chunk-REPTKUJ6.js} +4 -4
  43. package/dist/chunk-REPTKUJ6.js.map +1 -0
  44. package/dist/code-block.d.ts +32 -10
  45. package/dist/code-block.js +51 -43
  46. package/dist/code-block.js.map +1 -1
  47. package/dist/combobox.d.ts +3 -3
  48. package/dist/combobox.js +4 -3
  49. package/dist/command.js +3 -2
  50. package/dist/command.js.map +1 -1
  51. package/dist/component-page.js.map +1 -1
  52. package/dist/confirm-button.d.ts +1 -1
  53. package/dist/confirm-button.js +2 -1
  54. package/dist/confirm-button.js.map +1 -1
  55. package/dist/continue-prompt.js +11 -3
  56. package/dist/continue-prompt.js.map +1 -1
  57. package/dist/data-table.js +7 -6
  58. package/dist/data-table.js.map +1 -1
  59. package/dist/date-picker.js +1 -1
  60. package/dist/dialog.js +3 -2
  61. package/dist/dialog.js.map +1 -1
  62. package/dist/file-upload.d.ts +12 -1
  63. package/dist/file-upload.js +73 -89
  64. package/dist/file-upload.js.map +1 -1
  65. package/dist/filter-bar.js +3 -2
  66. package/dist/image-cropper.js +1 -1
  67. package/dist/list.js +1 -1
  68. package/dist/map.js.map +1 -1
  69. package/dist/markdown.d.ts +38 -0
  70. package/dist/markdown.js +150 -0
  71. package/dist/markdown.js.map +1 -0
  72. package/dist/match-dialog.js +2 -1
  73. package/dist/match-dialog.js.map +1 -1
  74. package/dist/media-capture.js +1 -1
  75. package/dist/native/markdown.d.ts +24 -0
  76. package/dist/native/markdown.js +200 -0
  77. package/dist/native/markdown.js.map +1 -0
  78. package/dist/native.d.ts +1 -7
  79. package/dist/native.js +357 -95
  80. package/dist/native.js.map +1 -1
  81. package/dist/recipes.d.ts +143 -0
  82. package/dist/recipes.js +180 -0
  83. package/dist/recipes.js.map +1 -0
  84. package/dist/search-field.js +2 -2
  85. package/dist/swipe-deck.js +1 -1
  86. package/dist/tag-input.js +4 -3
  87. package/dist/tag-input.js.map +1 -1
  88. package/dist/tag-selector.js +2 -1
  89. package/dist/tag-selector.js.map +1 -1
  90. package/dist/theme.d.ts +1 -1
  91. package/dist/theme.js +2 -1
  92. package/dist/theme.js.map +1 -1
  93. package/dist/tree-view.js +1 -1
  94. package/dist/vertical-video.js +2 -1
  95. package/dist/vertical-video.js.map +1 -1
  96. package/dist/video-player.js +1 -1
  97. package/dist/video-trimmer.js +1 -1
  98. package/dist/virtual.js +2 -2
  99. package/package.json +47 -16
  100. package/registry/achievement.json +1 -1
  101. package/registry/action-bar.json +1 -1
  102. package/registry/activity-feed.json +1 -1
  103. package/registry/alert.json +1 -1
  104. package/registry/app-shell.json +135 -12
  105. package/registry/argument-card.json +1 -1
  106. package/registry/audio-player.json +2 -2
  107. package/registry/auth-layout.json +1 -1
  108. package/registry/avatar.json +1 -1
  109. package/registry/badge.json +9 -3
  110. package/registry/button.json +7 -2
  111. package/registry/card-grid.json +1 -1
  112. package/registry/card.json +8 -3
  113. package/registry/catalogue-page.json +1 -1
  114. package/registry/changelog-page.json +1 -1
  115. package/registry/chat.json +1 -1
  116. package/registry/checkbox.json +1 -1
  117. package/registry/chip-link.json +1 -1
  118. package/registry/chip.json +9 -3
  119. package/registry/choice-grid.json +1 -1
  120. package/registry/code-block.json +65 -21
  121. package/registry/combobox.json +4 -3
  122. package/registry/command.json +1 -1
  123. package/registry/component-page.json +4 -4
  124. package/registry/confirm-button.json +1 -1
  125. package/registry/contact-page.json +1 -1
  126. package/registry/continue-prompt.json +1 -1
  127. package/registry/countdown.json +1 -1
  128. package/registry/cta-panel.json +1 -1
  129. package/registry/dashboard-layout.json +1 -1
  130. package/registry/data-table.json +2 -2
  131. package/registry/date-picker.json +3 -3
  132. package/registry/decision-list.json +1 -1
  133. package/registry/description-list.json +1 -1
  134. package/registry/detail-layout.json +1 -1
  135. package/registry/dialog.json +1 -1
  136. package/registry/docs-article-page.json +1 -1
  137. package/registry/docs-hub-page.json +1 -1
  138. package/registry/docs-layout.json +1 -1
  139. package/registry/dropdown-menu.json +1 -1
  140. package/registry/empty-state.json +1 -1
  141. package/registry/error-page.json +1 -1
  142. package/registry/examples/app-shell/console.d.ts +4 -0
  143. package/registry/examples/app-shell/console.js +93 -0
  144. package/registry/examples/app-shell/console.tsx +103 -0
  145. package/registry/examples/badge/native/dot.dark.png +0 -0
  146. package/registry/examples/badge/native/dot.light.png +0 -0
  147. package/registry/examples/badge/native/scrim.dark.png +0 -0
  148. package/registry/examples/badge/native/scrim.light.png +0 -0
  149. package/registry/examples/badge/native/tones.dark.png +0 -0
  150. package/registry/examples/badge/native/tones.light.png +0 -0
  151. package/registry/examples/card/native/default.dark.png +0 -0
  152. package/registry/examples/card/native/default.light.png +0 -0
  153. package/registry/examples/card/native/elevated.light.png +0 -0
  154. package/registry/examples/chip/native/multiple.dark.png +0 -0
  155. package/registry/examples/chip/native/multiple.light.png +0 -0
  156. package/registry/examples/chip/native/removable.dark.png +0 -0
  157. package/registry/examples/chip/native/removable.light.png +0 -0
  158. package/registry/examples/chip/native/single.dark.png +0 -0
  159. package/registry/examples/chip/native/single.light.png +0 -0
  160. package/registry/examples/chip/native/toggle.dark.png +0 -0
  161. package/registry/examples/chip/native/toggle.light.png +0 -0
  162. package/registry/examples/code-block/annotated.d.ts +4 -0
  163. package/registry/examples/code-block/annotated.js +31 -0
  164. package/registry/examples/code-block/annotated.tsx +26 -0
  165. package/registry/examples/component-page/button.tsx +1 -1
  166. package/registry/examples/form/native/amount.dark.png +0 -0
  167. package/registry/examples/form/native/amount.light.png +0 -0
  168. package/registry/examples/markdown/ai-stream.d.ts +4 -0
  169. package/registry/examples/markdown/ai-stream.js +31 -0
  170. package/registry/examples/markdown/ai-stream.tsx +32 -0
  171. package/registry/examples/markdown/basic.d.ts +4 -0
  172. package/registry/examples/markdown/basic.js +26 -0
  173. package/registry/examples/markdown/basic.tsx +22 -0
  174. package/registry/examples/markdown/native/ai-stream.d.ts +4 -0
  175. package/registry/examples/markdown/native/ai-stream.dark.png +0 -0
  176. package/registry/examples/markdown/native/ai-stream.js +17 -0
  177. package/registry/examples/markdown/native/ai-stream.light.png +0 -0
  178. package/registry/examples/markdown/native/ai-stream.tsx +17 -0
  179. package/registry/examples/markdown/native/basic.d.ts +4 -0
  180. package/registry/examples/markdown/native/basic.dark.png +0 -0
  181. package/registry/examples/markdown/native/basic.js +28 -0
  182. package/registry/examples/markdown/native/basic.light.png +0 -0
  183. package/registry/examples/markdown/native/basic.tsx +27 -0
  184. package/registry/examples/recipes/tone-chip.d.ts +4 -0
  185. package/registry/examples/recipes/tone-chip.js +46 -0
  186. package/registry/examples/recipes/tone-chip.tsx +32 -0
  187. package/registry/examples/screen/native/profile.dark.png +0 -0
  188. package/registry/examples/screen/native/profile.light.png +0 -0
  189. package/registry/examples/tag-selector/native/interests.dark.png +0 -0
  190. package/registry/examples/tag-selector/native/interests.light.png +0 -0
  191. package/registry/field.json +1 -1
  192. package/registry/file-upload.json +8 -3
  193. package/registry/filter-bar.json +1 -1
  194. package/registry/form.json +2 -2
  195. package/registry/gallery-page.json +1 -1
  196. package/registry/glow-card.json +1 -1
  197. package/registry/goal-gauge.json +1 -1
  198. package/registry/how-it-works-page.json +1 -1
  199. package/registry/image-cropper.json +2 -2
  200. package/registry/index.json +76 -16
  201. package/registry/iso.json +1 -1
  202. package/registry/kanban.json +1 -1
  203. package/registry/kbd.json +1 -1
  204. package/registry/landing-page.json +1 -1
  205. package/registry/landscape.json +1 -1
  206. package/registry/legal-page.json +1 -1
  207. package/registry/link-card.json +1 -1
  208. package/registry/list.json +2 -2
  209. package/registry/loader.json +1 -1
  210. package/registry/longform-page.json +1 -1
  211. package/registry/map.json +3 -3
  212. package/registry/markdown.json +266 -0
  213. package/registry/marketing-layout.json +1 -1
  214. package/registry/marquee.json +1 -1
  215. package/registry/mascot-quote.json +1 -1
  216. package/registry/mascot-state.json +1 -1
  217. package/registry/mascots.json +1 -1
  218. package/registry/match-dialog.json +1 -1
  219. package/registry/media-capture.json +2 -2
  220. package/registry/meter.json +1 -1
  221. package/registry/nav-bar.json +1 -1
  222. package/registry/number-ticker.json +1 -1
  223. package/registry/page-header.json +1 -1
  224. package/registry/page-hero.json +1 -1
  225. package/registry/page.json +1 -1
  226. package/registry/pagination.json +1 -1
  227. package/registry/photo-grid.json +1 -1
  228. package/registry/pointer-light.json +1 -1
  229. package/registry/popover.json +1 -1
  230. package/registry/pricing-page.json +1 -1
  231. package/registry/product-page.json +1 -1
  232. package/registry/profile-card.json +1 -1
  233. package/registry/progress.json +1 -1
  234. package/registry/promise-card.json +1 -1
  235. package/registry/radio-group.json +1 -1
  236. package/registry/recipes.json +99 -0
  237. package/registry/scroll-stage.json +1 -1
  238. package/registry/search-field.json +4 -3
  239. package/registry/section-backdrop.json +1 -1
  240. package/registry/separator.json +1 -1
  241. package/registry/settings-layout.json +1 -1
  242. package/registry/skeleton.json +1 -1
  243. package/registry/skip-link.json +1 -1
  244. package/registry/slider.json +1 -1
  245. package/registry/slot.json +1 -1
  246. package/registry/spinner.json +1 -1
  247. package/registry/split-view.json +1 -1
  248. package/registry/stage-banner.json +1 -1
  249. package/registry/stat.json +1 -1
  250. package/registry/status-page.json +1 -1
  251. package/registry/step-progress.json +1 -1
  252. package/registry/stepper.json +1 -1
  253. package/registry/swipe-deck.json +2 -2
  254. package/registry/switch.json +1 -1
  255. package/registry/tabs.json +1 -1
  256. package/registry/tag-input.json +2 -2
  257. package/registry/tag-selector.json +1 -1
  258. package/registry/terminal-replay.json +1 -1
  259. package/registry/theme.json +1 -1
  260. package/registry/toast.json +1 -1
  261. package/registry/toc.json +1 -1
  262. package/registry/tooltip.json +1 -1
  263. package/registry/tree-view.json +2 -2
  264. package/registry/verified-badge.json +1 -1
  265. package/registry/vertical-video.json +2 -2
  266. package/registry/video-player.json +2 -2
  267. package/registry/video-trimmer.json +2 -2
  268. package/registry/virtual.json +2 -2
  269. package/dist/button-BMAO7dKF.d.ts +0 -114
  270. package/dist/chunk-BRKMQ6JV.js.map +0 -1
  271. package/dist/chunk-FDCMBWUU.js.map +0 -1
  272. package/dist/chunk-LCZXS6AK.js.map +0 -1
  273. package/dist/chunk-RUPNPMP3.js.map +0 -1
  274. package/dist/chunk-UOMP6VOZ.js.map +0 -1
  275. package/dist/chunk-YH4LMWRU.js.map +0 -1
  276. package/dist/chunk-Z6FUPB3Q.js.map +0 -1
  277. /package/dist/{chunk-ND3L5VHT.js.map → chunk-5Z7FWCV2.js.map} +0 -0
  278. /package/dist/{chunk-ZKVQGXLD.js.map → chunk-FKBHWS4N.js.map} +0 -0
  279. /package/dist/{chunk-HCNOOPZE.js.map → chunk-KVDB4GXV.js.map} +0 -0
@@ -1,114 +0,0 @@
1
- import * as tailwind_variants from 'tailwind-variants';
2
- import { VariantProps } from 'tailwind-variants';
3
- import * as React from 'react';
4
-
5
- declare const buttonVariants: tailwind_variants.TVReturnType<{
6
- variant: {
7
- primary: "bg-accent text-on-accent shadow-sm hover:bg-accent-hover";
8
- gradient: "bg-linear-to-r from-accent to-accent-2 text-on-accent shadow-sm hover:brightness-110";
9
- secondary: "border border-border-default bg-surface-2 text-fg hover:border-border-strong hover:bg-surface-3";
10
- outline: "border border-border-default bg-transparent text-fg hover:border-accent hover:text-fg-accent";
11
- ghost: "text-fg-secondary hover:bg-surface-2 hover:text-fg";
12
- danger: "border border-danger/50 bg-danger/10 text-fg hover:border-danger hover:bg-danger/20";
13
- };
14
- size: {
15
- sm: "h-8 px-3 text-xs";
16
- md: "h-10 px-4 text-sm";
17
- lg: "h-12 px-6 text-sm";
18
- icon: "h-10 w-10";
19
- };
20
- shape: {
21
- rounded: "rounded-lg";
22
- pill: "rounded-full";
23
- cut: "rounded-[10px] [corner-shape:bevel]";
24
- };
25
- }, undefined, string, {
26
- variant: {
27
- primary: "bg-accent text-on-accent shadow-sm hover:bg-accent-hover";
28
- gradient: "bg-linear-to-r from-accent to-accent-2 text-on-accent shadow-sm hover:brightness-110";
29
- secondary: "border border-border-default bg-surface-2 text-fg hover:border-border-strong hover:bg-surface-3";
30
- outline: "border border-border-default bg-transparent text-fg hover:border-accent hover:text-fg-accent";
31
- ghost: "text-fg-secondary hover:bg-surface-2 hover:text-fg";
32
- danger: "border border-danger/50 bg-danger/10 text-fg hover:border-danger hover:bg-danger/20";
33
- };
34
- size: {
35
- sm: "h-8 px-3 text-xs";
36
- md: "h-10 px-4 text-sm";
37
- lg: "h-12 px-6 text-sm";
38
- icon: "h-10 w-10";
39
- };
40
- shape: {
41
- rounded: "rounded-lg";
42
- pill: "rounded-full";
43
- cut: "rounded-[10px] [corner-shape:bevel]";
44
- };
45
- }, undefined, tailwind_variants.TVReturnTypeLike<{
46
- variant: {
47
- primary: "bg-accent text-on-accent shadow-sm hover:bg-accent-hover";
48
- gradient: "bg-linear-to-r from-accent to-accent-2 text-on-accent shadow-sm hover:brightness-110";
49
- secondary: "border border-border-default bg-surface-2 text-fg hover:border-border-strong hover:bg-surface-3";
50
- outline: "border border-border-default bg-transparent text-fg hover:border-accent hover:text-fg-accent";
51
- ghost: "text-fg-secondary hover:bg-surface-2 hover:text-fg";
52
- danger: "border border-danger/50 bg-danger/10 text-fg hover:border-danger hover:bg-danger/20";
53
- };
54
- size: {
55
- sm: "h-8 px-3 text-xs";
56
- md: "h-10 px-4 text-sm";
57
- lg: "h-12 px-6 text-sm";
58
- icon: "h-10 w-10";
59
- };
60
- shape: {
61
- rounded: "rounded-lg";
62
- pill: "rounded-full";
63
- cut: "rounded-[10px] [corner-shape:bevel]";
64
- };
65
- }, undefined>>;
66
- type ButtonVariants = VariantProps<typeof buttonVariants>;
67
- type ButtonProps = React.ComponentProps<"button"> & ButtonVariants & {
68
- /**
69
- * The look: `primary` (the accent), `gradient` (accent → accent-2), `secondary` (a raised surface), `outline`,
70
- * `ghost`, `danger`.
71
- * @default "secondary"
72
- */
73
- variant?: ButtonVariants["variant"];
74
- /**
75
- * sm · md · lg, or `icon` (a square).
76
- * @default "md"
77
- */
78
- size?: ButtonVariants["size"];
79
- /**
80
- * rounded · pill · cut (45° corners, the Krizaka chamfer).
81
- * @default "rounded"
82
- */
83
- shape?: ButtonVariants["shape"];
84
- /** Renders the child (e.g. a <Link>) instead of the <button>, with props, classes and ref merged. */
85
- asChild?: boolean;
86
- /**
87
- * Disables and signals the wait (aria-busy, data-loading) and shows the Krizaka loader glyph before the label; the
88
- * button keeps its colour.
89
- */
90
- loading?: boolean;
91
- /**
92
- * Between 0 and 1, while `loading`: the glyph's trace and a light fill across the button grow to the value (an
93
- * upload, an export).
94
- */
95
- progress?: number | null;
96
- /**
97
- * How the action ended: `success` draws a check, `error` a cross (and the button shakes its head once); a ring of
98
- * the status colour opens around it. Clear it to return to rest.
99
- */
100
- status?: "success" | "error" | null;
101
- /** A message announced politely by assistive technology when it changes (“Saved”, “Upload failed”) — passed translated. */
102
- announce?: string;
103
- /**
104
- * Shows the glyph while `loading` / with a `status`. `false`: you draw your own.
105
- * @default true
106
- */
107
- indicator?: boolean;
108
- /** A sheen of light crosses the button on hover — for the one action a page is about. */
109
- shimmer?: boolean;
110
- /** The button leans a few pixels toward a fine pointer (driven by `MotionObserver`; never under reduced motion or on touch). */
111
- magnetic?: boolean;
112
- };
113
-
114
- export type { ButtonProps as B, ButtonVariants as a };
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/media.ts"],"sourcesContent":["// Shared by the data and media primitives (client): the clock format, the reduced-motion preference, the\n// in-view observer, a debounced callback. Never an entry of its own.\nimport * as React from \"react\";\n\n/** Seconds as a clock: `75` → `1:15`, `3725` → `1:02:05`. Negative or unknown values read `0:00`. */\nexport function formatClock(seconds: number): string {\n const s = Number.isFinite(seconds) && seconds > 0 ? Math.floor(seconds) : 0;\n const h = Math.floor(s / 3600);\n const m = Math.floor((s % 3600) / 60);\n const rest = String(s % 60).padStart(2, \"0\");\n return h > 0 ? `${h}:${String(m).padStart(2, \"0\")}:${rest}` : `${m}:${rest}`;\n}\n\n/** Clamps `value` between `min` and `max`. */\nexport const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\n\nconst REDUCE = \"(prefers-reduced-motion: reduce)\";\nconst subscribeReduce = (notify: () => void) => {\n if (typeof window === \"undefined\" || !window.matchMedia) return () => undefined;\n const query = window.matchMedia(REDUCE);\n query.addEventListener?.(\"change\", notify);\n return () => query.removeEventListener?.(\"change\", notify);\n};\n\n/** `true` when the person asked for less motion (`prefers-reduced-motion: reduce`); follows a change of the setting. */\nexport function usePrefersReducedMotion(): boolean {\n return React.useSyncExternalStore(\n subscribeReduce,\n () => (typeof window !== \"undefined\" && window.matchMedia ? window.matchMedia(REDUCE).matches : false),\n () => false,\n );\n}\n\n/** The latest value of a callback, for an effect or an event that must not re-subscribe when it changes. */\nexport function useLatest<T>(value: T) {\n const ref = React.useRef(value);\n React.useLayoutEffect(() => {\n ref.current = value;\n });\n return ref;\n}\n\n/** Calls `onChange(visible)` when at least `threshold` of the element enters or leaves the viewport. */\nexport function useInView(ref: React.RefObject<Element | null>, onChange: (visible: boolean) => void, enabled = true, threshold = 0.25) {\n const latest = useLatest(onChange);\n React.useEffect(() => {\n const element = ref.current;\n if (!enabled || !element || typeof IntersectionObserver === \"undefined\") return;\n const observer = new IntersectionObserver(([entry]) => entry && latest.current(entry.isIntersecting), { threshold });\n observer.observe(element);\n return () => observer.disconnect();\n }, [ref, enabled, threshold, latest]);\n}\n\n/** A stable function that runs `callback` once `delay` ms have passed without a new call; `cancel()` drops the pending one. */\nexport function useDebouncedCallback<A extends unknown[]>(callback: (...args: A) => void, delay: number) {\n const latest = useLatest(callback);\n const [debounced] = React.useState(() => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n let wait = delay;\n const run = (...args: A) => {\n clearTimeout(timer);\n if (wait <= 0) latest.current(...args);\n else timer = setTimeout(() => latest.current(...args), wait);\n };\n return Object.assign(run, {\n cancel: () => clearTimeout(timer),\n setDelay: (next: number) => {\n wait = next;\n },\n });\n });\n React.useEffect(() => debounced.setDelay(delay), [debounced, delay]);\n React.useEffect(() => () => debounced.cancel(), [debounced]);\n return debounced;\n}\n\n/** A value controlled by a prop when it is given, held inside otherwise; `set` calls `onChange` either way. */\nexport function useControllable<T>(value: T | undefined, defaultValue: T, onChange?: (next: T) => void) {\n const [inner, setInner] = React.useState(defaultValue);\n const controlled = value !== undefined;\n const current = controlled ? value : inner;\n const set = React.useCallback(\n (next: T) => {\n if (!controlled) setInner(next);\n onChange?.(next);\n },\n [controlled, onChange],\n );\n return [current, set] as const;\n}\n\n/** Starts a media element without an unhandled rejection (autoplay refused, the source replaced meanwhile). */\nexport function playSafely(media: HTMLMediaElement | null | undefined) {\n try {\n const started = media?.play() as Promise<void> | undefined;\n started?.catch?.(() => undefined);\n } catch {\n // Not playable here (no media support): the controls stay as they are.\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/search-field/search-field.tsx","../src/kbd/kbd.tsx"],"sourcesContent":["// Client. A search input (`type=\"search\"` inside a `role=\"search\"` landmark when it stands alone): a leading glyph,\n// a clear button, a global shortcut that focuses it (⌘K on Apple devices, Ctrl+K elsewhere, shown as `Kbd`), and\n// optional suggestions through the shared combobox engine. `onSearch` is debounced; Enter submits.\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { CloseGlyph, SearchGlyph } from \"../glyphs\";\nimport { Kbd } from \"../kbd\";\nimport { useListbox } from \"../listbox\";\nimport { useControllable, useDebouncedCallback } from \"../media\";\n\nexport const searchField = tv({\n slots: {\n root: \"relative w-full\",\n control:\n \"flex h-11 w-full items-center gap-2 rounded-lg border border-border-default bg-surface-2 pl-3 pr-1.5 text-sm text-fg \" +\n \"transition-[border-color,box-shadow] focus-within:border-accent focus-within:ring-2 focus-within:ring-ring\",\n icon: \"h-4 w-4 shrink-0 text-fg-secondary\",\n input:\n \"h-full min-w-0 flex-1 bg-transparent text-sm text-fg outline-hidden placeholder:text-fg-muted \" +\n \"[&::-webkit-search-cancel-button]:appearance-none\",\n clear:\n \"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-fg-secondary hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n shortcut: \"hidden shrink-0 items-center gap-0.5 pr-1 sm:flex\",\n list: \"absolute inset-x-0 top-full z-50 mt-1 max-h-72 overflow-y-auto rounded-lg border border-border-default bg-surface-2 p-1 shadow-lg\",\n option: \"flex cursor-pointer select-none items-center gap-2.5 rounded-md px-2.5 py-2 text-sm text-fg data-[active]:bg-surface-3\",\n group: \"px-2.5 pb-1 pt-2 text-[11px] font-semibold uppercase text-fg-muted\",\n },\n variants: {\n size: { md: {}, sm: { control: \"h-9\", clear: \"h-7 w-7\" } },\n },\n defaultVariants: { size: \"md\" },\n});\n\nexport type SearchSuggestion = {\n /** What `onSuggestionSelect` receives. */\n value: string;\n /** What the person reads. */\n label: string;\n /** A small text on the right (\"Creator\", \"12 videos\"). */\n meta?: string;\n};\n\nexport type SearchFieldProps = Omit<React.ComponentProps<\"input\">, \"value\" | \"defaultValue\" | \"onChange\" | \"onSubmit\" | \"size\" | \"type\"> & {\n /** The accessible name of the input (\"Search videos\"), passed translated. */\n label: string;\n /** The query (controlled). */\n value?: string;\n /** The query at first, uncontrolled. */\n defaultValue?: string;\n /** Called on every key with the query. */\n onValueChange?: (query: string) => void;\n /** Called with the query once typing pauses (`debounce` ms): fetch the results here. */\n onSearch?: (query: string) => void;\n /** The pause, in ms, before `onSearch`. Default 250. */\n debounce?: number;\n /** Called on Enter (no suggestion active) with the query: go to the results page. */\n onSubmit?: (query: string) => void;\n /** The global key that focuses the field with ⌘ / Ctrl (`\"k\"`), or `false` for none. Default `false`. */\n shortcut?: string | false;\n /** Suggestions under the field (recent searches, completions). */\n suggestions?: readonly SearchSuggestion[];\n /** A title above the suggestions (\"Recent searches\"). */\n suggestionsLabel?: string;\n /** Called with a suggestion picked (Enter on it, or a click). */\n onSuggestionSelect?: (suggestion: SearchSuggestion) => void;\n /** The clear button's name (\"Clear search\"), passed translated. */\n clearLabel?: string;\n /** Wraps the field in a `role=\"search\"` landmark (default) — `false` inside a form that already is one. */\n landmark?: boolean;\n /** md (default) · sm. */\n size?: \"md\" | \"sm\";\n};\n\nconst isApple = () => typeof navigator !== \"undefined\" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);\n\n/** A search box: clear, shortcut, debounced search, submit on Enter, suggestions with the keyboard. */\nexport function SearchField({\n label,\n value: valueProp,\n defaultValue = \"\",\n onValueChange,\n onSearch,\n debounce = 250,\n onSubmit,\n shortcut = false,\n suggestions,\n suggestionsLabel,\n onSuggestionSelect,\n clearLabel = \"Clear search\",\n landmark = true,\n size,\n className,\n placeholder,\n ...props\n}: SearchFieldProps) {\n const s = searchField({ size });\n const [value, setValue] = useControllable(valueProp, defaultValue, onValueChange);\n const [open, setOpen] = React.useState(false);\n const [apple, setApple] = React.useState(false);\n const input = React.useRef<HTMLInputElement>(null);\n const search = useDebouncedCallback((query: string) => onSearch?.(query), debounce);\n const list = suggestions ?? [];\n\n const pick = (i: number) => {\n const suggestion = list[i];\n if (!suggestion) return;\n setOpen(false);\n onSuggestionSelect?.(suggestion);\n };\n const listbox = useListbox({ count: list.length, onPick: pick, open: open && list.length > 0, setOpen });\n\n React.useEffect(() => {\n if (!shortcut) return;\n // eslint-disable-next-line react-hooks/set-state-in-effect -- the platform is only known in the browser (no hydration mismatch)\n setApple(isApple());\n const onKey = (event: KeyboardEvent) => {\n if (event.key.toLowerCase() === shortcut.toLowerCase() && (event.metaKey || event.ctrlKey) && !event.altKey) {\n event.preventDefault();\n input.current?.focus();\n input.current?.select();\n }\n };\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [shortcut]);\n\n const change = (query: string) => {\n setValue(query);\n search(query);\n listbox.setActive(-1);\n setOpen(true);\n };\n\n const field = (\n <div className={s.root({ className })} onBlur={(event) => !event.currentTarget.contains(event.relatedTarget as Node | null) && setOpen(false)}>\n <div className={s.control()}>\n <SearchGlyph className={s.icon()} />\n <input\n ref={input}\n type=\"search\"\n aria-label={label}\n enterKeyHint=\"search\"\n autoComplete=\"off\"\n {...(list.length > 0 ? listbox.inputProps : {})}\n value={value}\n placeholder={placeholder}\n className={s.input()}\n onChange={(event) => change(event.target.value)}\n onFocus={() => setOpen(true)}\n onKeyDown={(event) => {\n if (list.length > 0 && listbox.onKeyDown(event)) return;\n if (event.key === \"Enter\") {\n event.preventDefault();\n search.cancel();\n onSearch?.(value);\n onSubmit?.(value);\n setOpen(false);\n } else if (event.key === \"Escape\" && value) {\n event.preventDefault();\n change(\"\");\n }\n }}\n {...props}\n />\n {value ? (\n <button\n type=\"button\"\n aria-label={clearLabel}\n className={s.clear()}\n onClick={() => {\n change(\"\");\n input.current?.focus();\n }}\n >\n <CloseGlyph className=\"h-4 w-4\" />\n </button>\n ) : shortcut ? (\n <span className={s.shortcut()} aria-hidden>\n <Kbd size=\"sm\">{apple ? \"⌘\" : \"Ctrl\"}</Kbd>\n <Kbd size=\"sm\">{shortcut.toUpperCase()}</Kbd>\n </span>\n ) : null}\n </div>\n {list.length > 0 && (\n <div hidden={!open} className={s.list()}>\n {suggestionsLabel ? (\n <div aria-hidden className={s.group()}>\n {suggestionsLabel}\n </div>\n ) : null}\n <div id={listbox.listId} role=\"listbox\" aria-label={suggestionsLabel ?? label}>\n {list.map((suggestion, i) => (\n <div key={suggestion.value} {...listbox.optionProps(i, false)} className={s.option()}>\n <SearchGlyph className=\"h-3.5 w-3.5 shrink-0 text-fg-muted\" />\n <span className=\"min-w-0 flex-1 truncate\">{suggestion.label}</span>\n {suggestion.meta ? <span className=\"shrink-0 text-xs text-fg-secondary\">{suggestion.meta}</span> : null}\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n );\n return landmark ? <div role=\"search\">{field}</div> : field;\n}\n","// Server-safe. A key, or a shortcut of several (`<Kbd>⌘</Kbd><Kbd>K</Kbd>`).\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nexport const kbdVariants = tv({\n base:\n \"inline-flex shrink-0 items-center justify-center rounded-md border border-border-default border-b-2 bg-surface-2 \" +\n \"font-mono font-semibold text-fg-secondary\",\n variants: { size: { sm: \"h-5 min-w-5 px-1 text-[10px]\", md: \"h-6 min-w-6 px-1.5 text-xs\" } },\n defaultVariants: { size: \"md\" },\n});\n\nexport type KbdProps = React.ComponentProps<\"kbd\"> & {\n /**\n * sm · md.\n * @default \"md\"\n */\n size?: VariantProps<typeof kbdVariants>[\"size\"];\n};\n\nexport function Kbd({ size, className, ...props }: KbdProps) {\n return <kbd className={kbdVariants({ size, className })} {...props} />;\n}\n"],"mappings":"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","names":["tv","jsx","tv"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/combobox/combobox.tsx"],"sourcesContent":["// Client. The ARIA 1.2 combobox (`../listbox`): a text input that filters a listbox, the focus never leaves the input\n// (`aria-activedescendant`), the number of results announced in a polite live region. Static options filtered here,\n// or `onSearch` (debounced, the previous request aborted) for a server's; one value or several (chips, Backspace\n// removes the last), and new values created from the query when `creatable`.\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { Chip } from \"../chip\";\nimport { CheckGlyph, ChevronGlyph, CloseGlyph, PlusGlyph } from \"../glyphs\";\nimport { useListbox } from \"../listbox\";\nimport { useControllable, useDebouncedCallback } from \"../media\";\n\nexport const combobox = tv({\n slots: {\n root: \"relative flex w-full flex-col gap-1.5\",\n label: \"text-xs font-semibold text-fg-secondary\",\n control:\n \"flex min-h-11 w-full flex-wrap items-center gap-1.5 rounded-lg border border-border-default bg-surface-2 py-1.5 pl-2 pr-1 text-sm text-fg \" +\n \"transition-[border-color,box-shadow] focus-within:border-accent focus-within:ring-2 focus-within:ring-ring \" +\n \"data-[invalid]:border-danger data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50\",\n input: \"h-8 min-w-24 flex-1 bg-transparent px-1 text-sm text-fg outline-hidden placeholder:text-fg-muted disabled:cursor-not-allowed\",\n icon:\n \"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-fg-secondary hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n list: \"absolute inset-x-0 top-full z-50 mt-1 max-h-64 overflow-y-auto rounded-lg border border-border-default bg-surface-2 p-1 shadow-lg\",\n option:\n \"flex cursor-pointer select-none items-center gap-2.5 rounded-md px-2.5 py-2 text-sm text-fg \" +\n \"data-[active]:bg-surface-3 aria-disabled:cursor-not-allowed aria-disabled:opacity-40\",\n check: \"h-4 w-4 shrink-0 text-fg-accent\",\n description: \"block text-xs text-fg-secondary\",\n status: \"px-2.5 py-3 text-sm text-fg-secondary\",\n hint: \"text-xs text-fg-secondary\",\n error: \"flex items-center gap-1.5 text-xs font-medium text-fg-danger before:h-1.5 before:w-1.5 before:shrink-0 before:rounded-full before:bg-danger\",\n },\n});\n\nexport type ComboboxOption = {\n /** What the app stores. */\n value: string;\n /** What the person reads, passed translated. */\n label: string;\n /** A second line under the label. */\n description?: string;\n /** Shown but not choosable. */\n disabled?: boolean;\n};\n\nexport type ComboboxLabels = {\n /** No option matches (\"No results\"). */\n empty: string;\n /** While `onSearch` runs (\"Searching…\"). */\n loading: string;\n /** `onSearch` failed (\"Search unavailable\"). */\n error: string;\n /** The option that creates a value: `(query) => \"Create “\" + query + \"”\"`. */\n create: (query: string) => string;\n /** A chip's remove button: `(label) => \"Remove \" + label`. */\n remove: (label: string) => string;\n /** The button that empties a single choice (\"Clear\"). */\n clear: string;\n /** The button that opens the list (\"Show options\"). */\n toggle: string;\n /** Announced when the results change: `(n) => n + \" results\"`. */\n results: (count: number) => string;\n};\n\nexport const comboboxLabels: ComboboxLabels = {\n empty: \"No results\",\n loading: \"Searching…\",\n error: \"Search unavailable\",\n create: (query) => `Create “${query}”`,\n remove: (label) => `Remove ${label}`,\n clear: \"Clear\",\n toggle: \"Show options\",\n results: (count) => (count === 1 ? \"1 result\" : `${count} results`),\n};\n\ntype BaseProps = {\n /** The visible label of the field, passed translated (also its accessible name). */\n label: string;\n /** Hides the label visually, keeping it as the accessible name (a search bar with its own context). */\n hideLabel?: boolean;\n /** The options, filtered as the person types (by `filter`). */\n options?: readonly ComboboxOption[];\n /** Fetches the options for a query (debounced by `debounce`); the `signal` aborts when a newer query starts. */\n onSearch?: (query: string, signal: AbortSignal) => Promise<readonly ComboboxOption[]>;\n /** The wait, in ms, between the last key and `onSearch`. Default 250. */\n debounce?: number;\n /** Whether an option matches a query. Default: its label contains the query, ignoring case and accents. */\n filter?: (option: ComboboxOption, query: string) => boolean;\n /** Offers to create the query as a new value when no option has that exact label. */\n creatable?: boolean;\n /** Turns a query into the created option (an id from the server…). Default `{ value: query, label: query }`. */\n onCreate?: (query: string) => ComboboxOption;\n /** Keys that create the query at once, as Enter does (`[\",\"]` for tags); a paste is split on them too. */\n separators?: readonly string[];\n /** The input's placeholder, passed translated. */\n placeholder?: string;\n /** A help text under the field. */\n hint?: string;\n /** An error under the field: marks it invalid and is read with it. */\n error?: string;\n /** Not available. */\n disabled?: boolean;\n /** The name of a hidden input holding the value(s), for a plain `<form>` post. */\n name?: string;\n /** The words, passed translated; English by default. */\n labels?: Partial<ComboboxLabels>;\n /** Classes merged on the root. */\n className?: string;\n /** The id of the text input (default: generated). */\n id?: string;\n};\n\ntype SingleProps = BaseProps & {\n /** One value (default) or several. */\n multiple?: false;\n /** The chosen option (controlled), `null` for none. */\n value?: ComboboxOption | null;\n /** The chosen option at first, uncontrolled. */\n defaultValue?: ComboboxOption | null;\n /** Called with the chosen option, or `null` when it is cleared. */\n onValueChange?: (value: ComboboxOption | null) => void;\n /** Most options that can be chosen (several values only). */\n maxSelected?: never;\n};\n\ntype MultipleProps = BaseProps & {\n /** Several values, shown as chips. */\n multiple: true;\n /** The chosen options (controlled). */\n value?: readonly ComboboxOption[];\n /** The chosen options at first, uncontrolled. */\n defaultValue?: readonly ComboboxOption[];\n /** Called with every chosen option. */\n onValueChange?: (value: ComboboxOption[]) => void;\n /** Most options that can be chosen; the list stops offering more past it. */\n maxSelected?: number;\n};\n\nexport type ComboboxProps = SingleProps | MultipleProps;\n\nconst fold = (text: string) => text.normalize(\"NFD\").replace(/\\p{Diacritic}/gu, \"\").toLowerCase();\nconst defaultFilter = (option: ComboboxOption, query: string) => fold(option.label).includes(fold(query.trim()));\n\ntype Item = { kind: \"option\"; option: ComboboxOption } | { kind: \"create\"; query: string };\n\n/** A text field that suggests, filters and completes: one value or several (chips), static or fetched, creatable. */\nexport function Combobox(props: ComboboxProps) {\n const {\n label,\n hideLabel,\n options,\n onSearch,\n debounce = 250,\n filter = defaultFilter,\n creatable,\n onCreate = (query: string) => ({ value: query, label: query }),\n separators,\n placeholder,\n hint,\n error,\n disabled,\n name,\n labels: given,\n className,\n id: givenId,\n } = props;\n const labels = { ...comboboxLabels, ...given };\n const s = combobox();\n const autoId = React.useId();\n const inputId = givenId ?? `${autoId}-input`;\n const hintId = `${autoId}-hint`;\n const errorId = `${autoId}-error`;\n const multiple = props.multiple === true;\n\n const [values, setValues] = useControllable<readonly ComboboxOption[]>(\n props.value === undefined ? undefined : multiple ? (props.value as readonly ComboboxOption[]) : props.value ? [props.value as ComboboxOption] : [],\n multiple ? ((props.defaultValue as readonly ComboboxOption[]) ?? []) : props.defaultValue ? [props.defaultValue as ComboboxOption] : [],\n (next) => (multiple ? (props.onValueChange as ((v: ComboboxOption[]) => void) | undefined)?.([...next]) : (props.onValueChange as ((v: ComboboxOption | null) => void) | undefined)?.(next[0] ?? null)),\n );\n const single = multiple ? null : (values[0] ?? null);\n const [query, setQuery] = React.useState(single?.label ?? \"\");\n const [editing, setEditing] = React.useState(false);\n const [open, setOpenState] = React.useState(false);\n const [remote, setRemote] = React.useState<{ query: string; options: readonly ComboboxOption[]; state: \"idle\" | \"loading\" | \"error\" }>({ query: \"\", options: [], state: \"idle\" });\n const request = React.useRef<AbortController | null>(null);\n const inputRef = React.useRef<HTMLInputElement>(null);\n\n // The single choice's label shows in the input while the person is not typing.\n const shownQuery = !multiple && !editing ? (single?.label ?? \"\") : query;\n const searchQuery = editing ? query : \"\";\n\n const search = useDebouncedCallback((text: string) => {\n if (!onSearch) return;\n request.current?.abort();\n const controller = new AbortController();\n request.current = controller;\n onSearch(text, controller.signal).then(\n (found) => !controller.signal.aborted && setRemote({ query: text, options: found, state: \"idle\" }),\n () => !controller.signal.aborted && setRemote({ query: text, options: [], state: \"error\" }),\n );\n }, debounce);\n React.useEffect(() => () => request.current?.abort(), []);\n\n const setOpen = (next: boolean) => {\n if (next && onSearch && remote.state === \"idle\" && remote.query !== searchQuery) {\n setRemote((r) => ({ ...r, state: \"loading\" }));\n search(searchQuery);\n }\n setOpenState(next);\n };\n\n const chosen = new Set(values.map((v) => v.value));\n const source = onSearch ? remote.options : (options ?? []).filter((o) => !searchQuery.trim() || filter(o, searchQuery));\n const full = multiple && props.maxSelected !== undefined && values.length >= props.maxSelected;\n const trimmed = searchQuery.trim();\n const exact = [...source, ...values].some((o) => fold(o.label) === fold(trimmed));\n const items: Item[] = [\n ...source.map((option) => ({ kind: \"option\" as const, option })),\n ...(creatable && trimmed && !exact && !full ? [{ kind: \"create\" as const, query: trimmed }] : []),\n ];\n const isDisabled = (i: number) => {\n const item = items[i];\n if (!item) return true;\n if (item.kind === \"create\") return false;\n return Boolean(item.option.disabled) || (full && !chosen.has(item.option.value));\n };\n\n const commit = (option: ComboboxOption) => {\n if (multiple) {\n setValues(chosen.has(option.value) ? values.filter((v) => v.value !== option.value) : [...values, option]);\n setQuery(\"\");\n listbox.setActive(-1);\n } else {\n setValues([option]);\n setQuery(option.label);\n setEditing(false);\n setOpenState(false);\n }\n };\n const create = (text: string) => {\n const clean = text.trim();\n if (!clean || full) return;\n const existing = [...source, ...values].find((o) => fold(o.label) === fold(clean));\n if (multiple && existing && chosen.has(existing.value)) {\n setQuery(\"\");\n return;\n }\n commit(existing ?? onCreate(clean));\n };\n const pick = (i: number) => {\n const item = items[i];\n if (!item) return;\n if (item.kind === \"create\") create(item.query);\n else commit(item.option);\n };\n\n const listbox = useListbox({ count: items.length, isDisabled, onPick: pick, open, setOpen });\n\n const onChange = (text: string) => {\n setQuery(text);\n setEditing(true);\n listbox.setActive(-1);\n if (onSearch) {\n setRemote((r) => ({ ...r, state: \"loading\" }));\n search(text);\n }\n setOpenState(true);\n if (!multiple && text === \"\" && single) setValues([]);\n };\n\n const onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (separators?.includes(event.key) && query.trim()) {\n event.preventDefault();\n create(query);\n return;\n }\n if (listbox.onKeyDown(event)) return;\n if (event.key === \"Enter\" && creatable && query.trim()) {\n event.preventDefault();\n create(query);\n } else if (event.key === \"Backspace\" && multiple && query === \"\" && values.length > 0) {\n setValues(values.slice(0, -1));\n } else if (event.key === \"Escape\" && !multiple && editing) {\n setEditing(false);\n setQuery(single?.label ?? \"\");\n }\n };\n\n const onPaste = (event: React.ClipboardEvent<HTMLInputElement>) => {\n if (!separators?.length || !multiple) return;\n const text = event.clipboardData.getData(\"text\");\n const parts = text.split(new RegExp(`[${separators.map((c) => c.replace(/[\\\\\\]^-]/g, \"\\\\$&\")).join(\"\")}\\\\n]`)).map((p) => p.trim()).filter(Boolean);\n if (parts.length < 2) return;\n event.preventDefault();\n const next = [...values];\n for (const part of parts) {\n if (props.maxSelected !== undefined && next.length >= props.maxSelected) break;\n if (next.some((v) => fold(v.label) === fold(part))) continue;\n next.push((options ?? []).find((o) => fold(o.label) === fold(part)) ?? onCreate(part));\n }\n setValues(next);\n };\n\n const loading = Boolean(onSearch) && remote.state === \"loading\";\n const failed = Boolean(onSearch) && remote.state === \"error\";\n const describedBy = [hint ? hintId : null, error ? errorId : null].filter(Boolean).join(\" \") || undefined;\n\n return (\n <div\n className={s.root({ className })}\n onBlur={(event) => {\n if (event.currentTarget.contains(event.relatedTarget as Node | null)) return;\n setOpenState(false);\n if (!multiple) {\n setEditing(false);\n setQuery(single?.label ?? \"\");\n }\n }}\n >\n <label htmlFor={inputId} className={hideLabel ? \"sr-only\" : s.label()}>\n {label}\n </label>\n <div className={s.control()} data-invalid={error ? \"\" : undefined} data-disabled={disabled ? \"\" : undefined} onMouseDown={(event) => {\n if (event.target === event.currentTarget) {\n event.preventDefault();\n inputRef.current?.focus();\n }\n }}>\n {multiple &&\n values.map((option) => (\n <Chip key={option.value} size=\"sm\" removable removeLabel={labels.remove(option.label)} disabled={disabled} onRemove={() => setValues(values.filter((v) => v.value !== option.value))}>\n {option.label}\n </Chip>\n ))}\n <input\n ref={inputRef}\n id={inputId}\n type=\"text\"\n autoComplete=\"off\"\n spellCheck={false}\n {...listbox.inputProps}\n aria-describedby={describedBy}\n aria-invalid={error ? true : undefined}\n aria-busy={loading || undefined}\n value={shownQuery}\n placeholder={multiple && values.length > 0 ? undefined : placeholder}\n disabled={disabled}\n className={s.input()}\n onChange={(event) => onChange(event.target.value)}\n onKeyDown={onKeyDown}\n onPaste={onPaste}\n onClick={() => !open && setOpen(true)}\n />\n {!multiple && single && !disabled ? (\n <button type=\"button\" aria-label={labels.clear} className={s.icon()} onClick={() => {\n setValues([]);\n setQuery(\"\");\n inputRef.current?.focus();\n }}>\n <CloseGlyph className=\"h-4 w-4\" />\n </button>\n ) : null}\n <button type=\"button\" tabIndex={-1} aria-label={labels.toggle} aria-expanded={open} aria-controls={listbox.listId} disabled={disabled} className={s.icon()} onClick={() => {\n setOpen(!open);\n inputRef.current?.focus();\n }}>\n <ChevronGlyph direction={open ? \"up\" : \"down\"} className=\"h-4 w-4\" />\n </button>\n </div>\n {name && values.map((v) => <input key={v.value} type=\"hidden\" name={name} value={v.value} />)}\n <div id={listbox.listId} role=\"listbox\" aria-label={label} aria-multiselectable={multiple || undefined} hidden={!open || (items.length === 0 && !loading && !failed && !labels.empty)} className={s.list()}>\n {open && loading && items.length === 0 ? <div className={s.status()}>{labels.loading}</div> : null}\n {open && failed ? <div className={s.status()}>{labels.error}</div> : null}\n {open && !loading && !failed && items.length === 0 && labels.empty ? <div className={s.status()}>{labels.empty}</div> : null}\n {open &&\n items.map((item, i) =>\n item.kind === \"create\" ? (\n <div key=\"__create\" {...listbox.optionProps(i, false)} className={s.option()}>\n <PlusGlyph className={s.check()} />\n {labels.create(item.query)}\n </div>\n ) : (\n <div key={item.option.value} {...listbox.optionProps(i, chosen.has(item.option.value))} className={s.option()}>\n <span className=\"flex h-4 w-4 shrink-0 items-center justify-center\">{chosen.has(item.option.value) ? <CheckGlyph className={s.check()} /> : null}</span>\n <span className=\"min-w-0 flex-1\">\n {item.option.label}\n {item.option.description ? <span className={s.description()}>{item.option.description}</span> : null}\n </span>\n </div>\n ),\n )}\n </div>\n <span className=\"sr-only\" aria-live=\"polite\" aria-atomic>\n {open && !loading ? labels.results(items.length) : \"\"}\n </span>\n {hint ? (\n <p id={hintId} className={s.hint()}>\n {hint}\n </p>\n ) : null}\n {error ? (\n <p id={errorId} className={s.error()}>\n {error}\n </p>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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","names":["query"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/button/button.tsx"],"sourcesContent":["// Server-safe: no hook, no context. `buttonVariants` styles a <Link> or a Server Component without the component.\nimport { Slot } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { LoaderGlyph } from \"../spinner/glyph\";\n\nexport const buttonVariants = tv({\n base:\n \"inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap font-semibold transition-all \" +\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:pointer-events-none disabled:opacity-40 data-loading:cursor-progress active:scale-[0.98] \" +\n // Busy is not unavailable: a loading button keeps its colour (it is disabled only so it is not pressed twice).\n \"[&[data-loading]]:opacity-100\",\n variants: {\n variant: {\n primary: \"bg-accent text-on-accent shadow-sm hover:bg-accent-hover\",\n // The brand's signature: accent → accent-2 (on-accent reads on both stops, tested in @krizaka/tokens).\n gradient: \"bg-linear-to-r from-accent to-accent-2 text-on-accent shadow-sm hover:brightness-110\",\n secondary: \"border border-border-default bg-surface-2 text-fg hover:border-border-strong hover:bg-surface-3\",\n outline: \"border border-border-default bg-transparent text-fg hover:border-accent hover:text-fg-accent\",\n ghost: \"text-fg-secondary hover:bg-surface-2 hover:text-fg\",\n // The border and the tint carry the danger; the label stays the primary text role, legible on the tint at rest\n // and hovered over every surface (`text-fg-danger` is for text set directly on a surface).\n danger: \"border border-danger/50 bg-danger/10 text-fg hover:border-danger hover:bg-danger/20\",\n },\n size: { sm: \"h-8 px-3 text-xs\", md: \"h-10 px-4 text-sm\", lg: \"h-12 px-6 text-sm\", icon: \"h-10 w-10\" },\n // `cut`: the Krizaka chamfer — 45° corners (CSS corner-shape, where supported; rounded elsewhere). The border, the\n // focus ring and the hover follow the cut, unlike a clip-path.\n shape: { rounded: \"rounded-lg\", pill: \"rounded-full\", cut: \"rounded-[10px] [corner-shape:bevel]\" },\n },\n defaultVariants: { variant: \"secondary\", size: \"md\", shape: \"rounded\" },\n});\n\nexport type ButtonVariants = VariantProps<typeof buttonVariants>;\n\nexport type ButtonProps = React.ComponentProps<\"button\"> &\n ButtonVariants & {\n /**\n * The look: `primary` (the accent), `gradient` (accent → accent-2), `secondary` (a raised surface), `outline`,\n * `ghost`, `danger`.\n * @default \"secondary\"\n */\n variant?: ButtonVariants[\"variant\"];\n /**\n * sm · md · lg, or `icon` (a square).\n * @default \"md\"\n */\n size?: ButtonVariants[\"size\"];\n /**\n * rounded · pill · cut (45° corners, the Krizaka chamfer).\n * @default \"rounded\"\n */\n shape?: ButtonVariants[\"shape\"];\n /** Renders the child (e.g. a <Link>) instead of the <button>, with props, classes and ref merged. */\n asChild?: boolean;\n /**\n * Disables and signals the wait (aria-busy, data-loading) and shows the Krizaka loader glyph before the label; the\n * button keeps its colour.\n */\n loading?: boolean;\n /**\n * Between 0 and 1, while `loading`: the glyph's trace and a light fill across the button grow to the value (an\n * upload, an export).\n */\n progress?: number | null;\n /**\n * How the action ended: `success` draws a check, `error` a cross (and the button shakes its head once); a ring of\n * the status colour opens around it. Clear it to return to rest.\n */\n status?: \"success\" | \"error\" | null;\n /** A message announced politely by assistive technology when it changes (“Saved”, “Upload failed”) — passed translated. */\n announce?: string;\n /**\n * Shows the glyph while `loading` / with a `status`. `false`: you draw your own.\n * @default true\n */\n indicator?: boolean;\n /** A sheen of light crosses the button on hover — for the one action a page is about. */\n shimmer?: boolean;\n /** The button leans a few pixels toward a fine pointer (driven by `MotionObserver`; never under reduced motion or on touch). */\n magnetic?: boolean;\n };\n\nconst STATUS_TEXT = { success: \"text-fg-success\", error: \"text-fg-danger\" } as const;\n\nexport function Button({\n asChild,\n loading,\n progress,\n status,\n announce,\n indicator = true,\n shimmer,\n magnetic,\n variant,\n size,\n shape,\n className,\n disabled,\n children,\n ...props\n}: ButtonProps) {\n const Comp = asChild ? Slot.Root : \"button\";\n const known = loading && typeof progress === \"number\" && Number.isFinite(progress);\n const ratio = known ? Math.min(1, Math.max(0, progress)) : 0;\n const glyph = indicator && !asChild && (loading || status) ? (status ?? \"loading\") : null;\n // On a filled variant the glyph stays in the label's colour; on the others it takes the status colour.\n const filled = variant === \"primary\" || variant === \"gradient\";\n const button = (\n <Comp\n type={asChild ? undefined : \"button\"}\n aria-busy={loading || undefined}\n data-loading={loading ? \"\" : undefined}\n data-variant={variant ?? \"secondary\"}\n data-kz-status={status ?? undefined}\n data-kz-magnetic={magnetic ? \"\" : undefined}\n disabled={asChild ? undefined : disabled || loading}\n aria-disabled={asChild && (disabled || loading) ? true : undefined}\n className={buttonVariants({\n variant,\n size,\n shape,\n className: [known && \"relative isolate overflow-hidden\", shimmer && \"kz-sheen\", className].filter(Boolean).join(\" \") || undefined,\n })}\n {...props}\n >\n {asChild ? (\n children\n ) : (\n <>\n {known && (\n <span aria-hidden className=\"absolute inset-y-0 left-0 -z-10 bg-current opacity-15 transition-[width] duration-500\" style={{ width: `${ratio * 100}%` }} />\n )}\n {glyph && (\n <span\n aria-hidden\n data-slot=\"indicator\"\n className=\"-my-1 grid grid-cols-[1fr] transition-[grid-template-columns,margin,opacity] duration-300 starting:-mr-2 starting:grid-cols-[0fr] starting:opacity-0\"\n >\n <span className=\"overflow-hidden\">\n <LoaderGlyph state={glyph} value={known ? ratio : null} className={[\"h-4 w-4\", glyph !== \"loading\" && !filled && STATUS_TEXT[glyph]].filter(Boolean).join(\" \")} />\n </span>\n </span>\n )}\n {children}\n </>\n )}\n </Comp>\n );\n if (announce === undefined || asChild) return button;\n return (\n <>\n {button}\n <span role=\"status\" className=\"sr-only\">\n {announce}\n </span>\n </>\n );\n}\n\nexport type IconButtonProps = Omit<ButtonProps, \"size\"> & {\n /** The accessible name (and tooltip) of the button — required, passed translated. */\n label: string;\n /**\n * sm · md · lg, or `icon` (a square).\n * @default \"icon\"\n */\n size?: ButtonProps[\"size\"];\n};\n\n/** An icon-only button: `label` is required, it is its accessible name. Loading or with a status, the glyph takes the icon's place. */\nexport function IconButton({ label, size = \"icon\", children, ...props }: IconButtonProps) {\n const swap = props.indicator !== false && !props.asChild && (props.loading || props.status);\n return (\n <Button aria-label={label} title={label} size={size} {...props}>\n {swap ? null : children}\n </Button>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/button/button.tsx","../src/spinner/glyph.tsx"],"sourcesContent":["// Server-safe: no hook, no context. `buttonVariants` styles a <Link> or a Server Component without the component.\nimport { Slot } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { LoaderGlyph } from \"../spinner/glyph\";\n\nexport const buttonVariants = tv({\n base:\n \"inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap font-semibold transition-all \" +\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:pointer-events-none disabled:opacity-40 data-loading:cursor-progress active:scale-[0.98] \" +\n // Busy is not unavailable: a loading button keeps its colour (it is disabled only so it is not pressed twice).\n \"[&[data-loading]]:opacity-100\",\n variants: {\n variant: {\n primary: \"bg-accent text-on-accent shadow-sm hover:bg-accent-hover\",\n // The brand's signature: accent → accent-2 (on-accent reads on both stops, tested in @krizaka/tokens).\n gradient: \"bg-linear-to-r from-accent to-accent-2 text-on-accent shadow-sm hover:brightness-110\",\n secondary: \"border border-border-default bg-surface-2 text-fg hover:border-border-strong hover:bg-surface-3\",\n outline: \"border border-border-default bg-transparent text-fg hover:border-accent hover:text-fg-accent\",\n ghost: \"text-fg-secondary hover:bg-surface-2 hover:text-fg\",\n // The border and the tint carry the danger; the label stays the primary text role, legible on the tint at rest\n // and hovered over every surface (`text-fg-danger` is for text set directly on a surface).\n danger: \"border border-danger/50 bg-danger/10 text-fg hover:border-danger hover:bg-danger/20\",\n },\n size: { sm: \"h-8 px-3 text-xs\", md: \"h-10 px-4 text-sm\", lg: \"h-12 px-6 text-sm\", icon: \"h-10 w-10\" },\n // `cut`: the Krizaka chamfer — 45° corners (CSS corner-shape, where supported; rounded elsewhere). The border, the\n // focus ring and the hover follow the cut, unlike a clip-path.\n shape: { rounded: \"rounded-lg\", pill: \"rounded-full\", cut: \"rounded-[10px] [corner-shape:bevel]\" },\n },\n defaultVariants: { variant: \"secondary\", size: \"md\", shape: \"rounded\" },\n});\n\nexport type ButtonVariants = VariantProps<typeof buttonVariants>;\n\nexport type ButtonProps = React.ComponentProps<\"button\"> &\n ButtonVariants & {\n /**\n * The look: `primary` (the accent), `gradient` (accent → accent-2), `secondary` (a raised surface), `outline`,\n * `ghost`, `danger`.\n * @default \"secondary\"\n */\n variant?: ButtonVariants[\"variant\"];\n /**\n * sm · md · lg, or `icon` (a square).\n * @default \"md\"\n */\n size?: ButtonVariants[\"size\"];\n /**\n * rounded · pill · cut (45° corners, the Krizaka chamfer).\n * @default \"rounded\"\n */\n shape?: ButtonVariants[\"shape\"];\n /** Renders the child (e.g. a <Link>) instead of the <button>, with props, classes and ref merged. */\n asChild?: boolean;\n /**\n * Disables and signals the wait (aria-busy, data-loading) and shows the Krizaka loader glyph before the label; the\n * button keeps its colour.\n */\n loading?: boolean;\n /**\n * Between 0 and 1, while `loading`: the glyph's trace and a light fill across the button grow to the value (an\n * upload, an export).\n */\n progress?: number | null;\n /**\n * How the action ended: `success` draws a check, `error` a cross (and the button shakes its head once); a ring of\n * the status colour opens around it. Clear it to return to rest.\n */\n status?: \"success\" | \"error\" | null;\n /** A message announced politely by assistive technology when it changes (“Saved”, “Upload failed”) — passed translated. */\n announce?: string;\n /**\n * Shows the glyph while `loading` / with a `status`. `false`: you draw your own.\n * @default true\n */\n indicator?: boolean;\n /** A sheen of light crosses the button on hover — for the one action a page is about. */\n shimmer?: boolean;\n /** The button leans a few pixels toward a fine pointer (driven by `MotionObserver`; never under reduced motion or on touch). */\n magnetic?: boolean;\n };\n\nconst STATUS_TEXT = { success: \"text-fg-success\", error: \"text-fg-danger\" } as const;\n\nexport function Button({\n asChild,\n loading,\n progress,\n status,\n announce,\n indicator = true,\n shimmer,\n magnetic,\n variant,\n size,\n shape,\n className,\n disabled,\n children,\n ...props\n}: ButtonProps) {\n const Comp = asChild ? Slot.Root : \"button\";\n const known = loading && typeof progress === \"number\" && Number.isFinite(progress);\n const ratio = known ? Math.min(1, Math.max(0, progress)) : 0;\n const glyph = indicator && !asChild && (loading || status) ? (status ?? \"loading\") : null;\n // On a filled variant the glyph stays in the label's colour; on the others it takes the status colour.\n const filled = variant === \"primary\" || variant === \"gradient\";\n const button = (\n <Comp\n type={asChild ? undefined : \"button\"}\n aria-busy={loading || undefined}\n data-loading={loading ? \"\" : undefined}\n data-variant={variant ?? \"secondary\"}\n data-kz-status={status ?? undefined}\n data-kz-magnetic={magnetic ? \"\" : undefined}\n disabled={asChild ? undefined : disabled || loading}\n aria-disabled={asChild && (disabled || loading) ? true : undefined}\n className={buttonVariants({\n variant,\n size,\n shape,\n className: [known && \"relative isolate overflow-hidden\", shimmer && \"kz-sheen\", className].filter(Boolean).join(\" \") || undefined,\n })}\n {...props}\n >\n {asChild ? (\n children\n ) : (\n <>\n {known && (\n <span aria-hidden className=\"absolute inset-y-0 left-0 -z-10 bg-current opacity-15 transition-[width] duration-500\" style={{ width: `${ratio * 100}%` }} />\n )}\n {glyph && (\n <span\n aria-hidden\n data-slot=\"indicator\"\n className=\"-my-1 grid grid-cols-[1fr] transition-[grid-template-columns,margin,opacity] duration-300 starting:-mr-2 starting:grid-cols-[0fr] starting:opacity-0\"\n >\n <span className=\"overflow-hidden\">\n <LoaderGlyph state={glyph} value={known ? ratio : null} className={[\"h-4 w-4\", glyph !== \"loading\" && !filled && STATUS_TEXT[glyph]].filter(Boolean).join(\" \")} />\n </span>\n </span>\n )}\n {children}\n </>\n )}\n </Comp>\n );\n if (announce === undefined || asChild) return button;\n return (\n <>\n {button}\n <span role=\"status\" className=\"sr-only\">\n {announce}\n </span>\n </>\n );\n}\n\nexport type IconButtonProps = Omit<ButtonProps, \"size\"> & {\n /** The accessible name (and tooltip) of the button — required, passed translated. */\n label: string;\n /**\n * sm · md · lg, or `icon` (a square).\n * @default \"icon\"\n */\n size?: ButtonProps[\"size\"];\n};\n\n/** An icon-only button: `label` is required, it is its accessible name. Loading or with a status, the glyph takes the icon's place. */\nexport function IconButton({ label, size = \"icon\", children, ...props }: IconButtonProps) {\n const swap = props.indicator !== false && !props.asChild && (props.loading || props.status);\n return (\n <Button aria-label={label} title={label} size={size} {...props}>\n {swap ? null : children}\n </Button>\n );\n}\n","// The Krizaka loader glyph, server-safe: a hexagon (the marks' shape) traced by a segment of light around a node that\n// breathes; determinate, it fills to its value; done, the trace closes and a check (or a cross) draws itself. Animated by\n// motion.css (`kz-loader-*`); still under prefers-reduced-motion — a segment at rest and the node lit, never blank.\nimport type * as React from \"react\";\n\n/** The hexagon, pointy top, radius 9 around (12, 12). */\nconst HEX = \"M12 3 19.79 7.5 19.79 16.5 12 21 4.21 16.5 4.21 7.5Z\";\n\n/** What the glyph shows: a wait (with an optional value), or how it ended. */\nexport type LoaderGlyphState = \"loading\" | \"success\" | \"error\";\n\nexport interface LoaderGlyphProps extends Omit<React.ComponentProps<\"svg\">, \"children\"> {\n /** loading · success · error. */\n state?: LoaderGlyphState;\n /** Between 0 and 1: a determinate wait, the trace grows to it. Absent: indeterminate. */\n value?: number | null;\n}\n\n/** The glyph alone (decorative): the Spinner, the Loader and the Button's `loading` draw it. */\nexport function LoaderGlyph({ state = \"loading\", value, ...props }: LoaderGlyphProps) {\n const known = typeof value === \"number\" && Number.isFinite(value);\n const fill = state !== \"loading\" ? 100 : known ? Math.round(Math.min(1, Math.max(0, value)) * 100) : null;\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden data-state={state} data-progress={fill === null ? undefined : fill} {...props}>\n <path d={HEX} stroke=\"currentColor\" strokeOpacity=\"0.22\" strokeWidth=\"2.2\" strokeLinejoin=\"round\" />\n <path\n d={HEX}\n pathLength={100}\n stroke=\"currentColor\"\n strokeWidth=\"2.2\"\n strokeLinejoin=\"round\"\n strokeLinecap=\"round\"\n className=\"kz-loader-trace\"\n strokeDasharray={fill === null ? undefined : `${fill} ${100 - fill}`}\n />\n {state === \"loading\" && <circle cx=\"12\" cy=\"12\" r=\"2.6\" fill=\"currentColor\" className=\"kz-loader-node\" />}\n {state === \"success\" && <path d=\"M8.3 12.3l2.6 2.6 4.9-5.3\" pathLength={20} stroke=\"currentColor\" strokeWidth=\"2.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" className=\"kz-loader-mark\" />}\n {state === \"error\" && <path d=\"M9.4 9.4l5.2 5.2M14.6 9.4l-5.2 5.2\" pathLength={20} stroke=\"currentColor\" strokeWidth=\"2.2\" strokeLinecap=\"round\" className=\"kz-loader-mark\" />}\n </svg>\n );\n}\n"],"mappings":"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","names":["jsx","jsxs"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/chip/chip.tsx"],"sourcesContent":["// Client (a context). A chip is a pressable pill: alone it is a Radix Toggle (aria-pressed), inside `Chip.Group` an\n// item of a Radix Toggle Group (roving focus with the arrows; `single` = radio semantics, `multiple` = pressed\n// buttons). A `removable` chip is a static label with its own remove button (two buttons cannot nest).\nimport { Toggle as TogglePrimitive, ToggleGroup as ToggleGroupPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nexport const chip = tv({\n slots: {\n group: \"flex flex-wrap items-center gap-2 data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch\",\n base:\n \"inline-flex shrink-0 select-none items-center justify-center gap-1.5 whitespace-nowrap rounded-full border font-semibold transition-colors \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40 \" +\n \"border-border-default bg-surface-1 text-fg-secondary hover:border-border-strong hover:text-fg \" +\n \"data-[state=on]:border-accent data-[state=on]:bg-accent-soft data-[state=on]:text-fg\",\n remove:\n \"-mr-1.5 inline-flex h-6 w-6 items-center justify-center rounded-full text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n },\n variants: {\n size: { sm: { base: \"h-7 px-3 text-xs\" }, md: { base: \"h-9 px-3.5 text-xs\" } },\n },\n defaultVariants: { size: \"md\" },\n});\n\nexport type ChipVariants = VariantProps<typeof chip>;\n\ntype GroupContext = { size: ChipVariants[\"size\"] };\nconst ChipGroupContext = React.createContext<GroupContext | null>(null);\n\nfunction RemoveIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" className=\"h-3.5 w-3.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n );\n}\n\ntype ToggleChipProps = Omit<React.ComponentProps<\"button\">, \"value\" | \"defaultValue\"> &\n ChipVariants & {\n /**\n * sm · md (inside `Chip.Group`, the group's size by default).\n * @default \"md\"\n */\n size?: ChipVariants[\"size\"];\n /** Inside `Chip.Group`: the value this chip stands for (required there, ignored alone). */\n value?: string;\n /** Alone: pressed or not (`defaultSelected` uncontrolled), `onSelectedChange` on every press. */\n selected?: boolean;\n /** Alone: pressed at first, uncontrolled. */\n defaultSelected?: boolean;\n /** Alone: called with the new state on every press. */\n onSelectedChange?: (selected: boolean) => void;\n /** A pressable chip (the default); `true` makes it a static chip with a remove button. */\n removable?: false;\n };\n\ntype RemovableChipProps = Omit<React.ComponentProps<\"span\">, \"onSelect\"> &\n ChipVariants & {\n /** A static chip (a chosen tag, an applied filter) with a remove button. */\n removable: true;\n /** The accessible name of the remove button — passed translated, e.g. \"Remove #night\". */\n removeLabel: string;\n /** Runs when the remove button is pressed. */\n onRemove: () => void;\n /** Disables the remove button. */\n disabled?: boolean;\n };\n\nexport type ChipProps = ToggleChipProps | RemovableChipProps;\n\n/** A selectable pill (`selected` / `onSelectedChange`), an item of `Chip.Group` (`value`), or `removable`. */\nfunction ChipBase(props: ChipProps) {\n const group = React.useContext(ChipGroupContext);\n if (props.removable) {\n const { removable: _removable, removeLabel, onRemove, disabled, size, className, children, ...rest } = props;\n const s = chip({ size: size ?? group?.size });\n return (\n <span data-removable=\"\" className={s.base({ className })} {...rest}>\n {children}\n <button type=\"button\" aria-label={removeLabel} title={removeLabel} disabled={disabled} onClick={onRemove} className={s.remove()}>\n <RemoveIcon />\n </button>\n </span>\n );\n }\n const { removable: _removable, value, selected, defaultSelected, onSelectedChange, size, className, ...rest } = props;\n const s = chip({ size: size ?? group?.size });\n if (group) {\n if (value === undefined) throw new Error(\"Chip: a chip inside Chip.Group needs a `value`.\");\n return <ToggleGroupPrimitive.Item value={value} className={s.base({ className })} {...rest} />;\n }\n return (\n <TogglePrimitive.Root\n pressed={selected}\n defaultPressed={defaultSelected}\n onPressedChange={onSelectedChange}\n className={s.base({ className })}\n {...rest}\n />\n );\n}\n\ntype GroupBase = Omit<React.ComponentProps<\"div\">, \"defaultValue\" | \"dir\"> &\n ChipVariants & {\n /**\n * sm · md: the size of every chip of the group.\n * @default \"md\"\n */\n size?: ChipVariants[\"size\"];\n /** The group's accessible name — passed translated (\"Filter by format\"). */\n label?: string;\n /** The arrow keys that move: left / right (horizontal, a row) or up / down (vertical, a column). */\n orientation?: \"horizontal\" | \"vertical\";\n /** Arrow keys wrap from the last chip to the first (default `true`). */\n loop?: boolean;\n /** Disables every chip of the group. */\n disabled?: boolean;\n };\n\nexport type ChipGroupProps =\n | (GroupBase & {\n /** One choice: the chips behave as radios. */\n type: \"single\";\n /** The chosen value (controlled), `\"\"` when none. */\n value?: string;\n /** The value chosen at first, uncontrolled. */\n defaultValue?: string;\n /** Called with the new value on every choice. */\n onValueChange?: (value: string) => void;\n /** Keeps one chip selected: pressing the selected chip does not clear it (default `false`). */\n required?: boolean;\n })\n | (GroupBase & {\n /** Any number of choices: the chips are pressed buttons. */\n type: \"multiple\";\n /** The chosen values (controlled). */\n value?: string[];\n /** The values chosen at first, uncontrolled. */\n defaultValue?: string[];\n /** Called with the new values on every press. */\n onValueChange?: (value: string[]) => void;\n required?: never;\n });\n\n/** Chips that choose together: `type` single · multiple, `value`/`defaultValue` + `onValueChange`. */\nexport function ChipGroup({ label, size, className, children, ...props }: ChipGroupProps) {\n const s = chip({ size });\n const context = React.useMemo(() => ({ size }), [size]);\n const common = { \"aria-label\": label, className: s.group({ className }) };\n let group: React.ReactNode;\n if (props.type === \"single\") {\n const { type: _type, value, defaultValue, onValueChange, required, ...rest } = props;\n group = <SingleGroup {...rest} {...common} value={value} defaultValue={defaultValue} onValueChange={onValueChange} required={required}>{children}</SingleGroup>;\n } else {\n const { type: _type, required: _required, ...rest } = props;\n group = (\n <ToggleGroupPrimitive.Root type=\"multiple\" {...rest} {...common}>\n {children}\n </ToggleGroupPrimitive.Root>\n );\n }\n return <ChipGroupContext.Provider value={context}>{group}</ChipGroupContext.Provider>;\n}\n\n/** Radix's single group, plus `required`: the value is held here so that an empty change can be refused. */\nfunction SingleGroup({\n value,\n defaultValue,\n onValueChange,\n required,\n ...props\n}: Omit<GroupBase, \"size\" | \"label\"> & {\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n required?: boolean;\n}) {\n const [inner, setInner] = React.useState(defaultValue ?? \"\");\n const current = value ?? inner;\n return (\n <ToggleGroupPrimitive.Root\n type=\"single\"\n value={current}\n onValueChange={(next) => {\n if (required && !next) return;\n if (value === undefined) setInner(next);\n onValueChange?.(next);\n }}\n {...props}\n />\n );\n}\n\n/** `Chip` (selectable, removable) and `Chip.Group` (single · multiple). */\nexport const Chip = Object.assign(ChipBase, { Group: ChipGroup });\n"],"mappings":";;;AAGA,SAAS,UAAU,iBAAiB,eAAe,4BAA4B;AAC/E,YAAY,WAAW;AACvB,SAAS,UAA6B;AA4BhC,cA6CA,YA7CA;AA1BC,IAAM,OAAO,GAAG;AAAA,EACrB,OAAO;AAAA,IACL,OAAO;AAAA,IACP,MACE;AAAA,IAIF,QACE;AAAA,EAEJ;AAAA,EACA,UAAU;AAAA,IACR,MAAM,EAAE,IAAI,EAAE,MAAM,mBAAmB,GAAG,IAAI,EAAE,MAAM,qBAAqB,EAAE;AAAA,EAC/E;AAAA,EACA,iBAAiB,EAAE,MAAM,KAAK;AAChC,CAAC;AAKD,IAAM,mBAAyB,oBAAmC,IAAI;AAEtE,SAAS,aAAa;AACpB,SACE,oBAAC,SAAI,SAAQ,aAAY,WAAU,eAAc,MAAK,QAAO,QAAO,gBAAe,aAAY,OAAM,eAAc,SAAQ,eAAW,MACpI,8BAAC,UAAK,GAAE,wBAAuB,GACjC;AAEJ;AAoCA,SAAS,SAAS,OAAkB;AAClC,QAAM,QAAc,iBAAW,gBAAgB;AAC/C,MAAI,MAAM,WAAW;AACnB,UAAM,EAAE,WAAWA,aAAY,aAAa,UAAU,UAAU,MAAAC,OAAM,WAAAC,YAAW,UAAU,GAAGC,MAAK,IAAI;AACvG,UAAMC,KAAI,KAAK,EAAE,MAAMH,SAAQ,OAAO,KAAK,CAAC;AAC5C,WACE,qBAAC,UAAK,kBAAe,IAAG,WAAWG,GAAE,KAAK,EAAE,WAAAF,WAAU,CAAC,GAAI,GAAGC,OAC3D;AAAA;AAAA,MACD,oBAAC,YAAO,MAAK,UAAS,cAAY,aAAa,OAAO,aAAa,UAAoB,SAAS,UAAU,WAAWC,GAAE,OAAO,GAC5H,8BAAC,cAAW,GACd;AAAA,OACF;AAAA,EAEJ;AACA,QAAM,EAAE,WAAW,YAAY,OAAO,UAAU,iBAAiB,kBAAkB,MAAM,WAAW,GAAG,KAAK,IAAI;AAChH,QAAM,IAAI,KAAK,EAAE,MAAM,QAAQ,OAAO,KAAK,CAAC;AAC5C,MAAI,OAAO;AACT,QAAI,UAAU,OAAW,OAAM,IAAI,MAAM,iDAAiD;AAC1F,WAAO,oBAAC,qBAAqB,MAArB,EAA0B,OAAc,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC,GAAI,GAAG,MAAM;AAAA,EAC9F;AACA,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,SAAS;AAAA,MACT,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC;AAAA,MAC9B,GAAG;AAAA;AAAA,EACN;AAEJ;AA6CO,SAAS,UAAU,EAAE,OAAO,MAAM,WAAW,UAAU,GAAG,MAAM,GAAmB;AACxF,QAAM,IAAI,KAAK,EAAE,KAAK,CAAC;AACvB,QAAM,UAAgB,cAAQ,OAAO,EAAE,KAAK,IAAI,CAAC,IAAI,CAAC;AACtD,QAAM,SAAS,EAAE,cAAc,OAAO,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE;AACxE,MAAI;AACJ,MAAI,MAAM,SAAS,UAAU;AAC3B,UAAM,EAAE,MAAM,OAAO,OAAO,cAAc,eAAe,UAAU,GAAG,KAAK,IAAI;AAC/E,YAAQ,oBAAC,eAAa,GAAG,MAAO,GAAG,QAAQ,OAAc,cAA4B,eAA8B,UAAqB,UAAS;AAAA,EACnJ,OAAO;AACL,UAAM,EAAE,MAAM,OAAO,UAAU,WAAW,GAAG,KAAK,IAAI;AACtD,YACE,oBAAC,qBAAqB,MAArB,EAA0B,MAAK,YAAY,GAAG,MAAO,GAAG,QACtD,UACH;AAAA,EAEJ;AACA,SAAO,oBAAC,iBAAiB,UAAjB,EAA0B,OAAO,SAAU,iBAAM;AAC3D;AAGA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAKG;AACD,QAAM,CAAC,OAAO,QAAQ,IAAU,eAAS,gBAAgB,EAAE;AAC3D,QAAM,UAAU,SAAS;AACzB,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,MAAK;AAAA,MACL,OAAO;AAAA,MACP,eAAe,CAAC,SAAS;AACvB,YAAI,YAAY,CAAC,KAAM;AACvB,YAAI,UAAU,OAAW,UAAS,IAAI;AACtC,wBAAgB,IAAI;AAAA,MACtB;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAGO,IAAM,OAAO,OAAO,OAAO,UAAU,EAAE,OAAO,UAAU,CAAC;","names":["_removable","size","className","rest","s"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/button/button.tsx"],"sourcesContent":["// Server-safe: no hook, no context. `buttonVariants` styles a <Link> or a Server Component without the component.\nimport { Slot } from \"radix-ui\";\nimport type * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { LoaderGlyph } from \"../spinner/glyph\";\n\nexport const buttonVariants = tv({\n base:\n \"inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap font-semibold transition-all \" +\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:pointer-events-none disabled:opacity-40 data-loading:cursor-progress active:scale-[0.98] \" +\n // Busy is not unavailable: a loading button keeps its colour (it is disabled only so it is not pressed twice).\n \"[&[data-loading]]:opacity-100\",\n variants: {\n variant: {\n primary: \"bg-accent text-on-accent shadow-sm hover:bg-accent-hover\",\n // The brand's signature: accent → accent-2 (on-accent reads on both stops, tested in @krizaka/tokens).\n gradient: \"bg-linear-to-r from-accent to-accent-2 text-on-accent shadow-sm hover:brightness-110\",\n secondary: \"border border-border-default bg-surface-2 text-fg hover:border-border-strong hover:bg-surface-3\",\n outline: \"border border-border-default bg-transparent text-fg hover:border-accent hover:text-fg-accent\",\n ghost: \"text-fg-secondary hover:bg-surface-2 hover:text-fg\",\n // The border and the tint carry the danger; the label stays the primary text role, legible on the tint at rest\n // and hovered over every surface (`text-fg-danger` is for text set directly on a surface).\n danger: \"border border-danger/50 bg-danger/10 text-fg hover:border-danger hover:bg-danger/20\",\n },\n size: { sm: \"h-8 px-3 text-xs\", md: \"h-10 px-4 text-sm\", lg: \"h-12 px-6 text-sm\", icon: \"h-10 w-10\" },\n // `cut`: the Krizaka chamfer — 45° corners (CSS corner-shape, where supported; rounded elsewhere). The border, the\n // focus ring and the hover follow the cut, unlike a clip-path.\n shape: { rounded: \"rounded-lg\", pill: \"rounded-full\", cut: \"rounded-[10px] [corner-shape:bevel]\" },\n },\n defaultVariants: { variant: \"secondary\", size: \"md\", shape: \"rounded\" },\n});\n\nexport type ButtonVariants = VariantProps<typeof buttonVariants>;\n\nexport type ButtonProps = React.ComponentProps<\"button\"> &\n ButtonVariants & {\n /**\n * The look: `primary` (the accent), `gradient` (accent → accent-2), `secondary` (a raised surface), `outline`,\n * `ghost`, `danger`.\n * @default \"secondary\"\n */\n variant?: ButtonVariants[\"variant\"];\n /**\n * sm · md · lg, or `icon` (a square).\n * @default \"md\"\n */\n size?: ButtonVariants[\"size\"];\n /**\n * rounded · pill · cut (45° corners, the Krizaka chamfer).\n * @default \"rounded\"\n */\n shape?: ButtonVariants[\"shape\"];\n /** Renders the child (e.g. a <Link>) instead of the <button>, with props, classes and ref merged. */\n asChild?: boolean;\n /**\n * Disables and signals the wait (aria-busy, data-loading) and shows the Krizaka loader glyph before the label; the\n * button keeps its colour.\n */\n loading?: boolean;\n /**\n * Between 0 and 1, while `loading`: the glyph's trace and a light fill across the button grow to the value (an\n * upload, an export).\n */\n progress?: number | null;\n /**\n * How the action ended: `success` draws a check, `error` a cross (and the button shakes its head once); a ring of\n * the status colour opens around it. Clear it to return to rest.\n */\n status?: \"success\" | \"error\" | null;\n /** A message announced politely by assistive technology when it changes (“Saved”, “Upload failed”) — passed translated. */\n announce?: string;\n /**\n * Shows the glyph while `loading` / with a `status`. `false`: you draw your own.\n * @default true\n */\n indicator?: boolean;\n /** A sheen of light crosses the button on hover — for the one action a page is about. */\n shimmer?: boolean;\n /** The button leans a few pixels toward a fine pointer (driven by `MotionObserver`; never under reduced motion or on touch). */\n magnetic?: boolean;\n };\n\nconst STATUS_TEXT = { success: \"text-fg-success\", error: \"text-fg-danger\" } as const;\n\nexport function Button({\n asChild,\n loading,\n progress,\n status,\n announce,\n indicator = true,\n shimmer,\n magnetic,\n variant,\n size,\n shape,\n className,\n disabled,\n children,\n ...props\n}: ButtonProps) {\n const Comp = asChild ? Slot.Root : \"button\";\n const known = loading && typeof progress === \"number\" && Number.isFinite(progress);\n const ratio = known ? Math.min(1, Math.max(0, progress)) : 0;\n const glyph = indicator && !asChild && (loading || status) ? (status ?? \"loading\") : null;\n // On a filled variant the glyph stays in the label's colour; on the others it takes the status colour.\n const filled = variant === \"primary\" || variant === \"gradient\";\n const button = (\n <Comp\n type={asChild ? undefined : \"button\"}\n aria-busy={loading || undefined}\n data-loading={loading ? \"\" : undefined}\n data-variant={variant ?? \"secondary\"}\n data-kz-status={status ?? undefined}\n data-kz-magnetic={magnetic ? \"\" : undefined}\n disabled={asChild ? undefined : disabled || loading}\n aria-disabled={asChild && (disabled || loading) ? true : undefined}\n className={buttonVariants({\n variant,\n size,\n shape,\n className: [known && \"relative isolate overflow-hidden\", shimmer && \"kz-sheen\", className].filter(Boolean).join(\" \") || undefined,\n })}\n {...props}\n >\n {asChild ? (\n children\n ) : (\n <>\n {known && (\n <span aria-hidden className=\"absolute inset-y-0 left-0 -z-10 bg-current opacity-15 transition-[width] duration-500\" style={{ width: `${ratio * 100}%` }} />\n )}\n {glyph && (\n <span\n aria-hidden\n data-slot=\"indicator\"\n className=\"-my-1 grid grid-cols-[1fr] transition-[grid-template-columns,margin,opacity] duration-300 starting:-mr-2 starting:grid-cols-[0fr] starting:opacity-0\"\n >\n <span className=\"overflow-hidden\">\n <LoaderGlyph state={glyph} value={known ? ratio : null} className={[\"h-4 w-4\", glyph !== \"loading\" && !filled && STATUS_TEXT[glyph]].filter(Boolean).join(\" \")} />\n </span>\n </span>\n )}\n {children}\n </>\n )}\n </Comp>\n );\n if (announce === undefined || asChild) return button;\n return (\n <>\n {button}\n <span role=\"status\" className=\"sr-only\">\n {announce}\n </span>\n </>\n );\n}\n\nexport type IconButtonProps = Omit<ButtonProps, \"size\"> & {\n /** The accessible name (and tooltip) of the button — required, passed translated. */\n label: string;\n /**\n * sm · md · lg, or `icon` (a square).\n * @default \"icon\"\n */\n size?: ButtonProps[\"size\"];\n};\n\n/** An icon-only button: `label` is required, it is its accessible name. Loading or with a status, the glyph takes the icon's place. */\nexport function IconButton({ label, size = \"icon\", children, ...props }: IconButtonProps) {\n const swap = props.indicator !== false && !props.asChild && (props.loading || props.status);\n return (\n <Button aria-label={label} title={label} size={size} {...props}>\n {swap ? null : children}\n </Button>\n );\n}\n"],"mappings":"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","names":[]}