@craftzbay/ui 0.9.0 → 0.11.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 (208) hide show
  1. package/README.md +82 -10
  2. package/dist-lib/__tests__/helpers/color.d.ts +9 -0
  3. package/dist-lib/__tests__/helpers/theme.d.ts +18 -0
  4. package/dist-lib/components/ui/Accordion.js +71 -0
  5. package/dist-lib/components/ui/Accordion.js.map +1 -0
  6. package/dist-lib/components/ui/Alert.d.ts +17 -1
  7. package/dist-lib/components/ui/Alert.js +79 -0
  8. package/dist-lib/components/ui/Alert.js.map +1 -0
  9. package/dist-lib/components/ui/Avatar.d.ts +3 -3
  10. package/dist-lib/components/ui/Avatar.js +94 -0
  11. package/dist-lib/components/ui/Avatar.js.map +1 -0
  12. package/dist-lib/components/ui/Badge.d.ts +4 -2
  13. package/dist-lib/components/ui/Badge.js +76 -0
  14. package/dist-lib/components/ui/Badge.js.map +1 -0
  15. package/dist-lib/components/ui/Breadcrumbs.d.ts +2 -0
  16. package/dist-lib/components/ui/Breadcrumbs.js +52 -0
  17. package/dist-lib/components/ui/Breadcrumbs.js.map +1 -0
  18. package/dist-lib/components/ui/Button.d.ts +6 -4
  19. package/dist-lib/components/ui/Button.js +131 -0
  20. package/dist-lib/components/ui/Button.js.map +1 -0
  21. package/dist-lib/components/ui/Calendar.d.ts +7 -1
  22. package/dist-lib/components/ui/Calendar.js +139 -0
  23. package/dist-lib/components/ui/Calendar.js.map +1 -0
  24. package/dist-lib/components/ui/Card.d.ts +3 -1
  25. package/dist-lib/components/ui/Card.js +97 -0
  26. package/dist-lib/components/ui/Card.js.map +1 -0
  27. package/dist-lib/components/ui/Carousel.d.ts +11 -7
  28. package/dist-lib/components/ui/Carousel.js +174 -0
  29. package/dist-lib/components/ui/Carousel.js.map +1 -0
  30. package/dist-lib/components/ui/Chart.d.ts +68 -7
  31. package/dist-lib/components/ui/Chart.js +487 -0
  32. package/dist-lib/components/ui/Chart.js.map +1 -0
  33. package/dist-lib/components/ui/Checkbox.js +70 -0
  34. package/dist-lib/components/ui/Checkbox.js.map +1 -0
  35. package/dist-lib/components/ui/Combobox.d.ts +9 -0
  36. package/dist-lib/components/ui/Combobox.js +195 -0
  37. package/dist-lib/components/ui/Combobox.js.map +1 -0
  38. package/dist-lib/components/ui/CommandPalette.d.ts +9 -6
  39. package/dist-lib/components/ui/CommandPalette.js +179 -0
  40. package/dist-lib/components/ui/CommandPalette.js.map +1 -0
  41. package/dist-lib/components/ui/ContextMenu.d.ts +7 -2
  42. package/dist-lib/components/ui/ContextMenu.js +165 -0
  43. package/dist-lib/components/ui/ContextMenu.js.map +1 -0
  44. package/dist-lib/components/ui/DataGrid.d.ts +17 -4
  45. package/dist-lib/components/ui/DataGrid.js +99 -0
  46. package/dist-lib/components/ui/DataGrid.js.map +1 -0
  47. package/dist-lib/components/ui/DatePicker.d.ts +16 -13
  48. package/dist-lib/components/ui/DatePicker.js +193 -0
  49. package/dist-lib/components/ui/DatePicker.js.map +1 -0
  50. package/dist-lib/components/ui/DesignSystemProvider.d.ts +102 -41
  51. package/dist-lib/components/ui/DesignSystemProvider.js +135 -0
  52. package/dist-lib/components/ui/DesignSystemProvider.js.map +1 -0
  53. package/dist-lib/components/ui/Dialog.d.ts +19 -4
  54. package/dist-lib/components/ui/Dialog.js +165 -0
  55. package/dist-lib/components/ui/Dialog.js.map +1 -0
  56. package/dist-lib/components/ui/Drawer.d.ts +23 -2
  57. package/dist-lib/components/ui/Drawer.js +132 -0
  58. package/dist-lib/components/ui/Drawer.js.map +1 -0
  59. package/dist-lib/components/ui/DropdownMenu.js +169 -0
  60. package/dist-lib/components/ui/DropdownMenu.js.map +1 -0
  61. package/dist-lib/components/ui/EmptyState.d.ts +2 -0
  62. package/dist-lib/components/ui/EmptyState.js +46 -0
  63. package/dist-lib/components/ui/EmptyState.js.map +1 -0
  64. package/dist-lib/components/ui/ErrorState.d.ts +13 -2
  65. package/dist-lib/components/ui/ErrorState.js +69 -0
  66. package/dist-lib/components/ui/ErrorState.js.map +1 -0
  67. package/dist-lib/components/ui/FileUpload.d.ts +3 -0
  68. package/dist-lib/components/ui/FileUpload.js +111 -0
  69. package/dist-lib/components/ui/FileUpload.js.map +1 -0
  70. package/dist-lib/components/ui/Form.d.ts +16 -2
  71. package/dist-lib/components/ui/Form.js +131 -0
  72. package/dist-lib/components/ui/Form.js.map +1 -0
  73. package/dist-lib/components/ui/Icon.d.ts +1 -1
  74. package/dist-lib/components/ui/Icon.js +32 -0
  75. package/dist-lib/components/ui/Icon.js.map +1 -0
  76. package/dist-lib/components/ui/IconButton.d.ts +3 -3
  77. package/dist-lib/components/ui/IconButton.js +64 -0
  78. package/dist-lib/components/ui/IconButton.js.map +1 -0
  79. package/dist-lib/components/ui/Input.d.ts +9 -4
  80. package/dist-lib/components/ui/Input.js +149 -0
  81. package/dist-lib/components/ui/Input.js.map +1 -0
  82. package/dist-lib/components/ui/Kbd.js +24 -0
  83. package/dist-lib/components/ui/Kbd.js.map +1 -0
  84. package/dist-lib/components/ui/MultiSelect.d.ts +3 -1
  85. package/dist-lib/components/ui/MultiSelect.js +210 -0
  86. package/dist-lib/components/ui/MultiSelect.js.map +1 -0
  87. package/dist-lib/components/ui/Pagination.d.ts +23 -0
  88. package/dist-lib/components/ui/Pagination.js +153 -0
  89. package/dist-lib/components/ui/Pagination.js.map +1 -0
  90. package/dist-lib/components/ui/Popover.js +35 -0
  91. package/dist-lib/components/ui/Popover.js.map +1 -0
  92. package/dist-lib/components/ui/Progress.d.ts +3 -3
  93. package/dist-lib/components/ui/Progress.js +100 -0
  94. package/dist-lib/components/ui/Progress.js.map +1 -0
  95. package/dist-lib/components/ui/RadioGroup.d.ts +1 -1
  96. package/dist-lib/components/ui/RadioGroup.js +81 -0
  97. package/dist-lib/components/ui/RadioGroup.js.map +1 -0
  98. package/dist-lib/components/ui/RelativeTime.d.ts +19 -0
  99. package/dist-lib/components/ui/RelativeTime.js +31 -0
  100. package/dist-lib/components/ui/RelativeTime.js.map +1 -0
  101. package/dist-lib/components/ui/ScrollArea.d.ts +9 -1
  102. package/dist-lib/components/ui/ScrollArea.js +60 -0
  103. package/dist-lib/components/ui/ScrollArea.js.map +1 -0
  104. package/dist-lib/components/ui/Select.d.ts +6 -1
  105. package/dist-lib/components/ui/Select.js +139 -0
  106. package/dist-lib/components/ui/Select.js.map +1 -0
  107. package/dist-lib/components/ui/Separator.js +26 -0
  108. package/dist-lib/components/ui/Separator.js.map +1 -0
  109. package/dist-lib/components/ui/Sheet.d.ts +10 -1
  110. package/dist-lib/components/ui/Sheet.js +136 -0
  111. package/dist-lib/components/ui/Sheet.js.map +1 -0
  112. package/dist-lib/components/ui/Sidebar.d.ts +29 -14
  113. package/dist-lib/components/ui/Sidebar.js +212 -0
  114. package/dist-lib/components/ui/Sidebar.js.map +1 -0
  115. package/dist-lib/components/ui/Skeleton.d.ts +13 -2
  116. package/dist-lib/components/ui/Skeleton.js +29 -0
  117. package/dist-lib/components/ui/Skeleton.js.map +1 -0
  118. package/dist-lib/components/ui/Slider.js +64 -0
  119. package/dist-lib/components/ui/Slider.js.map +1 -0
  120. package/dist-lib/components/ui/Snackbar.d.ts +1 -1
  121. package/dist-lib/components/ui/Snackbar.js +57 -0
  122. package/dist-lib/components/ui/Snackbar.js.map +1 -0
  123. package/dist-lib/components/ui/Spinner.js +54 -0
  124. package/dist-lib/components/ui/Spinner.js.map +1 -0
  125. package/dist-lib/components/ui/Stepper.d.ts +6 -0
  126. package/dist-lib/components/ui/Stepper.js +111 -0
  127. package/dist-lib/components/ui/Stepper.js.map +1 -0
  128. package/dist-lib/components/ui/Switch.js +80 -0
  129. package/dist-lib/components/ui/Switch.js.map +1 -0
  130. package/dist-lib/components/ui/Table.d.ts +41 -5
  131. package/dist-lib/components/ui/Table.js +179 -0
  132. package/dist-lib/components/ui/Table.js.map +1 -0
  133. package/dist-lib/components/ui/Tabs.d.ts +1 -1
  134. package/dist-lib/components/ui/Tabs.js +91 -0
  135. package/dist-lib/components/ui/Tabs.js.map +1 -0
  136. package/dist-lib/components/ui/TagInput.d.ts +17 -2
  137. package/dist-lib/components/ui/TagInput.js +111 -0
  138. package/dist-lib/components/ui/TagInput.js.map +1 -0
  139. package/dist-lib/components/ui/Textarea.js +88 -0
  140. package/dist-lib/components/ui/Textarea.js.map +1 -0
  141. package/dist-lib/components/ui/Timeline.d.ts +16 -6
  142. package/dist-lib/components/ui/Timeline.js +56 -0
  143. package/dist-lib/components/ui/Timeline.js.map +1 -0
  144. package/dist-lib/components/ui/Toast.d.ts +1 -1
  145. package/dist-lib/components/ui/Toast.js +158 -0
  146. package/dist-lib/components/ui/Toast.js.map +1 -0
  147. package/dist-lib/components/ui/Tooltip.d.ts +4 -1
  148. package/dist-lib/components/ui/Tooltip.js +60 -0
  149. package/dist-lib/components/ui/Tooltip.js.map +1 -0
  150. package/dist-lib/components/ui/TopNav.d.ts +7 -3
  151. package/dist-lib/components/ui/TopNav.js +62 -0
  152. package/dist-lib/components/ui/TopNav.js.map +1 -0
  153. package/dist-lib/components/ui/Tree.d.ts +34 -7
  154. package/dist-lib/components/ui/Tree.js +170 -0
  155. package/dist-lib/components/ui/Tree.js.map +1 -0
  156. package/dist-lib/hooks/use-debounce.d.ts +19 -0
  157. package/dist-lib/hooks/use-debounce.js +36 -0
  158. package/dist-lib/hooks/use-debounce.js.map +1 -0
  159. package/dist-lib/hooks/use-delayed-loading.d.ts +21 -0
  160. package/dist-lib/hooks/use-delayed-loading.js +20 -0
  161. package/dist-lib/hooks/use-delayed-loading.js.map +1 -0
  162. package/dist-lib/hooks/use-field-ids.d.ts +30 -0
  163. package/dist-lib/hooks/use-field-ids.js +18 -0
  164. package/dist-lib/hooks/use-field-ids.js.map +1 -0
  165. package/dist-lib/hooks/use-media-query.d.ts +3 -1
  166. package/dist-lib/hooks/use-media-query.js +22 -0
  167. package/dist-lib/hooks/use-media-query.js.map +1 -0
  168. package/dist-lib/hooks/use-modifier-key.d.ts +13 -0
  169. package/dist-lib/hooks/use-modifier-key.js +19 -0
  170. package/dist-lib/hooks/use-modifier-key.js.map +1 -0
  171. package/dist-lib/hooks/use-strings.d.ts +5 -0
  172. package/dist-lib/hooks/use-strings.js +12 -0
  173. package/dist-lib/hooks/use-strings.js.map +1 -0
  174. package/dist-lib/hooks/use-toast.d.ts +7 -2
  175. package/dist-lib/hooks/use-toast.js +67 -0
  176. package/dist-lib/hooks/use-toast.js.map +1 -0
  177. package/dist-lib/icon.d.ts +8 -0
  178. package/dist-lib/icon.js +7 -0
  179. package/dist-lib/icon.js.map +1 -0
  180. package/dist-lib/icons/index.d.ts +80 -88
  181. package/dist-lib/icons/index.js +163 -0
  182. package/dist-lib/icons/index.js.map +1 -0
  183. package/dist-lib/illustrations/index.d.ts +1 -0
  184. package/dist-lib/illustrations/index.js +114 -0
  185. package/dist-lib/illustrations/index.js.map +1 -0
  186. package/dist-lib/index.d.ts +68 -59
  187. package/dist-lib/index.js +288 -4530
  188. package/dist-lib/index.js.map +1 -1
  189. package/dist-lib/lib/format.d.ts +65 -0
  190. package/dist-lib/lib/format.js +95 -0
  191. package/dist-lib/lib/format.js.map +1 -0
  192. package/dist-lib/lib/return-focus.d.ts +11 -0
  193. package/dist-lib/lib/return-focus.js +12 -0
  194. package/dist-lib/lib/return-focus.js.map +1 -0
  195. package/dist-lib/lib/strings.d.ts +190 -0
  196. package/dist-lib/lib/strings.js +141 -0
  197. package/dist-lib/lib/strings.js.map +1 -0
  198. package/dist-lib/lib/strings.mn.d.ts +3 -0
  199. package/dist-lib/lib/strings.mn.js +115 -0
  200. package/dist-lib/lib/strings.mn.js.map +1 -0
  201. package/dist-lib/lib/utils.d.ts +0 -1
  202. package/dist-lib/lib/utils.js +10 -0
  203. package/dist-lib/lib/utils.js.map +1 -0
  204. package/dist-lib/styles.css +1 -1
  205. package/dist-lib/theme.css +840 -0
  206. package/package.json +38 -8
  207. package/dist-lib/index.cjs +0 -8
  208. package/dist-lib/index.cjs.map +0 -1
@@ -0,0 +1,149 @@
1
+ "use client";
2
+ import { jsxs as B, jsx as e } from "react/jsx-runtime";
3
+ import { forwardRef as Y, useRef as Z, useState as P } from "react";
4
+ import { X as $, EyeOff as ee, Eye as te, Search as re } from "../../icons/index.js";
5
+ import { cn as s } from "../../lib/utils.js";
6
+ import { useStrings as ne } from "../../hooks/use-strings.js";
7
+ import { useFieldIds as se } from "../../hooks/use-field-ids.js";
8
+ import { cva as j } from "class-variance-authority";
9
+ const ie = j(
10
+ [
11
+ "group inline-flex items-center w-full",
12
+ "rounded-md border border-border-input bg-card text-sm",
13
+ "transition-colors duration-[var(--duration-fast)] ease-[var(--ease-out)]",
14
+ "has-disabled:opacity-50 has-disabled:pointer-events-none",
15
+ "focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-offset-background",
16
+ // Invalid state comes from the input's aria-invalid so Form wiring needs no extra prop.
17
+ "has-aria-invalid:border-danger has-aria-invalid:focus-within:border-danger has-aria-invalid:focus-within:ring-danger"
18
+ ],
19
+ {
20
+ variants: {
21
+ size: {
22
+ sm: "h-8 px-2.5 gap-1.5",
23
+ md: "h-9 px-3 gap-2",
24
+ lg: "h-10 px-3.5 gap-2"
25
+ },
26
+ tone: {
27
+ default: "focus-within:border-accent focus-within:ring-ring",
28
+ error: "border-danger focus-within:border-danger focus-within:ring-danger"
29
+ }
30
+ },
31
+ defaultVariants: {
32
+ size: "md",
33
+ tone: "default"
34
+ }
35
+ }
36
+ ), oe = j([
37
+ "flex-1 min-w-0 bg-transparent outline-none",
38
+ "placeholder:text-foreground-subtle",
39
+ "text-foreground",
40
+ // 16px below md so iOS Safari does not zoom the page on focus; --text-lg = 1rem.
41
+ "text-lg md:text-sm",
42
+ "disabled:cursor-not-allowed"
43
+ ]), S = s(
44
+ "text-foreground-subtle hover:text-foreground relative -m-1 flex size-6 shrink-0 items-center justify-center rounded-sm",
45
+ 'before:absolute before:-inset-0.5 before:content-[""]',
46
+ "focus-visible:ring-ring focus-visible:ring-offset-background outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
47
+ "disabled:pointer-events-none"
48
+ ), ae = Y(function({
49
+ className: R,
50
+ type: i = "text",
51
+ size: O,
52
+ tone: F,
53
+ label: v,
54
+ helperText: l,
55
+ error: o,
56
+ prefix: H,
57
+ suffix: w,
58
+ clearable: L,
59
+ onClear: u,
60
+ hideLabel: T,
61
+ id: _,
62
+ disabled: r,
63
+ value: N,
64
+ defaultValue: a,
65
+ onInput: f,
66
+ "aria-describedby": D,
67
+ "aria-invalid": m,
68
+ ...M
69
+ }, c) {
70
+ const g = ne(), y = !!o || m === !0 || m === "true", { fieldId: z, helperId: V, errorId: X, describedBy: q } = se(_, {
71
+ hasHelper: !!l,
72
+ hasError: !!o,
73
+ extra: D
74
+ }), h = N === null ? "" : N, n = Z(null), A = (t) => {
75
+ n.current = t, typeof c == "function" ? c(t) : c && (c.current = t);
76
+ }, [d, C] = P(!1), G = i === "password" ? d ? "text" : "password" : i, J = y ? "error" : F, E = H ?? (i === "search" ? /* @__PURE__ */ e(re, { className: "text-foreground-subtle size-4", "aria-hidden": !0 }) : null), p = h !== void 0, [K, I] = P(
77
+ a == null ? "" : String(a)
78
+ ), b = p ? h : K, Q = b !== void 0 && b !== "" && b !== null, U = (t) => {
79
+ p || I(t.currentTarget.value), f == null || f(t);
80
+ }, W = () => {
81
+ var t, k;
82
+ if (!p && n.current) {
83
+ const x = (t = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")) == null ? void 0 : t.set;
84
+ x == null || x.call(n.current, ""), n.current.dispatchEvent(new Event("input", { bubbles: !0 })), I("");
85
+ }
86
+ u == null || u(), (k = n.current) == null || k.focus();
87
+ };
88
+ return /* @__PURE__ */ B("div", { className: s("flex flex-col gap-1.5", R), children: [
89
+ v && /* @__PURE__ */ e(
90
+ "label",
91
+ {
92
+ htmlFor: z,
93
+ className: s("text-foreground text-sm font-medium", T && "sr-only"),
94
+ children: v
95
+ }
96
+ ),
97
+ /* @__PURE__ */ B("div", { className: s(ie({ size: O, tone: J })), children: [
98
+ E && /* @__PURE__ */ e("span", { className: "text-foreground-subtle flex items-center [&_svg]:size-4", children: E }),
99
+ /* @__PURE__ */ e(
100
+ "input",
101
+ {
102
+ ref: A,
103
+ id: z,
104
+ type: G,
105
+ disabled: r,
106
+ value: h,
107
+ defaultValue: a,
108
+ onInput: U,
109
+ "aria-invalid": y ? !0 : m,
110
+ "aria-describedby": q,
111
+ className: s(oe()),
112
+ ...M
113
+ }
114
+ ),
115
+ L && Q && /* @__PURE__ */ e(
116
+ "button",
117
+ {
118
+ type: "button",
119
+ onClick: W,
120
+ disabled: r,
121
+ tabIndex: r ? -1 : void 0,
122
+ "aria-label": g.input.clear,
123
+ className: S,
124
+ children: /* @__PURE__ */ e($, { className: "size-4", "aria-hidden": !0 })
125
+ }
126
+ ),
127
+ i === "password" && /* @__PURE__ */ e(
128
+ "button",
129
+ {
130
+ type: "button",
131
+ onClick: () => C((t) => !t),
132
+ disabled: r,
133
+ tabIndex: r ? -1 : void 0,
134
+ "aria-label": d ? g.input.hidePassword : g.input.showPassword,
135
+ "aria-pressed": d,
136
+ className: S,
137
+ children: d ? /* @__PURE__ */ e(ee, { className: "size-4", "aria-hidden": !0 }) : /* @__PURE__ */ e(te, { className: "size-4", "aria-hidden": !0 })
138
+ }
139
+ ),
140
+ w && /* @__PURE__ */ e("span", { className: "text-foreground-subtle flex items-center [&_svg]:size-4", children: w })
141
+ ] }),
142
+ o ? /* @__PURE__ */ e("p", { id: X, className: "text-danger-text text-xs", children: o }) : l ? /* @__PURE__ */ e("p", { id: V, className: "text-foreground-subtle text-xs", children: l }) : null
143
+ ] });
144
+ });
145
+ ae.displayName = "Input";
146
+ export {
147
+ ae as Input
148
+ };
149
+ //# sourceMappingURL=Input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Input.js","sources":["../../../src/components/ui/Input.tsx"],"sourcesContent":["'use client';\n\nimport {\n forwardRef,\n useRef,\n useState,\n type ComponentProps,\n type InputHTMLAttributes,\n type ReactNode,\n} from 'react';\nimport { Eye, EyeOff, Search, X } from '@/icons';\nimport { cn } from '@/lib/utils';\nimport { useStrings } from '@/hooks/use-strings';\nimport { cva, type VariantProps } from '@/lib/cva';\nimport { useFieldIds } from '@/hooks/use-field-ids';\n\n/* -----------------------------------------------------------------------------\n * Field-shell variants. The inner <input> is unstyled background and gets all\n * its visual treatment from this wrapper so prefix / suffix / clear slots\n * share the same border + focus state with no double rings.\n * --------------------------------------------------------------------------- */\nconst field = cva(\n [\n 'group inline-flex items-center w-full',\n 'rounded-md border border-border-input bg-card text-sm',\n 'transition-colors duration-[var(--duration-fast)] ease-[var(--ease-out)]',\n 'has-disabled:opacity-50 has-disabled:pointer-events-none',\n 'focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-offset-background',\n // Invalid state comes from the input's aria-invalid so Form wiring needs no extra prop.\n 'has-aria-invalid:border-danger has-aria-invalid:focus-within:border-danger has-aria-invalid:focus-within:ring-danger',\n ],\n {\n variants: {\n size: {\n sm: 'h-8 px-2.5 gap-1.5',\n md: 'h-9 px-3 gap-2',\n lg: 'h-10 px-3.5 gap-2',\n },\n tone: {\n default: 'focus-within:border-accent focus-within:ring-ring',\n error: 'border-danger focus-within:border-danger focus-within:ring-danger',\n },\n },\n defaultVariants: {\n size: 'md',\n tone: 'default',\n },\n },\n);\n\nconst innerInput = cva([\n 'flex-1 min-w-0 bg-transparent outline-none',\n 'placeholder:text-foreground-subtle',\n 'text-foreground',\n // 16px below md so iOS Safari does not zoom the page on focus; --text-lg = 1rem.\n 'text-lg md:text-sm',\n 'disabled:cursor-not-allowed',\n]);\n\ntype InputType = NonNullable<InputHTMLAttributes<HTMLInputElement>['type']>;\n\n// Inline clear / password buttons: 24px box (WCAG 2.5.8 minimum) with a halo\n// that extends the hit area without growing the field.\nconst innerButton = cn(\n 'text-foreground-subtle hover:text-foreground relative -m-1 flex size-6 shrink-0 items-center justify-center rounded-sm',\n 'before:absolute before:-inset-0.5 before:content-[\"\"]',\n 'focus-visible:ring-ring focus-visible:ring-offset-background outline-none focus-visible:ring-2 focus-visible:ring-offset-2',\n 'disabled:pointer-events-none',\n);\n\nexport interface InputProps\n extends\n Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'prefix' | 'value'>,\n VariantProps<typeof field> {\n /** Controlled value. `null` is treated as an empty controlled value (no React warning). */\n value?: InputHTMLAttributes<HTMLInputElement>['value'] | null;\n /** Field type. `password` enables a show/hide toggle; `search` adds a clear button. */\n type?: InputType;\n /** Visible label rendered above the input. */\n label?: ReactNode;\n /** Hint below the input. Hidden while `error` is set. */\n helperText?: ReactNode;\n /** Validation message. Renders in `danger` colour and sets `aria-invalid`. */\n error?: ReactNode;\n /** Content rendered inside the field, before the input — icon or short text. */\n prefix?: ReactNode;\n /** Content rendered inside the field, after the input. */\n suffix?: ReactNode;\n /** Show an inline clear (×) button when the input has a value. */\n clearable?: boolean;\n /**\n * Fires when the clear button is pressed. Controlled inputs own their state\n * and should reset `value` here; uncontrolled inputs are cleared for you.\n */\n onClear?: () => void;\n /** Visually conceal the label while keeping it for screen readers. */\n hideLabel?: boolean;\n}\n\n/**\n * Text-style input with optional label, helper / error, prefix / suffix,\n * clear, and password show-hide. Wraps a single native `<input>` so it works\n * with `react-hook-form` and any controlled / uncontrolled pattern.\n *\n * @example Email with helper\n * <Input type=\"email\" label=\"Work email\" helperText=\"We never share this.\" />\n *\n * @example Password with show/hide\n * <Input type=\"password\" label=\"Password\" autoComplete=\"current-password\" />\n *\n * @example Search with clear\n * <Input type=\"search\" placeholder=\"Search…\" value={q} onChange={…}\n * clearable onClear={() => setQ('')} />\n *\n * @do Always pair an input with a visible label. If space forces hiding it,\n * use `hideLabel` so the label stays in the accessibility tree.\n * @dont Use placeholder as the only label — placeholders disappear on input\n * and fail accessibility.\n */\nexport const Input = forwardRef<HTMLInputElement, InputProps>(function Input(\n {\n className,\n type = 'text',\n size,\n tone,\n label,\n helperText,\n error,\n prefix,\n suffix,\n clearable,\n onClear,\n hideLabel,\n id,\n disabled,\n value: valueProp,\n defaultValue,\n onInput,\n 'aria-describedby': ariaDescribedby,\n 'aria-invalid': ariaInvalid,\n ...props\n },\n ref,\n) {\n const strings = useStrings();\n const isError = Boolean(error) || ariaInvalid === true || ariaInvalid === 'true';\n const { fieldId, helperId, errorId, describedBy } = useFieldIds(id, {\n hasHelper: Boolean(helperText),\n hasError: Boolean(error),\n extra: ariaDescribedby,\n });\n // `null` (nullable form state) is a controlled empty value, not \"uncontrolled\".\n const value = valueProp === null ? '' : valueProp;\n const innerRef = useRef<HTMLInputElement | null>(null);\n const setRefs = (el: HTMLInputElement | null) => {\n innerRef.current = el;\n if (typeof ref === 'function') ref(el);\n else if (ref) ref.current = el;\n };\n\n const [showPassword, setShowPassword] = useState(false);\n const effectiveType = type === 'password' ? (showPassword ? 'text' : 'password') : type;\n\n const effectiveTone = isError ? 'error' : tone;\n\n // Pre-fill prefix slot for search type.\n const renderedPrefix =\n prefix ??\n (type === 'search' ? <Search className=\"text-foreground-subtle size-4\" aria-hidden /> : null);\n\n // Track the value of uncontrolled inputs so `clearable` can show/hide the\n // button without the consumer wiring state.\n const isControlled = value !== undefined;\n const [localValue, setLocalValue] = useState<string>(\n defaultValue === undefined || defaultValue === null ? '' : String(defaultValue),\n );\n const current = isControlled ? value : localValue;\n const hasValue = current !== undefined && current !== '' && current !== null;\n\n // React 19 types `onInput` as `InputEventHandler`; `ComponentProps` keeps this portable.\n const handleInput: NonNullable<ComponentProps<'input'>['onInput']> = (e) => {\n if (!isControlled) setLocalValue(e.currentTarget.value);\n onInput?.(e);\n };\n\n const handleClear = () => {\n if (!isControlled && innerRef.current) {\n // Use the native setter so React's onChange/onInput listeners fire.\n const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;\n setter?.call(innerRef.current, '');\n innerRef.current.dispatchEvent(new Event('input', { bubbles: true }));\n setLocalValue('');\n }\n onClear?.();\n innerRef.current?.focus();\n };\n\n return (\n <div className={cn('flex flex-col gap-1.5', className)}>\n {label && (\n <label\n htmlFor={fieldId}\n className={cn('text-foreground text-sm font-medium', hideLabel && 'sr-only')}\n >\n {label}\n </label>\n )}\n\n <div className={cn(field({ size, tone: effectiveTone }))}>\n {renderedPrefix && (\n <span className=\"text-foreground-subtle flex items-center [&_svg]:size-4\">\n {renderedPrefix}\n </span>\n )}\n\n <input\n ref={setRefs}\n id={fieldId}\n type={effectiveType}\n disabled={disabled}\n value={value}\n defaultValue={defaultValue}\n onInput={handleInput}\n aria-invalid={isError ? true : ariaInvalid}\n aria-describedby={describedBy}\n className={cn(innerInput())}\n {...props}\n />\n\n {clearable && hasValue && (\n <button\n type=\"button\"\n onClick={handleClear}\n disabled={disabled}\n tabIndex={disabled ? -1 : undefined}\n aria-label={strings.input.clear}\n className={innerButton}\n >\n <X className=\"size-4\" aria-hidden />\n </button>\n )}\n\n {type === 'password' && (\n <button\n type=\"button\"\n onClick={() => setShowPassword((p) => !p)}\n disabled={disabled}\n tabIndex={disabled ? -1 : undefined}\n aria-label={showPassword ? strings.input.hidePassword : strings.input.showPassword}\n aria-pressed={showPassword}\n className={innerButton}\n >\n {showPassword ? (\n <EyeOff className=\"size-4\" aria-hidden />\n ) : (\n <Eye className=\"size-4\" aria-hidden />\n )}\n </button>\n )}\n\n {suffix && (\n <span className=\"text-foreground-subtle flex items-center [&_svg]:size-4\">{suffix}</span>\n )}\n </div>\n\n {error ? (\n <p id={errorId} className=\"text-danger-text text-xs\">\n {error}\n </p>\n ) : helperText ? (\n <p id={helperId} className=\"text-foreground-subtle text-xs\">\n {helperText}\n </p>\n ) : null}\n </div>\n );\n});\n\nInput.displayName = 'Input';\n"],"names":["field","cva","innerInput","innerButton","cn","Input","forwardRef","className","type","size","tone","label","helperText","error","prefix","suffix","clearable","onClear","hideLabel","id","disabled","valueProp","defaultValue","onInput","ariaDescribedby","ariaInvalid","props","ref","strings","useStrings","isError","fieldId","helperId","errorId","describedBy","useFieldIds","value","innerRef","useRef","setRefs","el","showPassword","setShowPassword","useState","effectiveType","effectiveTone","renderedPrefix","jsx","Search","isControlled","localValue","setLocalValue","current","hasValue","handleInput","e","handleClear","setter","_a","_b","jsxs","X","p","EyeOff","Eye"],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ "use client";
2
+ import { jsx as n } from "react/jsx-runtime";
3
+ import { forwardRef as m } from "react";
4
+ import { cn as s } from "../../lib/utils.js";
5
+ const d = m(function({ size: e = "sm", className: r, ...o }, t) {
6
+ return /* @__PURE__ */ n(
7
+ "kbd",
8
+ {
9
+ ref: t,
10
+ className: s(
11
+ "border-border bg-background-subtle inline-flex items-center justify-center rounded-sm border font-mono",
12
+ "text-foreground-muted shadow-xs",
13
+ e === "sm" ? "h-5 min-w-5 px-1 text-xs" : "h-6 min-w-6 px-1.5 text-xs",
14
+ r
15
+ ),
16
+ ...o
17
+ }
18
+ );
19
+ });
20
+ d.displayName = "Kbd";
21
+ export {
22
+ d as Kbd
23
+ };
24
+ //# sourceMappingURL=Kbd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Kbd.js","sources":["../../../src/components/ui/Kbd.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef, type HTMLAttributes } from 'react';\nimport { cn } from '@/lib/utils';\n\nexport interface KbdProps extends HTMLAttributes<HTMLElement> {\n /** Visual size — match the surrounding text. */\n size?: 'sm' | 'md';\n}\n\n/**\n * Stylised keyboard shortcut indicator. Compose multiple `<Kbd>` for chords.\n *\n * @example\n * Press <Kbd>⌘</Kbd>+<Kbd>K</Kbd> to open the command palette.\n *\n * @do Use OS-conventional symbols (⌘ ⇧ ⌥ ⌃ ⏎) — keep visuals consistent\n * across the app.\n * @dont Use Kbd for clickable buttons — it implies a real keyboard input.\n */\nexport const Kbd = forwardRef<HTMLElement, KbdProps>(function Kbd(\n { size = 'sm', className, ...props },\n ref,\n) {\n return (\n <kbd\n ref={ref}\n className={cn(\n 'border-border bg-background-subtle inline-flex items-center justify-center rounded-sm border font-mono',\n 'text-foreground-muted shadow-xs',\n size === 'sm' ? 'h-5 min-w-5 px-1 text-xs' : 'h-6 min-w-6 px-1.5 text-xs',\n className,\n )}\n {...props}\n />\n );\n});\nKbd.displayName = 'Kbd';\n"],"names":["Kbd","forwardRef","size","className","props","ref","jsx","cn"],"mappings":";;;;AAoBO,MAAMA,IAAMC,EAAkC,SACnD,EAAE,MAAAC,IAAO,MAAM,WAAAC,GAAW,GAAGC,EAAA,GAC7BC,GACA;AACA,SACE,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAAD;AAAA,MACA,WAAWE;AAAA,QACT;AAAA,QACA;AAAA,QACAL,MAAS,OAAO,6BAA6B;AAAA,QAC7CC;AAAA,MAAA;AAAA,MAED,GAAGC;AAAA,IAAA;AAAA,EAAA;AAGV,CAAC;AACDJ,EAAI,cAAc;"}
@@ -32,6 +32,8 @@ export interface MultiSelectProps {
32
32
  clearable?: boolean;
33
33
  /** Disable the entire field. */
34
34
  disabled?: boolean;
35
+ /** Consumer-supplied id for the search input (label association). */
36
+ id?: string;
35
37
  className?: string;
36
38
  }
37
39
  /**
@@ -39,7 +41,7 @@ export interface MultiSelectProps {
39
41
  *
40
42
  * Keyboard:
41
43
  * - Backspace on the input with empty query removes the last chip
42
- * - Enter / Space toggles the highlighted option
44
+ * - ArrowUp / ArrowDown move the highlight, Enter toggles the highlighted option
43
45
  * - Escape closes the menu
44
46
  *
45
47
  * @example Tag picker
@@ -0,0 +1,210 @@
1
+ "use client";
2
+ import { jsxs as n, jsx as t } from "react/jsx-runtime";
3
+ import { forwardRef as Y, useState as N, useRef as O, useCallback as k, useMemo as P } from "react";
4
+ import * as g from "@radix-ui/react-popover";
5
+ import { Command as b } from "cmdk";
6
+ import { X as j, ChevronsUpDown as Z, Check as _ } from "../../icons/index.js";
7
+ import { cn as a } from "../../lib/utils.js";
8
+ import { useStrings as ee } from "../../hooks/use-strings.js";
9
+ import { formatString as te } from "../../lib/strings.js";
10
+ import { useFieldIds as re } from "../../hooks/use-field-ids.js";
11
+ const oe = Y(function({
12
+ options: l,
13
+ value: r,
14
+ onChange: s,
15
+ label: d,
16
+ helperText: h,
17
+ error: x,
18
+ placeholder: B,
19
+ emptyText: L,
20
+ maxVisibleChips: M = 3,
21
+ clearable: E = !0,
22
+ disabled: c,
23
+ id: S,
24
+ className: K
25
+ }, U) {
26
+ const i = ee(), z = B ?? i.multiSelect.placeholder, $ = L ?? i.multiSelect.empty, v = !!x, { fieldId: u, helperId: q, errorId: H, describedBy: Q } = re(S, {
27
+ hasHelper: !!h,
28
+ hasError: v
29
+ }), [f, m] = N(!1), [p, X] = N(""), C = O(null), I = O(null), [G, J] = N(void 0), T = k((e) => J(e == null ? void 0 : e.id), []), w = P(() => l.filter((e) => r.includes(e.value)), [l, r]), D = k(
30
+ (e) => {
31
+ r.includes(e) ? s(r.filter((o) => o !== e)) : s([...r, e]);
32
+ },
33
+ [r, s]
34
+ ), V = k(() => s([]), [s]), W = (e) => {
35
+ e.key === "Backspace" && p === "" && r.length > 0 ? s(r.slice(0, -1)) : (e.key === "ArrowDown" || e.key === "ArrowUp") && !f && m(!0);
36
+ }, R = P(
37
+ () => l.filter((e) => e.label.toLowerCase().includes(p.toLowerCase())),
38
+ [l, p]
39
+ ), y = w.slice(0, M), A = w.length - y.length;
40
+ return /* @__PURE__ */ n("div", { ref: U, className: a("flex flex-col gap-1.5", K), children: [
41
+ d && /* @__PURE__ */ t(
42
+ "label",
43
+ {
44
+ id: `${u}-label`,
45
+ htmlFor: u,
46
+ className: "text-foreground text-sm font-medium",
47
+ children: d
48
+ }
49
+ ),
50
+ /* @__PURE__ */ t(b, { shouldFilter: !1, className: "flex w-full flex-col", children: /* @__PURE__ */ n(g.Root, { open: f, onOpenChange: m, children: [
51
+ /* @__PURE__ */ t(g.Anchor, { asChild: !0, children: /* @__PURE__ */ n(
52
+ "div",
53
+ {
54
+ ref: I,
55
+ "aria-disabled": c || void 0,
56
+ className: a(
57
+ "bg-card flex h-9 w-full cursor-text items-center gap-1.5 overflow-hidden rounded-md border px-2 py-1 text-sm",
58
+ "transition-colors duration-[var(--duration-fast)] ease-[var(--ease-out)]",
59
+ "focus-within:ring-offset-background focus-within:ring-2 focus-within:ring-offset-2",
60
+ v ? "border-danger focus-within:border-danger focus-within:ring-danger" : "border-border-input focus-within:border-accent focus-within:ring-ring",
61
+ c && "pointer-events-none opacity-50"
62
+ ),
63
+ onClick: () => {
64
+ var e;
65
+ (e = C.current) == null || e.focus(), c || m(!0);
66
+ },
67
+ children: [
68
+ /* @__PURE__ */ n("div", { className: "flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden", children: [
69
+ y.map((e) => /* @__PURE__ */ n(
70
+ "span",
71
+ {
72
+ className: "bg-accent-soft text-on-accent-soft inline-flex max-w-[10rem] shrink-0 items-center gap-1 rounded-md px-1.5 py-0.5 text-xs font-medium",
73
+ children: [
74
+ /* @__PURE__ */ t("span", { className: "truncate", title: e.label, children: e.label }),
75
+ /* @__PURE__ */ t(
76
+ "button",
77
+ {
78
+ type: "button",
79
+ "aria-label": te(i.multiSelect.remove, { label: e.label }),
80
+ className: "text-on-accent-soft hover:text-foreground focus-visible:ring-ring focus-visible:ring-offset-background relative inline-flex items-center rounded-sm outline-none before:absolute before:-inset-1.5 before:content-[''] focus-visible:ring-2 focus-visible:ring-offset-1",
81
+ onClick: (o) => {
82
+ o.stopPropagation(), D(e.value);
83
+ },
84
+ children: /* @__PURE__ */ t(j, { className: "size-3", "aria-hidden": !0 })
85
+ }
86
+ )
87
+ ]
88
+ },
89
+ e.value
90
+ )),
91
+ A > 0 && /* @__PURE__ */ n("span", { className: "bg-background-muted text-foreground-muted shrink-0 rounded-md px-1.5 py-0.5 text-xs font-medium", children: [
92
+ "+",
93
+ A
94
+ ] }),
95
+ /* @__PURE__ */ t(
96
+ "input",
97
+ {
98
+ ref: C,
99
+ id: u,
100
+ type: "text",
101
+ autoComplete: "off",
102
+ value: p,
103
+ onChange: (e) => X(e.target.value),
104
+ onFocus: () => m(!0),
105
+ onKeyDown: W,
106
+ placeholder: y.length === 0 ? z : void 0,
107
+ role: "combobox",
108
+ "aria-expanded": f,
109
+ "aria-controls": f ? G : void 0,
110
+ "aria-haspopup": "listbox",
111
+ "aria-autocomplete": "list",
112
+ "aria-labelledby": d ? `${u}-label` : void 0,
113
+ "aria-label": d || S ? void 0 : z,
114
+ "aria-invalid": v || void 0,
115
+ "aria-describedby": Q,
116
+ className: "placeholder:text-foreground-subtle min-w-[6ch] flex-1 bg-transparent text-lg outline-none md:text-sm",
117
+ disabled: c
118
+ }
119
+ )
120
+ ] }),
121
+ /* @__PURE__ */ n("span", { className: "flex shrink-0 items-center gap-1 pl-1", children: [
122
+ E && w.length > 0 && /* @__PURE__ */ t(
123
+ "button",
124
+ {
125
+ type: "button",
126
+ "aria-label": i.multiSelect.clearAll,
127
+ onClick: (e) => {
128
+ e.stopPropagation(), V();
129
+ },
130
+ className: "text-foreground-subtle hover:text-foreground focus-visible:ring-ring focus-visible:ring-offset-background inline-flex size-6 shrink-0 items-center justify-center rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
131
+ children: /* @__PURE__ */ t(j, { className: "size-4", "aria-hidden": !0 })
132
+ }
133
+ ),
134
+ /* @__PURE__ */ t(Z, { className: "text-foreground-subtle size-4", "aria-hidden": !0 })
135
+ ] })
136
+ ]
137
+ }
138
+ ) }),
139
+ /* @__PURE__ */ t(g.Portal, { children: /* @__PURE__ */ t(
140
+ g.Content,
141
+ {
142
+ align: "start",
143
+ sideOffset: 4,
144
+ "aria-label": i.command.suggestions,
145
+ className: a(
146
+ "border-border bg-popover text-popover-foreground z-[var(--z-popover)] w-[var(--radix-popover-trigger-width)] overflow-hidden rounded-lg border shadow-md",
147
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
148
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
149
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
150
+ ),
151
+ onOpenAutoFocus: (e) => e.preventDefault(),
152
+ onInteractOutside: (e) => {
153
+ var F;
154
+ const o = e.target;
155
+ o && ((F = I.current) != null && F.contains(o)) && e.preventDefault();
156
+ },
157
+ children: /* @__PURE__ */ n(
158
+ b.List,
159
+ {
160
+ ref: T,
161
+ label: i.command.suggestions,
162
+ className: "max-h-64 overflow-y-auto p-1",
163
+ children: [
164
+ R.map((e) => {
165
+ const o = r.includes(e.value);
166
+ return /* @__PURE__ */ n(
167
+ b.Item,
168
+ {
169
+ value: e.value,
170
+ disabled: e.disabled,
171
+ onSelect: () => D(e.value),
172
+ className: a(
173
+ "text-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm",
174
+ "data-[selected=true]:bg-background-muted",
175
+ "data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50"
176
+ ),
177
+ children: [
178
+ /* @__PURE__ */ t(
179
+ "span",
180
+ {
181
+ "aria-hidden": !0,
182
+ className: a(
183
+ "flex size-4 items-center justify-center rounded-sm border",
184
+ o ? "border-accent bg-accent text-on-accent" : "border-border-input"
185
+ ),
186
+ children: o && /* @__PURE__ */ t(_, { className: "size-3", "aria-hidden": !0 })
187
+ }
188
+ ),
189
+ /* @__PURE__ */ t("span", { className: "flex-1", children: e.label }),
190
+ e.description && /* @__PURE__ */ t("span", { className: "text-foreground-subtle text-xs", children: e.description })
191
+ ]
192
+ },
193
+ e.value
194
+ );
195
+ }),
196
+ R.length === 0 && /* @__PURE__ */ t(b.Empty, { className: "text-foreground-subtle px-2 py-6 text-center text-sm", children: $ })
197
+ ]
198
+ }
199
+ )
200
+ }
201
+ ) })
202
+ ] }) }),
203
+ x ? /* @__PURE__ */ t("p", { id: H, className: "text-danger-text text-xs", children: x }) : h ? /* @__PURE__ */ t("p", { id: q, className: "text-foreground-subtle text-xs", children: h }) : null
204
+ ] });
205
+ });
206
+ oe.displayName = "MultiSelect";
207
+ export {
208
+ oe as MultiSelect
209
+ };
210
+ //# sourceMappingURL=MultiSelect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MultiSelect.js","sources":["../../../src/components/ui/MultiSelect.tsx"],"sourcesContent":["'use client';\n\nimport {\n forwardRef,\n useCallback,\n useMemo,\n useRef,\n useState,\n type KeyboardEvent,\n type ReactNode,\n} from 'react';\nimport * as PopoverPrimitive from '@radix-ui/react-popover';\nimport { Command as CommandPrimitive } from 'cmdk';\nimport { Check, ChevronsUpDown, X } from '@/icons';\nimport { cn } from '@/lib/utils';\nimport { useStrings } from '@/hooks/use-strings';\nimport { formatString } from '@/lib/strings';\nimport { useFieldIds } from '@/hooks/use-field-ids';\n\nexport interface MultiSelectOption {\n /** Stable, unique value submitted in `onChange`. */\n value: string;\n /** Visible label shown both in the menu and in the selected chip. */\n label: string;\n /** Optional secondary text shown after the label in the menu. */\n description?: string;\n /** Disable selecting this option. */\n disabled?: boolean;\n}\n\nexport interface MultiSelectProps {\n /** All possible options. */\n options: MultiSelectOption[];\n /** Currently selected values. Controlled. */\n value: string[];\n /** Called whenever the selection changes. */\n onChange: (next: string[]) => void;\n /** Visible label above the field. */\n label?: ReactNode;\n /** Hint below the field. Hidden when `error` is set. */\n helperText?: ReactNode;\n /** Validation message. */\n error?: ReactNode;\n /** Empty-state placeholder shown when nothing is selected. */\n placeholder?: string;\n /** Text shown when no options match the search. */\n emptyText?: string;\n /** Max number of chips rendered inline; remainder shown as \"+N more\". */\n maxVisibleChips?: number;\n /** Whether the user can clear all selections with a single click. */\n clearable?: boolean;\n /** Disable the entire field. */\n disabled?: boolean;\n /** Consumer-supplied id for the search input (label association). */\n id?: string;\n className?: string;\n}\n\n/**\n * Chip-based multi-select with a searchable menu (cmdk under the hood).\n *\n * Keyboard:\n * - Backspace on the input with empty query removes the last chip\n * - ArrowUp / ArrowDown move the highlight, Enter toggles the highlighted option\n * - Escape closes the menu\n *\n * @example Tag picker\n * <MultiSelect label=\"Tags\"\n * options={tags}\n * value={selected}\n * onChange={setSelected}\n * placeholder=\"Pick tags\" />\n *\n * @example Bounded with overflow chip\n * <MultiSelect options={users}\n * value={value}\n * onChange={setValue}\n * maxVisibleChips={3} />\n *\n * @do Cap `maxVisibleChips` (3–5) on narrow surfaces so the field doesn't\n * reflow as the user adds selections.\n * @dont Use MultiSelect for fewer than ~6 options. CheckboxGroup is clearer.\n */\nexport const MultiSelect = forwardRef<HTMLDivElement, MultiSelectProps>(function MultiSelect(\n {\n options,\n value,\n onChange,\n label,\n helperText,\n error,\n placeholder: placeholderProp,\n emptyText: emptyTextProp,\n maxVisibleChips = 3,\n clearable = true,\n disabled,\n id,\n className,\n },\n ref,\n) {\n const strings = useStrings();\n const placeholder = placeholderProp ?? strings.multiSelect.placeholder;\n const emptyText = emptyTextProp ?? strings.multiSelect.empty;\n const isError = Boolean(error);\n const { fieldId, helperId, errorId, describedBy } = useFieldIds(id, {\n hasHelper: Boolean(helperText),\n hasError: isError,\n });\n\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState('');\n const inputRef = useRef<HTMLInputElement>(null);\n const fieldRef = useRef<HTMLDivElement>(null);\n // cmdk assigns its own id to the list (ours is ignored), so read it back\n // from the node once the portal mounts to keep aria-controls truthful.\n const [listId, setListId] = useState<string | undefined>(undefined);\n const listRef = useCallback((el: HTMLDivElement | null) => setListId(el?.id), []);\n\n const selected = useMemo(() => options.filter((o) => value.includes(o.value)), [options, value]);\n\n const toggle = useCallback(\n (v: string) => {\n if (value.includes(v)) onChange(value.filter((x) => x !== v));\n else onChange([...value, v]);\n },\n [value, onChange],\n );\n\n const clear = useCallback(() => onChange([]), [onChange]);\n\n const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Backspace' && query === '' && value.length > 0) {\n onChange(value.slice(0, -1));\n } else if ((e.key === 'ArrowDown' || e.key === 'ArrowUp') && !open) {\n setOpen(true);\n }\n };\n\n const filtered = useMemo(\n () => options.filter((o) => o.label.toLowerCase().includes(query.toLowerCase())),\n [options, query],\n );\n\n const visibleChips = selected.slice(0, maxVisibleChips);\n const overflow = selected.length - visibleChips.length;\n\n return (\n <div ref={ref} className={cn('flex flex-col gap-1.5', className)}>\n {label && (\n <label\n id={`${fieldId}-label`}\n htmlFor={fieldId}\n className=\"text-foreground text-sm font-medium\"\n >\n {label}\n </label>\n )}\n\n {/* The Command root wraps the whole field so cmdk's root keydown handler\n (ArrowUp/Down/Enter) sees the input even though the list is portaled.\n A plain <input> is used instead of Command.Input because cmdk forces\n its own id / aria-* on that element, breaking the label association. */}\n <CommandPrimitive shouldFilter={false} className=\"flex w-full flex-col\">\n <PopoverPrimitive.Root open={open} onOpenChange={setOpen}>\n <PopoverPrimitive.Anchor asChild>\n {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -- click-to-focus wrapper; the <input role=\"combobox\"> inside owns keyboard handling */}\n <div\n ref={fieldRef}\n aria-disabled={disabled || undefined}\n className={cn(\n 'bg-card flex h-9 w-full cursor-text items-center gap-1.5 overflow-hidden rounded-md border px-2 py-1 text-sm',\n 'transition-colors duration-[var(--duration-fast)] ease-[var(--ease-out)]',\n 'focus-within:ring-offset-background focus-within:ring-2 focus-within:ring-offset-2',\n isError\n ? 'border-danger focus-within:border-danger focus-within:ring-danger'\n : 'border-border-input focus-within:border-accent focus-within:ring-ring',\n disabled && 'pointer-events-none opacity-50',\n )}\n onClick={() => {\n inputRef.current?.focus();\n if (!disabled) setOpen(true);\n }}\n >\n <div className=\"flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden\">\n {visibleChips.map((opt) => (\n <span\n key={opt.value}\n className=\"bg-accent-soft text-on-accent-soft inline-flex max-w-[10rem] shrink-0 items-center gap-1 rounded-md px-1.5 py-0.5 text-xs font-medium\"\n >\n <span className=\"truncate\" title={opt.label}>\n {opt.label}\n </span>\n <button\n type=\"button\"\n aria-label={formatString(strings.multiSelect.remove, { label: opt.label })}\n className=\"text-on-accent-soft hover:text-foreground focus-visible:ring-ring focus-visible:ring-offset-background relative inline-flex items-center rounded-sm outline-none before:absolute before:-inset-1.5 before:content-[''] focus-visible:ring-2 focus-visible:ring-offset-1\"\n onClick={(e) => {\n e.stopPropagation();\n toggle(opt.value);\n }}\n >\n <X className=\"size-3\" aria-hidden />\n </button>\n </span>\n ))}\n {overflow > 0 && (\n <span className=\"bg-background-muted text-foreground-muted shrink-0 rounded-md px-1.5 py-0.5 text-xs font-medium\">\n +{overflow}\n </span>\n )}\n <input\n ref={inputRef}\n id={fieldId}\n type=\"text\"\n autoComplete=\"off\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n onFocus={() => setOpen(true)}\n onKeyDown={handleKeyDown}\n placeholder={visibleChips.length === 0 ? placeholder : undefined}\n role=\"combobox\"\n aria-expanded={open}\n aria-controls={open ? listId : undefined}\n aria-haspopup=\"listbox\"\n aria-autocomplete=\"list\"\n // Unlabelled field: name the input after the placeholder. A\n // consumer `id` implies an external <label htmlFor>.\n aria-labelledby={label ? `${fieldId}-label` : undefined}\n aria-label={label || id ? undefined : placeholder}\n aria-invalid={isError || undefined}\n aria-describedby={describedBy}\n className=\"placeholder:text-foreground-subtle min-w-[6ch] flex-1 bg-transparent text-lg outline-none md:text-sm\"\n disabled={disabled}\n />\n </div>\n <span className=\"flex shrink-0 items-center gap-1 pl-1\">\n {clearable && selected.length > 0 && (\n <button\n type=\"button\"\n aria-label={strings.multiSelect.clearAll}\n onClick={(e) => {\n e.stopPropagation();\n clear();\n }}\n className=\"text-foreground-subtle hover:text-foreground focus-visible:ring-ring focus-visible:ring-offset-background inline-flex size-6 shrink-0 items-center justify-center rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-offset-2\"\n >\n <X className=\"size-4\" aria-hidden />\n </button>\n )}\n <ChevronsUpDown className=\"text-foreground-subtle size-4\" aria-hidden />\n </span>\n </div>\n </PopoverPrimitive.Anchor>\n\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n align=\"start\"\n sideOffset={4}\n // Radix gives the content role=\"dialog\"; a dialog needs a name.\n aria-label={strings.command.suggestions}\n className={cn(\n 'border-border bg-popover text-popover-foreground z-[var(--z-popover)] w-[var(--radix-popover-trigger-width)] overflow-hidden rounded-lg border shadow-md',\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n )}\n onOpenAutoFocus={(e) => e.preventDefault()}\n onInteractOutside={(e) => {\n // Clicks inside the anchor (chips, clear) must not close the menu.\n const target = e.target as Node | null;\n if (target && fieldRef.current?.contains(target)) e.preventDefault();\n }}\n >\n <CommandPrimitive.List\n ref={listRef}\n label={strings.command.suggestions}\n className=\"max-h-64 overflow-y-auto p-1\"\n >\n {filtered.map((opt) => {\n const isSelected = value.includes(opt.value);\n return (\n <CommandPrimitive.Item\n key={opt.value}\n value={opt.value}\n disabled={opt.disabled}\n onSelect={() => toggle(opt.value)}\n className={cn(\n 'text-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm',\n 'data-[selected=true]:bg-background-muted',\n 'data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50',\n )}\n >\n <span\n aria-hidden\n className={cn(\n 'flex size-4 items-center justify-center rounded-sm border',\n isSelected\n ? 'border-accent bg-accent text-on-accent'\n : 'border-border-input',\n )}\n >\n {isSelected && <Check className=\"size-3\" aria-hidden />}\n </span>\n <span className=\"flex-1\">{opt.label}</span>\n {opt.description && (\n <span className=\"text-foreground-subtle text-xs\">{opt.description}</span>\n )}\n </CommandPrimitive.Item>\n );\n })}\n {filtered.length === 0 && (\n <CommandPrimitive.Empty className=\"text-foreground-subtle px-2 py-6 text-center text-sm\">\n {emptyText}\n </CommandPrimitive.Empty>\n )}\n </CommandPrimitive.List>\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n </PopoverPrimitive.Root>\n </CommandPrimitive>\n\n {error ? (\n <p id={errorId} className=\"text-danger-text text-xs\">\n {error}\n </p>\n ) : helperText ? (\n <p id={helperId} className=\"text-foreground-subtle text-xs\">\n {helperText}\n </p>\n ) : null}\n </div>\n );\n});\n\nMultiSelect.displayName = 'MultiSelect';\n"],"names":["MultiSelect","forwardRef","options","value","onChange","label","helperText","error","placeholderProp","emptyTextProp","maxVisibleChips","clearable","disabled","id","className","ref","strings","useStrings","placeholder","emptyText","isError","fieldId","helperId","errorId","describedBy","useFieldIds","open","setOpen","useState","query","setQuery","inputRef","useRef","fieldRef","listId","setListId","listRef","useCallback","el","selected","useMemo","o","toggle","v","x","clear","handleKeyDown","filtered","visibleChips","overflow","cn","jsx","CommandPrimitive","jsxs","PopoverPrimitive","_a","opt","formatString","e","X","ChevronsUpDown","target","isSelected","Check"],"mappings":"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"}
@@ -1,4 +1,25 @@
1
1
  import { HTMLAttributes } from 'react';
2
+ import { UiStrings } from '../../lib/strings.js';
3
+ export interface PaginationLabels {
4
+ /** Summary line. Receives the computed range. */
5
+ showing: (from: number, to: number, total: number) => string;
6
+ /** sr-only label for the page-size select. */
7
+ rowsPerPage: string;
8
+ first: string;
9
+ last: string;
10
+ prev: string;
11
+ next: string;
12
+ /** aria-label for a numbered page button. */
13
+ page: (n: number) => string;
14
+ /** Suffix in the page-size options, e.g. "20 / page". */
15
+ perPage: (n: number) => string;
16
+ /** aria-label of the nav landmark. */
17
+ nav: string;
18
+ }
19
+ /** Build the label set from a strings object (context → functions). */
20
+ export declare function paginationLabelsFromStrings(s: UiStrings['pagination']): PaginationLabels;
21
+ /** English defaults — derived from `defaultStrings.pagination`. */
22
+ export declare const defaultPaginationLabels: PaginationLabels;
2
23
  export interface PaginationProps extends HTMLAttributes<HTMLElement> {
3
24
  /** 1-indexed current page. */
4
25
  page: number;
@@ -16,6 +37,8 @@ export interface PaginationProps extends HTMLAttributes<HTMLElement> {
16
37
  onPageSizeChange?: (size: number) => void;
17
38
  /** Show first/last (« ») jump buttons. Default true. */
18
39
  showJump?: boolean;
40
+ /** Override any of the visible / accessible strings (i18n). */
41
+ labels?: Partial<PaginationLabels>;
19
42
  }
20
43
  /**
21
44
  * Numbered pagination with prev/next, first/last jumps, page-size selector,