@blurrow/ui 0.2.0 → 0.2.1

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 (271) hide show
  1. package/README.md +48 -36
  2. package/dist/Accordion.d.ts +17 -0
  3. package/dist/Accordion.js +138 -0
  4. package/dist/Accordion.js.map +1 -0
  5. package/dist/Alert.d.ts +13 -0
  6. package/dist/Alert.js +69 -0
  7. package/dist/Alert.js.map +1 -0
  8. package/dist/AuthorCard.d.ts +12 -0
  9. package/dist/AuthorCard.js +57 -0
  10. package/dist/AuthorCard.js.map +1 -0
  11. package/dist/Avatar.d.ts +21 -0
  12. package/dist/Avatar.js +96 -0
  13. package/dist/Avatar.js.map +1 -0
  14. package/dist/Badge.d.ts +12 -0
  15. package/dist/Badge.js +46 -0
  16. package/dist/Badge.js.map +1 -0
  17. package/dist/Banner.d.ts +15 -0
  18. package/dist/Banner.js +68 -0
  19. package/dist/Banner.js.map +1 -0
  20. package/dist/Breadcrumb.d.ts +13 -0
  21. package/dist/Breadcrumb.js +45 -0
  22. package/dist/Breadcrumb.js.map +1 -0
  23. package/dist/Button.d.ts +19 -0
  24. package/dist/Button.js +282 -0
  25. package/dist/Button.js.map +1 -0
  26. package/dist/ButtonGroup.d.ts +19 -0
  27. package/dist/ButtonGroup.js +62 -0
  28. package/dist/ButtonGroup.js.map +1 -0
  29. package/dist/Card.d.ts +40 -0
  30. package/dist/Card.js +175 -0
  31. package/dist/Card.js.map +1 -0
  32. package/dist/CardShell.d.ts +18 -0
  33. package/dist/CardShell.js +58 -0
  34. package/dist/CardShell.js.map +1 -0
  35. package/dist/Carousel.d.ts +22 -0
  36. package/dist/Carousel.js +247 -0
  37. package/dist/Carousel.js.map +1 -0
  38. package/dist/Checkbox.d.ts +15 -0
  39. package/dist/Checkbox.js +84 -0
  40. package/dist/Checkbox.js.map +1 -0
  41. package/dist/CloseButton.d.ts +12 -0
  42. package/dist/CloseButton.js +63 -0
  43. package/dist/CloseButton.js.map +1 -0
  44. package/dist/CodeBlock.d.ts +12 -0
  45. package/dist/CodeBlock.js +221 -0
  46. package/dist/CodeBlock.js.map +1 -0
  47. package/dist/ColorPicker.d.ts +24 -0
  48. package/dist/ColorPicker.js +366 -0
  49. package/dist/ColorPicker.js.map +1 -0
  50. package/dist/Combobox.d.ts +34 -0
  51. package/dist/Combobox.js +255 -0
  52. package/dist/Combobox.js.map +1 -0
  53. package/dist/CommandPalette.d.ts +25 -0
  54. package/dist/CommandPalette.js +304 -0
  55. package/dist/CommandPalette.js.map +1 -0
  56. package/dist/ContextMenu.d.ts +16 -0
  57. package/dist/ContextMenu.js +209 -0
  58. package/dist/ContextMenu.js.map +1 -0
  59. package/dist/DataList.d.ts +15 -0
  60. package/dist/DataList.js +42 -0
  61. package/dist/DataList.js.map +1 -0
  62. package/dist/DatePicker.d.ts +65 -0
  63. package/dist/DatePicker.js +391 -0
  64. package/dist/DatePicker.js.map +1 -0
  65. package/dist/DateRangePicker.d.ts +34 -0
  66. package/dist/DateRangePicker.js +180 -0
  67. package/dist/DateRangePicker.js.map +1 -0
  68. package/dist/Drawer.d.ts +17 -0
  69. package/dist/Drawer.js +132 -0
  70. package/dist/Drawer.js.map +1 -0
  71. package/dist/DropdownMenu.d.ts +23 -0
  72. package/dist/DropdownMenu.js +185 -0
  73. package/dist/DropdownMenu.js.map +1 -0
  74. package/dist/EmptyState.d.ts +13 -0
  75. package/dist/EmptyState.js +55 -0
  76. package/dist/EmptyState.js.map +1 -0
  77. package/dist/FieldMessage.d.ts +26 -0
  78. package/dist/FieldMessage.js +95 -0
  79. package/dist/FieldMessage.js.map +1 -0
  80. package/dist/FileUpload.d.ts +32 -0
  81. package/dist/FileUpload.js +253 -0
  82. package/dist/FileUpload.js.map +1 -0
  83. package/dist/Floating.d.ts +32 -0
  84. package/dist/Floating.js +95 -0
  85. package/dist/Floating.js.map +1 -0
  86. package/dist/HoverCard.d.ts +18 -0
  87. package/dist/HoverCard.js +90 -0
  88. package/dist/HoverCard.js.map +1 -0
  89. package/dist/Input.d.ts +17 -0
  90. package/dist/Input.js +96 -0
  91. package/dist/Input.js.map +1 -0
  92. package/dist/Kbd.d.ts +9 -0
  93. package/dist/Kbd.js +24 -0
  94. package/dist/Kbd.js.map +1 -0
  95. package/dist/Modal.d.ts +15 -0
  96. package/dist/Modal.js +109 -0
  97. package/dist/Modal.js.map +1 -0
  98. package/dist/NumberInput.d.ts +23 -0
  99. package/dist/NumberInput.js +162 -0
  100. package/dist/NumberInput.js.map +1 -0
  101. package/dist/OtpInput.d.ts +26 -0
  102. package/dist/OtpInput.js +173 -0
  103. package/dist/OtpInput.js.map +1 -0
  104. package/dist/Pagination.d.ts +11 -0
  105. package/dist/Pagination.js +148 -0
  106. package/dist/Pagination.js.map +1 -0
  107. package/dist/Popover.d.ts +13 -0
  108. package/dist/Popover.js +98 -0
  109. package/dist/Popover.js.map +1 -0
  110. package/dist/Progress.d.ts +12 -0
  111. package/dist/Progress.js +61 -0
  112. package/dist/Progress.js.map +1 -0
  113. package/dist/QuoteCard.d.ts +13 -0
  114. package/dist/QuoteCard.js +92 -0
  115. package/dist/QuoteCard.js.map +1 -0
  116. package/dist/Radio.d.ts +19 -0
  117. package/dist/Radio.js +161 -0
  118. package/dist/Radio.js.map +1 -0
  119. package/dist/RadioCards.d.ts +27 -0
  120. package/dist/RadioCards.js +205 -0
  121. package/dist/RadioCards.js.map +1 -0
  122. package/dist/ReactionBar.d.ts +15 -0
  123. package/dist/ReactionBar.js +92 -0
  124. package/dist/ReactionBar.js.map +1 -0
  125. package/dist/ReadingProgress.d.ts +8 -0
  126. package/dist/ReadingProgress.js +51 -0
  127. package/dist/ReadingProgress.js.map +1 -0
  128. package/dist/ReadingTime.d.ts +9 -0
  129. package/dist/ReadingTime.js +39 -0
  130. package/dist/ReadingTime.js.map +1 -0
  131. package/dist/Select.d.ts +20 -0
  132. package/dist/Select.js +244 -0
  133. package/dist/Select.js.map +1 -0
  134. package/dist/Separator.d.ts +14 -0
  135. package/dist/Separator.js +33 -0
  136. package/dist/Separator.js.map +1 -0
  137. package/dist/ShareButtons.d.ts +10 -0
  138. package/dist/ShareButtons.js +107 -0
  139. package/dist/ShareButtons.js.map +1 -0
  140. package/dist/Sidebar.d.ts +30 -0
  141. package/dist/Sidebar.js +140 -0
  142. package/dist/Sidebar.js.map +1 -0
  143. package/dist/Skeleton.d.ts +15 -0
  144. package/dist/Skeleton.js +67 -0
  145. package/dist/Skeleton.js.map +1 -0
  146. package/dist/Slider.d.ts +27 -0
  147. package/dist/Slider.js +182 -0
  148. package/dist/Slider.js.map +1 -0
  149. package/dist/Spinner.d.ts +12 -0
  150. package/dist/Spinner.js +24 -0
  151. package/dist/Spinner.js.map +1 -0
  152. package/dist/SpotlightCard.d.ts +17 -0
  153. package/dist/SpotlightCard.js +87 -0
  154. package/dist/SpotlightCard.js.map +1 -0
  155. package/dist/Stat.d.ts +19 -0
  156. package/dist/Stat.js +71 -0
  157. package/dist/Stat.js.map +1 -0
  158. package/dist/StatCard.d.ts +24 -0
  159. package/dist/StatCard.js +212 -0
  160. package/dist/StatCard.js.map +1 -0
  161. package/dist/Steps.d.ts +14 -0
  162. package/dist/Steps.js +94 -0
  163. package/dist/Steps.js.map +1 -0
  164. package/dist/Table.d.ts +21 -0
  165. package/dist/Table.js +93 -0
  166. package/dist/Table.js.map +1 -0
  167. package/dist/TableOfContents.d.ts +17 -0
  168. package/dist/TableOfContents.js +92 -0
  169. package/dist/TableOfContents.js.map +1 -0
  170. package/dist/Tabs.d.ts +17 -0
  171. package/dist/Tabs.js +123 -0
  172. package/dist/Tabs.js.map +1 -0
  173. package/dist/TagInput.d.ts +16 -0
  174. package/dist/TagInput.js +169 -0
  175. package/dist/TagInput.js.map +1 -0
  176. package/dist/Textarea.d.ts +13 -0
  177. package/dist/Textarea.js +74 -0
  178. package/dist/Textarea.js.map +1 -0
  179. package/dist/TimePicker.d.ts +28 -0
  180. package/dist/TimePicker.js +264 -0
  181. package/dist/TimePicker.js.map +1 -0
  182. package/dist/Timeline.d.ts +19 -0
  183. package/dist/Timeline.js +76 -0
  184. package/dist/Timeline.js.map +1 -0
  185. package/dist/Toast.d.ts +18 -0
  186. package/dist/Toast.js +318 -0
  187. package/dist/Toast.js.map +1 -0
  188. package/dist/Toggle.d.ts +13 -0
  189. package/dist/Toggle.js +84 -0
  190. package/dist/Toggle.js.map +1 -0
  191. package/dist/Tooltip.d.ts +13 -0
  192. package/dist/Tooltip.js +138 -0
  193. package/dist/Tooltip.js.map +1 -0
  194. package/dist/cli.js +172 -5
  195. package/dist/index.d.ts +68 -631
  196. package/dist/index.js +67 -5558
  197. package/dist/index.js.map +1 -1
  198. package/dist/registry/Accordion.tsx +6 -6
  199. package/dist/registry/Alert.tsx +13 -10
  200. package/dist/registry/AuthorCard.tsx +7 -7
  201. package/dist/registry/Avatar.tsx +5 -5
  202. package/dist/registry/Badge.tsx +12 -15
  203. package/dist/registry/Banner.tsx +15 -18
  204. package/dist/registry/Breadcrumb.tsx +3 -3
  205. package/dist/registry/Button.tsx +58 -69
  206. package/dist/registry/ButtonGroup.tsx +9 -9
  207. package/dist/registry/Card.tsx +15 -15
  208. package/dist/registry/CardShell.tsx +6 -6
  209. package/dist/registry/Carousel.tsx +19 -15
  210. package/dist/registry/Checkbox.tsx +14 -9
  211. package/dist/registry/CloseButton.tsx +2 -2
  212. package/dist/registry/CodeBlock.tsx +15 -15
  213. package/dist/registry/ColorPicker.tsx +404 -0
  214. package/dist/registry/Combobox.tsx +318 -0
  215. package/dist/registry/CommandPalette.tsx +31 -24
  216. package/dist/registry/ContextMenu.tsx +241 -0
  217. package/dist/registry/DataList.tsx +4 -4
  218. package/dist/registry/DatePicker.tsx +494 -0
  219. package/dist/registry/DateRangePicker.tsx +232 -0
  220. package/dist/registry/Drawer.tsx +17 -15
  221. package/dist/registry/DropdownMenu.tsx +39 -77
  222. package/dist/registry/EmptyState.tsx +8 -8
  223. package/dist/registry/FieldMessage.tsx +123 -0
  224. package/dist/registry/FileUpload.tsx +318 -0
  225. package/dist/registry/Floating.tsx +134 -0
  226. package/dist/registry/HoverCard.tsx +113 -0
  227. package/dist/registry/Input.tsx +54 -43
  228. package/dist/registry/Kbd.tsx +4 -5
  229. package/dist/registry/Modal.tsx +17 -15
  230. package/dist/registry/NumberInput.tsx +183 -0
  231. package/dist/registry/OtpInput.tsx +219 -0
  232. package/dist/registry/Pagination.tsx +15 -18
  233. package/dist/registry/Popover.tsx +40 -45
  234. package/dist/registry/Progress.tsx +4 -4
  235. package/dist/registry/QuoteCard.tsx +7 -7
  236. package/dist/registry/Radio.tsx +24 -21
  237. package/dist/registry/RadioCards.tsx +247 -0
  238. package/dist/registry/ReactionBar.tsx +5 -5
  239. package/dist/registry/ReadingProgress.tsx +2 -2
  240. package/dist/registry/ReadingTime.tsx +2 -2
  241. package/dist/registry/Select.tsx +28 -53
  242. package/dist/registry/Separator.tsx +52 -0
  243. package/dist/registry/ShareButtons.tsx +2 -2
  244. package/dist/registry/Sidebar.tsx +18 -18
  245. package/dist/registry/Skeleton.tsx +1 -1
  246. package/dist/registry/Slider.tsx +229 -0
  247. package/dist/registry/Spinner.tsx +32 -0
  248. package/dist/registry/SpotlightCard.tsx +6 -6
  249. package/dist/registry/Stat.tsx +4 -4
  250. package/dist/registry/StatCard.tsx +19 -19
  251. package/dist/registry/Steps.tsx +7 -7
  252. package/dist/registry/Table.tsx +12 -10
  253. package/dist/registry/TableOfContents.tsx +8 -8
  254. package/dist/registry/Tabs.tsx +6 -6
  255. package/dist/registry/TagInput.tsx +9 -9
  256. package/dist/registry/Textarea.tsx +5 -5
  257. package/dist/registry/TimePicker.tsx +315 -0
  258. package/dist/registry/Timeline.tsx +14 -14
  259. package/dist/registry/Toast.tsx +15 -12
  260. package/dist/registry/Toggle.tsx +7 -10
  261. package/dist/registry/Tooltip.tsx +21 -73
  262. package/dist/registry/styles.css +868 -62
  263. package/dist/registry/useFloatingPanel.ts +112 -0
  264. package/dist/styles.css +868 -62
  265. package/dist/useFloatingPanel.d.ts +24 -0
  266. package/dist/useFloatingPanel.js +89 -0
  267. package/dist/useFloatingPanel.js.map +1 -0
  268. package/dist/useModalBehavior.d.ts +15 -0
  269. package/dist/useModalBehavior.js +57 -0
  270. package/dist/useModalBehavior.js.map +1 -0
  271. package/package.json +1 -1
@@ -0,0 +1,169 @@
1
+ "use client";
2
+
3
+ // ../../components/ui/TagInput.tsx
4
+ import { useState, useRef, useEffect, useId } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ function TagInput({
7
+ value,
8
+ defaultValue = [],
9
+ onChange,
10
+ placeholder = "add a tag\u2026",
11
+ label,
12
+ hint,
13
+ maxTags,
14
+ fullWidth = false,
15
+ className = ""
16
+ }) {
17
+ const [internal, setInternal] = useState(defaultValue);
18
+ const [input, setInput] = useState("");
19
+ const [animTags, setAnimTags] = useState(
20
+ () => (value ?? defaultValue).map((t) => ({ text: t, state: "visible" }))
21
+ );
22
+ const inputRef = useRef(null);
23
+ const uid = useId();
24
+ const prevValueRef = useRef(value ?? defaultValue);
25
+ const tags = value !== void 0 ? value : internal;
26
+ useEffect(() => {
27
+ const prev = prevValueRef.current;
28
+ const next = tags;
29
+ const added = next.filter((t) => !prev.includes(t));
30
+ const removed = prev.filter((t) => !next.includes(t));
31
+ if (added.length === 0 && removed.length === 0) return;
32
+ setAnimTags((current) => {
33
+ const updated = current.map(
34
+ (at) => removed.includes(at.text) ? { ...at, state: "exiting" } : at
35
+ );
36
+ added.forEach((t) => {
37
+ if (!updated.find((at) => at.text === t)) {
38
+ updated.push({ text: t, state: "entering" });
39
+ }
40
+ });
41
+ return updated;
42
+ });
43
+ if (added.length > 0) {
44
+ requestAnimationFrame(() => {
45
+ setAnimTags(
46
+ (current) => current.map(
47
+ (at) => at.state === "entering" ? { ...at, state: "visible" } : at
48
+ )
49
+ );
50
+ });
51
+ }
52
+ if (removed.length > 0) {
53
+ setTimeout(() => {
54
+ setAnimTags((current) => current.filter((at) => at.state !== "exiting"));
55
+ }, 180);
56
+ }
57
+ prevValueRef.current = next;
58
+ }, [tags]);
59
+ const update = (next) => {
60
+ setInternal(next);
61
+ onChange?.(next);
62
+ };
63
+ const addTag = (raw) => {
64
+ const tag = raw.trim();
65
+ if (!tag || tags.includes(tag)) return;
66
+ if (maxTags && tags.length >= maxTags) return;
67
+ update([...tags, tag]);
68
+ setInput("");
69
+ };
70
+ const removeTag = (index) => {
71
+ const tagText = tags[index];
72
+ if (!tagText) return;
73
+ setAnimTags(
74
+ (current) => current.map(
75
+ (at) => at.text === tagText ? { ...at, state: "exiting" } : at
76
+ )
77
+ );
78
+ setTimeout(() => {
79
+ update(tags.filter((_, i) => i !== index));
80
+ }, 180);
81
+ };
82
+ const handleKeyDown = (e) => {
83
+ if (e.key === "Enter" || e.key === ",") {
84
+ e.preventDefault();
85
+ addTag(input);
86
+ }
87
+ if (e.key === "Backspace" && !input && tags.length > 0) {
88
+ removeTag(tags.length - 1);
89
+ }
90
+ };
91
+ return /* @__PURE__ */ jsxs("div", { className: ["flex flex-col gap-1.5", fullWidth ? "w-full" : ""].join(" "), children: [
92
+ label && /* @__PURE__ */ jsx("label", { htmlFor: `${uid}-input`, className: "text-[12.5px] font-medium text-ink-label tracking-[-0.005em]", children: label }),
93
+ /* @__PURE__ */ jsxs(
94
+ "div",
95
+ {
96
+ onClick: () => inputRef.current?.focus(),
97
+ className: [
98
+ "flex flex-wrap items-center gap-1.5 rounded-xl px-3 py-2.5",
99
+ "transition-[box-shadow,background] duration-200 ease-out cursor-text",
100
+ "[background:var(--field)] [box-shadow:var(--field-shadow)]",
101
+ "hover:[background:var(--field-hover)] hover:[box-shadow:var(--field-shadow-hover)]",
102
+ "focus-within:[background:var(--field-focus)] focus-within:[box-shadow:var(--field-shadow-focus)]",
103
+ fullWidth ? "w-full" : "",
104
+ className
105
+ ].filter(Boolean).join(" "),
106
+ children: [
107
+ animTags.map((at) => /* @__PURE__ */ jsxs(
108
+ "span",
109
+ {
110
+ className: [
111
+ "inline-flex items-center gap-1 pl-2 pr-1 py-0.5 rounded-[7px] text-[11.5px] font-medium text-ink-label select-none",
112
+ "transition-all duration-180 ease-[cubic-bezier(0.34,1.4,0.64,1)]",
113
+ at.state === "entering" ? "opacity-0 scale-75" : "",
114
+ at.state === "visible" ? "opacity-100 scale-100" : "",
115
+ at.state === "exiting" ? "opacity-0 scale-75" : ""
116
+ ].join(" "),
117
+ style: {
118
+ /* Tags are small raised faces sitting in the sunken field */
119
+ background: "var(--face)",
120
+ boxShadow: "var(--face-shadow)"
121
+ },
122
+ children: [
123
+ at.text,
124
+ /* @__PURE__ */ jsx(
125
+ "button",
126
+ {
127
+ type: "button",
128
+ onClick: (e) => {
129
+ e.stopPropagation();
130
+ const idx = tags.indexOf(at.text);
131
+ if (idx !== -1) removeTag(idx);
132
+ },
133
+ "aria-label": `Remove ${at.text}`,
134
+ className: "w-4 h-4 inline-flex items-center justify-center rounded-[5px] text-ink-faint hover:text-ink hover:bg-ink/[0.06] cursor-pointer transition-colors",
135
+ children: /* @__PURE__ */ jsx("svg", { width: "8", height: "8", fill: "none", stroke: "currentColor", strokeWidth: "2.5", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) })
136
+ }
137
+ )
138
+ ]
139
+ },
140
+ at.text
141
+ )),
142
+ /* @__PURE__ */ jsx(
143
+ "input",
144
+ {
145
+ ref: inputRef,
146
+ id: `${uid}-input`,
147
+ type: "text",
148
+ value: input,
149
+ onChange: (e) => setInput(e.target.value),
150
+ onKeyDown: handleKeyDown,
151
+ onBlur: () => {
152
+ if (input) addTag(input);
153
+ },
154
+ placeholder: tags.length === 0 ? placeholder : "",
155
+ "aria-label": label ? void 0 : placeholder,
156
+ "aria-describedby": hint ? `${uid}-hint` : void 0,
157
+ className: "flex-1 min-w-[80px] bg-transparent text-[13.5px] text-ink placeholder:text-ink-faint outline-none py-0.5"
158
+ }
159
+ )
160
+ ]
161
+ }
162
+ ),
163
+ hint && /* @__PURE__ */ jsx("p", { id: `${uid}-hint`, className: "text-[11.5px] text-ink-muted mt-0.5", children: hint })
164
+ ] });
165
+ }
166
+ export {
167
+ TagInput
168
+ };
169
+ //# sourceMappingURL=TagInput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../components/ui/TagInput.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useState, useRef, useEffect, useId } from \"react\";\n\ninterface TagInputProps {\n value?: string[];\n defaultValue?: string[];\n onChange?: (tags: string[]) => void;\n placeholder?: string;\n label?: string;\n hint?: string;\n maxTags?: number;\n fullWidth?: boolean;\n className?: string;\n}\n\ninterface AnimatedTag {\n text: string;\n state: \"entering\" | \"visible\" | \"exiting\";\n}\n\nexport function TagInput({\n value,\n defaultValue = [],\n onChange,\n placeholder = \"add a tag…\",\n label,\n hint,\n maxTags,\n fullWidth = false,\n className = \"\",\n}: TagInputProps) {\n const [internal, setInternal] = useState<string[]>(defaultValue);\n const [input, setInput] = useState(\"\");\n const [animTags, setAnimTags] = useState<AnimatedTag[]>(() =>\n (value ?? defaultValue).map((t) => ({ text: t, state: \"visible\" as const }))\n );\n const inputRef = useRef<HTMLInputElement>(null);\n const uid = useId();\n const prevValueRef = useRef<string[]>(value ?? defaultValue);\n\n const tags = value !== undefined ? value : internal;\n\n useEffect(() => {\n const prev = prevValueRef.current;\n const next = tags;\n\n const added = next.filter((t) => !prev.includes(t));\n const removed = prev.filter((t) => !next.includes(t));\n\n if (added.length === 0 && removed.length === 0) return;\n\n setAnimTags((current) => {\n const updated = current.map((at) =>\n removed.includes(at.text) ? { ...at, state: \"exiting\" as const } : at\n );\n added.forEach((t) => {\n if (!updated.find((at) => at.text === t)) {\n updated.push({ text: t, state: \"entering\" });\n }\n });\n return updated;\n });\n\n if (added.length > 0) {\n requestAnimationFrame(() => {\n setAnimTags((current) =>\n current.map((at) =>\n at.state === \"entering\" ? { ...at, state: \"visible\" } : at\n )\n );\n });\n }\n\n if (removed.length > 0) {\n setTimeout(() => {\n setAnimTags((current) => current.filter((at) => at.state !== \"exiting\"));\n }, 180);\n }\n\n prevValueRef.current = next;\n }, [tags]);\n\n const update = (next: string[]) => {\n setInternal(next);\n onChange?.(next);\n };\n\n const addTag = (raw: string) => {\n const tag = raw.trim();\n if (!tag || tags.includes(tag)) return;\n if (maxTags && tags.length >= maxTags) return;\n update([...tags, tag]);\n setInput(\"\");\n };\n\n const removeTag = (index: number) => {\n const tagText = tags[index];\n if (!tagText) return;\n setAnimTags((current) =>\n current.map((at) =>\n at.text === tagText ? { ...at, state: \"exiting\" as const } : at\n )\n );\n setTimeout(() => {\n update(tags.filter((_, i) => i !== index));\n }, 180);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Enter\" || e.key === \",\") {\n e.preventDefault();\n addTag(input);\n }\n if (e.key === \"Backspace\" && !input && tags.length > 0) {\n removeTag(tags.length - 1);\n }\n };\n\n return (\n <div className={[\"flex flex-col gap-1.5\", fullWidth ? \"w-full\" : \"\"].join(\" \")}>\n {label && (\n <label htmlFor={`${uid}-input`} className=\"text-[12.5px] font-medium text-ink-label tracking-[-0.005em]\">\n {label}\n </label>\n )}\n <div\n onClick={() => inputRef.current?.focus()}\n className={[\n \"flex flex-wrap items-center gap-1.5 rounded-xl px-3 py-2.5\",\n \"transition-[box-shadow,background] duration-200 ease-out cursor-text\",\n \"[background:var(--field)] [box-shadow:var(--field-shadow)]\",\n \"hover:[background:var(--field-hover)] hover:[box-shadow:var(--field-shadow-hover)]\",\n \"focus-within:[background:var(--field-focus)] focus-within:[box-shadow:var(--field-shadow-focus)]\",\n fullWidth ? \"w-full\" : \"\",\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n >\n {animTags.map((at) => (\n <span\n key={at.text}\n className={[\n \"inline-flex items-center gap-1 pl-2 pr-1 py-0.5 rounded-[7px] text-[11.5px] font-medium text-ink-label select-none\",\n \"transition-all duration-180 ease-[cubic-bezier(0.34,1.4,0.64,1)]\",\n at.state === \"entering\" ? \"opacity-0 scale-75\" : \"\",\n at.state === \"visible\" ? \"opacity-100 scale-100\" : \"\",\n at.state === \"exiting\" ? \"opacity-0 scale-75\" : \"\",\n ].join(\" \")}\n style={{\n /* Tags are small raised faces sitting in the sunken field */\n background: \"var(--face)\",\n boxShadow: \"var(--face-shadow)\",\n }}\n >\n {at.text}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n const idx = tags.indexOf(at.text);\n if (idx !== -1) removeTag(idx);\n }}\n aria-label={`Remove ${at.text}`}\n className=\"w-4 h-4 inline-flex items-center justify-center rounded-[5px] text-ink-faint hover:text-ink hover:bg-ink/[0.06] cursor-pointer transition-colors\"\n >\n <svg width=\"8\" height=\"8\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </span>\n ))}\n <input\n ref={inputRef}\n id={`${uid}-input`}\n type=\"text\"\n value={input}\n onChange={(e) => setInput(e.target.value)}\n onKeyDown={handleKeyDown}\n onBlur={() => { if (input) addTag(input); }}\n placeholder={tags.length === 0 ? placeholder : \"\"}\n aria-label={label ? undefined : placeholder}\n aria-describedby={hint ? `${uid}-hint` : undefined}\n className=\"flex-1 min-w-[80px] bg-transparent text-[13.5px] text-ink placeholder:text-ink-faint outline-none py-0.5\"\n />\n </div>\n {hint && <p id={`${uid}-hint`} className=\"text-[11.5px] text-ink-muted mt-0.5\">{hint}</p>}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,13 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+
4
+ interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
5
+ label?: string;
6
+ hint?: string;
7
+ error?: string;
8
+ fullWidth?: boolean;
9
+ autoResize?: boolean;
10
+ }
11
+ declare function Textarea({ label, hint, error, fullWidth, autoResize, className, id, rows, onChange, ...rest }: TextareaProps): react_jsx_runtime.JSX.Element;
12
+
13
+ export { Textarea };
@@ -0,0 +1,74 @@
1
+ "use client";
2
+
3
+ // ../../components/ui/Textarea.tsx
4
+ import { useRef, useCallback, useEffect } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ function Textarea({
7
+ label,
8
+ hint,
9
+ error,
10
+ fullWidth = false,
11
+ autoResize = false,
12
+ className = "",
13
+ id,
14
+ rows = 3,
15
+ onChange,
16
+ ...rest
17
+ }) {
18
+ const textareaId = id ?? label?.toLowerCase().replace(/\s+/g, "-");
19
+ const messageId = textareaId && (error || hint) ? `${textareaId}-message` : void 0;
20
+ const ref = useRef(null);
21
+ const resize = useCallback(() => {
22
+ if (!autoResize || !ref.current) return;
23
+ ref.current.style.height = "auto";
24
+ ref.current.style.height = ref.current.scrollHeight + "px";
25
+ }, [autoResize]);
26
+ useEffect(() => {
27
+ resize();
28
+ }, [resize]);
29
+ const handleChange = (e) => {
30
+ resize();
31
+ onChange?.(e);
32
+ };
33
+ return /* @__PURE__ */ jsxs("div", { className: ["group/field flex flex-col gap-1.5", fullWidth ? "w-full" : ""].join(" "), children: [
34
+ label && /* @__PURE__ */ jsx(
35
+ "label",
36
+ {
37
+ htmlFor: textareaId,
38
+ className: "text-[12.5px] font-medium text-ink-label tracking-[-0.005em] transition-colors duration-200 group-focus-within/field:text-ink-strong",
39
+ children: label
40
+ }
41
+ ),
42
+ /* @__PURE__ */ jsx(
43
+ "textarea",
44
+ {
45
+ ref,
46
+ id: textareaId,
47
+ rows,
48
+ onChange: handleChange,
49
+ className: [
50
+ "field rounded-xl px-4 py-3 text-[13.5px] text-ink resize-none scroll-thin",
51
+ "placeholder:text-ink-faint placeholder:transition-colors placeholder:duration-200 focus:placeholder:text-ink-ghost",
52
+ "disabled:opacity-40 disabled:cursor-not-allowed",
53
+ fullWidth ? "w-full" : "",
54
+ className
55
+ ].filter(Boolean).join(" "),
56
+ "aria-invalid": error ? true : void 0,
57
+ "aria-describedby": messageId,
58
+ ...rest
59
+ }
60
+ ),
61
+ error && /* @__PURE__ */ jsxs("p", { id: messageId, role: "alert", className: "text-[11.5px] text-danger font-medium flex items-center gap-1 mt-0.5", children: [
62
+ /* @__PURE__ */ jsxs("svg", { width: "11", height: "11", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", children: [
63
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
64
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", d: "M12 8v4m0 4h.01" })
65
+ ] }),
66
+ error
67
+ ] }),
68
+ hint && !error && /* @__PURE__ */ jsx("p", { id: messageId, className: "text-[11.5px] text-ink-muted mt-0.5", children: hint })
69
+ ] });
70
+ }
71
+ export {
72
+ Textarea
73
+ };
74
+ //# sourceMappingURL=Textarea.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../components/ui/Textarea.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useRef, useCallback, useEffect } from \"react\";\n\ninterface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {\n label?: string;\n hint?: string;\n error?: string;\n fullWidth?: boolean;\n autoResize?: boolean;\n}\n\nexport function Textarea({\n label,\n hint,\n error,\n fullWidth = false,\n autoResize = false,\n className = \"\",\n id,\n rows = 3,\n onChange,\n ...rest\n}: TextareaProps) {\n const textareaId = id ?? label?.toLowerCase().replace(/\\s+/g, \"-\");\n const messageId = textareaId && (error || hint) ? `${textareaId}-message` : undefined;\n const ref = useRef<HTMLTextAreaElement>(null);\n\n const resize = useCallback(() => {\n if (!autoResize || !ref.current) return;\n ref.current.style.height = \"auto\";\n ref.current.style.height = ref.current.scrollHeight + \"px\";\n }, [autoResize]);\n\n useEffect(() => { resize(); }, [resize]);\n\n const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n resize();\n onChange?.(e);\n };\n\n return (\n <div className={[\"group/field flex flex-col gap-1.5\", fullWidth ? \"w-full\" : \"\"].join(\" \")}>\n {label && (\n <label\n htmlFor={textareaId}\n className=\"text-[12.5px] font-medium text-ink-label tracking-[-0.005em] transition-colors duration-200 group-focus-within/field:text-ink-strong\"\n >\n {label}\n </label>\n )}\n <textarea\n ref={ref}\n id={textareaId}\n rows={rows}\n onChange={handleChange}\n className={[\n \"field rounded-xl px-4 py-3 text-[13.5px] text-ink resize-none scroll-thin\",\n \"placeholder:text-ink-faint placeholder:transition-colors placeholder:duration-200 focus:placeholder:text-ink-ghost\",\n \"disabled:opacity-40 disabled:cursor-not-allowed\",\n fullWidth ? \"w-full\" : \"\",\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n aria-invalid={error ? true : undefined}\n aria-describedby={messageId}\n {...rest}\n />\n {error && (\n <p id={messageId} role=\"alert\" className=\"text-[11.5px] text-danger font-medium flex items-center gap-1 mt-0.5\">\n <svg width=\"11\" height=\"11\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path strokeLinecap=\"round\" d=\"M12 8v4m0 4h.01\" />\n </svg>\n {error}\n </p>\n )}\n {hint && !error && (\n <p id={messageId} className=\"text-[11.5px] text-ink-muted mt-0.5\">{hint}</p>\n )}\n </div>\n );\n}\n"],"mappings":";;;AAEA,SAAgB,QAAQ,aAAa,iBAAiB;AA0C9C,cA2BE,YA3BF;AAhCD,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,YAAY;AAAA,EACZ;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,QAAM,aAAa,MAAM,OAAO,YAAY,EAAE,QAAQ,QAAQ,GAAG;AACjE,QAAM,YAAY,eAAe,SAAS,QAAQ,GAAG,UAAU,aAAa;AAC5E,QAAM,MAAM,OAA4B,IAAI;AAE5C,QAAM,SAAS,YAAY,MAAM;AAC/B,QAAI,CAAC,cAAc,CAAC,IAAI,QAAS;AACjC,QAAI,QAAQ,MAAM,SAAS;AAC3B,QAAI,QAAQ,MAAM,SAAS,IAAI,QAAQ,eAAe;AAAA,EACxD,GAAG,CAAC,UAAU,CAAC;AAEf,YAAU,MAAM;AAAE,WAAO;AAAA,EAAG,GAAG,CAAC,MAAM,CAAC;AAEvC,QAAM,eAAe,CAAC,MAA8C;AAClE,WAAO;AACP,eAAW,CAAC;AAAA,EACd;AAEA,SACE,qBAAC,SAAI,WAAW,CAAC,qCAAqC,YAAY,WAAW,EAAE,EAAE,KAAK,GAAG,GACtF;AAAA,aACC;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,WAAU;AAAA,QAET;AAAA;AAAA,IACH;AAAA,IAEF;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,IAAI;AAAA,QACJ;AAAA,QACA,UAAU;AAAA,QACV,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA,YAAY,WAAW;AAAA,UACvB;AAAA,QACF,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAAA,QACX,gBAAc,QAAQ,OAAO;AAAA,QAC7B,oBAAkB;AAAA,QACjB,GAAG;AAAA;AAAA,IACN;AAAA,IACC,SACC,qBAAC,OAAE,IAAI,WAAW,MAAK,SAAQ,WAAU,wEACvC;AAAA,2BAAC,SAAI,OAAM,MAAK,QAAO,MAAK,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,SAAQ,aACpF;AAAA,4BAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,MAAK;AAAA,QAC/B,oBAAC,UAAK,eAAc,SAAQ,GAAE,mBAAkB;AAAA,SAClD;AAAA,MACC;AAAA,OACH;AAAA,IAED,QAAQ,CAAC,SACR,oBAAC,OAAE,IAAI,WAAW,WAAU,uCAAuC,gBAAK;AAAA,KAE5E;AAEJ;","names":[]}
@@ -0,0 +1,28 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface TimePickerProps {
4
+ /** 24-hour "HH:mm", like "14:30" */
5
+ value?: string | null;
6
+ defaultValue?: string | null;
7
+ onChange?: (time: string | null) => void;
8
+ /** Minutes between choices in the minute column */
9
+ step?: number;
10
+ /** Show 1–12 with am / pm instead of 0–23 */
11
+ hour12?: boolean;
12
+ /** Earliest and latest times, as "HH:mm" */
13
+ min?: string;
14
+ max?: string;
15
+ label?: string;
16
+ hint?: string;
17
+ error?: string;
18
+ placeholder?: string;
19
+ clearable?: boolean;
20
+ disabled?: boolean;
21
+ fullWidth?: boolean;
22
+ name?: string;
23
+ className?: string;
24
+ }
25
+ declare function formatTime(t: string, hour12: boolean): string;
26
+ declare function TimePicker({ value, defaultValue, onChange, step, hour12, min, max, label, hint, error, placeholder, clearable, disabled, fullWidth, name, className, }: TimePickerProps): react_jsx_runtime.JSX.Element;
27
+
28
+ export { TimePicker, formatTime };
@@ -0,0 +1,264 @@
1
+ "use client";
2
+
3
+ // ../../components/ui/TimePicker.tsx
4
+ import { useId, useRef, useState } from "react";
5
+ import { ClearKey } from "./DatePicker.js";
6
+ import { Portal } from "./Floating.js";
7
+ import { FieldLabel, FieldMessage } from "./FieldMessage.js";
8
+ import { useFloatingPanel } from "./useFloatingPanel.js";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ var pad = (n) => String(n).padStart(2, "0");
11
+ var toMinutes = (t) => {
12
+ if (!t) return null;
13
+ const m = /^(\d{1,2}):(\d{2})$/.exec(t);
14
+ return m ? Number(m[1]) * 60 + Number(m[2]) : null;
15
+ };
16
+ var fromMinutes = (m) => `${pad(Math.floor(m / 60))}:${pad(m % 60)}`;
17
+ function formatTime(t, hour12) {
18
+ const m = toMinutes(t);
19
+ if (m === null) return t;
20
+ const h = Math.floor(m / 60);
21
+ return hour12 ? `${h % 12 || 12}:${pad(m % 60)} ${h < 12 ? "am" : "pm"}` : fromMinutes(m);
22
+ }
23
+ function TimePicker({
24
+ value,
25
+ defaultValue = null,
26
+ onChange,
27
+ step = 15,
28
+ hour12 = false,
29
+ min,
30
+ max,
31
+ label,
32
+ hint,
33
+ error,
34
+ placeholder = "pick a time",
35
+ clearable = false,
36
+ disabled = false,
37
+ fullWidth = false,
38
+ name,
39
+ className = ""
40
+ }) {
41
+ const uid = useId();
42
+ const ref = useRef(null);
43
+ const buttonRef = useRef(null);
44
+ const [internal, setInternal] = useState(defaultValue);
45
+ const current = value !== void 0 ? value : internal;
46
+ const total = toMinutes(current);
47
+ const { open, mounted, toggle, close, setFloating, panelStyle } = useFloatingPanel(ref, {
48
+ onEscape: () => buttonRef.current?.focus()
49
+ });
50
+ const messageId = error || hint ? `${uid}-message` : void 0;
51
+ const lo = toMinutes(min) ?? 0;
52
+ const hi = toMinutes(max) ?? 24 * 60 - 1;
53
+ const hour = total === null ? null : Math.floor(total / 60);
54
+ const minute = total === null ? null : total % 60;
55
+ const set = (t) => {
56
+ setInternal(t);
57
+ onChange?.(t);
58
+ };
59
+ const choose = (h, m) => {
60
+ let t = h * 60 + m;
61
+ if (t < lo) t = lo;
62
+ if (t > hi) t = hi;
63
+ set(fromMinutes(t));
64
+ };
65
+ const hours = hour12 ? Array.from({ length: 12 }, (_, i) => i === 0 ? 12 : i) : Array.from({ length: 24 }, (_, i) => i);
66
+ const minutes = Array.from({ length: Math.ceil(60 / step) }, (_, i) => i * step).filter((m) => m < 60);
67
+ const isPm = hour !== null && hour >= 12;
68
+ const to24 = (h12, pm) => h12 % 12 + (pm ? 12 : 0);
69
+ const hourOff = (h24) => h24 * 60 + 59 < lo || h24 * 60 > hi;
70
+ const minuteOff = (m) => hour !== null && (hour * 60 + m < lo || hour * 60 + m > hi);
71
+ const columns = [
72
+ {
73
+ id: "hour",
74
+ label: "hour",
75
+ cells: hours.map((h) => {
76
+ const h24 = hour12 ? to24(h, isPm) : h;
77
+ return {
78
+ key: `h${h}`,
79
+ label: hour12 ? String(h) : pad(h),
80
+ selected: hour === h24,
81
+ off: hourOff(h24),
82
+ pick: () => choose(h24, minute ?? 0)
83
+ };
84
+ })
85
+ },
86
+ {
87
+ id: "minute",
88
+ label: "minute",
89
+ cells: minutes.map((m) => ({
90
+ key: `m${m}`,
91
+ label: pad(m),
92
+ selected: minute === m,
93
+ off: minuteOff(m),
94
+ pick: () => {
95
+ choose(hour ?? Math.floor(lo / 60), m);
96
+ close();
97
+ buttonRef.current?.focus();
98
+ }
99
+ }))
100
+ }
101
+ ];
102
+ if (hour12) {
103
+ columns.push({
104
+ id: "period",
105
+ label: "am or pm",
106
+ cells: ["am", "pm"].map((p) => {
107
+ const pm = p === "pm";
108
+ const h24 = to24(hour === null ? 12 : hour % 12 || 12, pm);
109
+ return {
110
+ key: p,
111
+ label: p,
112
+ selected: hour !== null && isPm === pm,
113
+ off: pm ? hi < 12 * 60 : lo >= 12 * 60,
114
+ pick: () => choose(h24, minute ?? 0)
115
+ };
116
+ })
117
+ });
118
+ }
119
+ const colRefs = useRef({});
120
+ const attachColumn = (id) => (el) => {
121
+ colRefs.current[id] = el;
122
+ const sel = el?.querySelector('[aria-selected="true"]');
123
+ if (!el || el.dataset.scrolled) return;
124
+ el.dataset.scrolled = "1";
125
+ if (sel) el.scrollTop = sel.offsetTop - el.clientHeight / 2 + sel.offsetHeight / 2;
126
+ if (id === "hour") el.focus({ preventScroll: true });
127
+ };
128
+ const onColumnKey = (ci) => (e) => {
129
+ const col = columns[ci];
130
+ const enabled = col.cells.map((c, i) => c.off ? -1 : i).filter((i) => i >= 0);
131
+ const at = col.cells.findIndex((c) => c.selected);
132
+ const pos = enabled.indexOf(at);
133
+ const focusCol = (n) => colRefs.current[columns[Math.max(0, Math.min(columns.length - 1, n))].id]?.focus();
134
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
135
+ e.preventDefault();
136
+ const d = e.key === "ArrowDown" ? 1 : -1;
137
+ const next = enabled[pos < 0 ? 0 : (pos + d + enabled.length) % enabled.length];
138
+ if (col.id === "minute") choose(hour ?? Math.floor(lo / 60), minutes[next]);
139
+ else col.cells[next]?.pick();
140
+ requestAnimationFrame(
141
+ () => colRefs.current[col.id]?.querySelector('[aria-selected="true"]')?.scrollIntoView({ block: "nearest" })
142
+ );
143
+ } else if (e.key === "ArrowRight") {
144
+ e.preventDefault();
145
+ focusCol(ci + 1);
146
+ } else if (e.key === "ArrowLeft") {
147
+ e.preventDefault();
148
+ focusCol(ci - 1);
149
+ } else if (e.key === "Enter") {
150
+ e.preventDefault();
151
+ close();
152
+ buttonRef.current?.focus();
153
+ }
154
+ };
155
+ return /* @__PURE__ */ jsxs("div", { className: ["group/field flex flex-col gap-1.5", fullWidth ? "w-full" : ""].join(" "), children: [
156
+ label && /* @__PURE__ */ jsx(FieldLabel, { id: `${uid}-label`, children: label }),
157
+ /* @__PURE__ */ jsxs("div", { ref, className: ["relative", fullWidth ? "w-full" : "inline-block w-44", className].join(" "), children: [
158
+ /* @__PURE__ */ jsxs(
159
+ "button",
160
+ {
161
+ ref: buttonRef,
162
+ type: "button",
163
+ disabled,
164
+ onClick: toggle,
165
+ "aria-haspopup": "dialog",
166
+ "aria-expanded": open,
167
+ "aria-labelledby": label ? `${uid}-label ${uid}-value` : void 0,
168
+ "aria-invalid": error ? true : void 0,
169
+ "aria-describedby": messageId,
170
+ "data-open": open || void 0,
171
+ className: [
172
+ "field group/time relative isolate flex w-full cursor-pointer items-center gap-2.5 rounded-xl py-3 pl-3.5 text-left text-[13.5px]",
173
+ clearable && current ? "pr-10" : "pr-4",
174
+ "focus:outline-none disabled:opacity-40 disabled:pointer-events-none"
175
+ ].join(" "),
176
+ children: [
177
+ /* @__PURE__ */ jsxs(
178
+ "svg",
179
+ {
180
+ "aria-hidden": true,
181
+ width: "15",
182
+ height: "15",
183
+ fill: "none",
184
+ stroke: "currentColor",
185
+ strokeWidth: "2",
186
+ strokeLinecap: "round",
187
+ viewBox: "0 0 24 24",
188
+ className: [
189
+ "shrink-0 transition-[color,transform] duration-300 ease-[var(--ease-spring)]",
190
+ open ? "scale-110 text-ink-soft" : "text-ink-faint group-hover/time:text-ink-body"
191
+ ].join(" "),
192
+ children: [
193
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
194
+ /* @__PURE__ */ jsx("path", { d: "M12 7v5l3 2" })
195
+ ]
196
+ }
197
+ ),
198
+ /* @__PURE__ */ jsx("span", { id: `${uid}-value`, className: ["tabular-nums", current ? "font-medium text-ink" : "text-ink-faint"].join(" "), children: current ? formatTime(current, hour12) : placeholder })
199
+ ]
200
+ }
201
+ ),
202
+ clearable && current && !disabled && /* @__PURE__ */ jsx(
203
+ ClearKey,
204
+ {
205
+ label: "clear time",
206
+ onClear: () => {
207
+ set(null);
208
+ buttonRef.current?.focus();
209
+ }
210
+ }
211
+ ),
212
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: current ?? "" })
213
+ ] }),
214
+ mounted && /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
215
+ "div",
216
+ {
217
+ ref: setFloating,
218
+ role: "dialog",
219
+ "aria-label": label ? `choose ${label}` : "choose a time",
220
+ className: "rounded-[16px] p-1.5",
221
+ style: panelStyle,
222
+ children: /* @__PURE__ */ jsx("div", { className: "flex gap-1 rounded-[11px] bg-panel-face p-1.5", style: { boxShadow: "var(--panel-face-shadow)" }, children: columns.map((col, ci) => /* @__PURE__ */ jsx(
223
+ "div",
224
+ {
225
+ ref: attachColumn(col.id),
226
+ role: "listbox",
227
+ "aria-label": col.label,
228
+ tabIndex: 0,
229
+ onKeyDown: onColumnKey(ci),
230
+ className: "flex max-h-56 w-14 flex-col gap-0.5 overflow-y-auto rounded-[9px] p-0.5 scroll-thin outline-none focus-visible:ring-2 focus-visible:ring-ink",
231
+ style: { scrollbarWidth: "none" },
232
+ children: col.cells.map((c) => /* @__PURE__ */ jsx(
233
+ "div",
234
+ {
235
+ role: "option",
236
+ "aria-selected": c.selected,
237
+ "aria-disabled": c.off || void 0,
238
+ onClick: () => !c.off && c.pick(),
239
+ className: [
240
+ "grid h-8 shrink-0 place-items-center rounded-[8px] text-[13px] tabular-nums transition-colors duration-100",
241
+ c.off ? "cursor-not-allowed text-ink-ghost" : c.selected ? "cursor-pointer font-semibold text-ink-inverse" : "cursor-pointer text-ink-body hover:bg-surface-active hover:text-ink"
242
+ ].join(" "),
243
+ style: c.selected && !c.off ? {
244
+ background: "var(--face-inverse)",
245
+ boxShadow: "var(--face-inverse-shadow)",
246
+ textShadow: "var(--emboss-inverse)"
247
+ } : void 0,
248
+ children: c.label
249
+ },
250
+ c.key
251
+ ))
252
+ },
253
+ col.id
254
+ )) })
255
+ }
256
+ ) }),
257
+ /* @__PURE__ */ jsx(FieldMessage, { id: messageId, error, hint })
258
+ ] });
259
+ }
260
+ export {
261
+ TimePicker,
262
+ formatTime
263
+ };
264
+ //# sourceMappingURL=TimePicker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../components/ui/TimePicker.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useId, useRef, useState } from \"react\";\nimport { ClearKey } from \"./DatePicker\";\nimport { Portal } from \"./Floating\";\nimport { FieldLabel, FieldMessage } from \"./FieldMessage\";\nimport { useFloatingPanel } from \"./useFloatingPanel\";\n\ninterface TimePickerProps {\n /** 24-hour \"HH:mm\", like \"14:30\" */\n value?: string | null;\n defaultValue?: string | null;\n onChange?: (time: string | null) => void;\n /** Minutes between choices in the minute column */\n step?: number;\n /** Show 1–12 with am / pm instead of 0–23 */\n hour12?: boolean;\n /** Earliest and latest times, as \"HH:mm\" */\n min?: string;\n max?: string;\n label?: string;\n hint?: string;\n error?: string;\n placeholder?: string;\n clearable?: boolean;\n disabled?: boolean;\n fullWidth?: boolean;\n name?: string;\n className?: string;\n}\n\nconst pad = (n: number) => String(n).padStart(2, \"0\");\nconst toMinutes = (t?: string | null) => {\n if (!t) return null;\n const m = /^(\\d{1,2}):(\\d{2})$/.exec(t);\n return m ? Number(m[1]) * 60 + Number(m[2]) : null;\n};\nconst fromMinutes = (m: number) => `${pad(Math.floor(m / 60))}:${pad(m % 60)}`;\n\nexport function formatTime(t: string, hour12: boolean) {\n const m = toMinutes(t);\n if (m === null) return t;\n const h = Math.floor(m / 60);\n return hour12 ? `${h % 12 || 12}:${pad(m % 60)} ${h < 12 ? \"am\" : \"pm\"}` : fromMinutes(m);\n}\n\ntype Column = \"hour\" | \"minute\" | \"period\";\n\n/* A field that opens a panel of columns: hours, minutes and, for a\n 12-hour clock, am / pm. The chosen cell in each is a raised key in the\n inverse material. Arrows move within a column and between columns;\n Enter or picking a minute closes it. */\nexport function TimePicker({\n value,\n defaultValue = null,\n onChange,\n step = 15,\n hour12 = false,\n min,\n max,\n label,\n hint,\n error,\n placeholder = \"pick a time\",\n clearable = false,\n disabled = false,\n fullWidth = false,\n name,\n className = \"\",\n}: TimePickerProps) {\n const uid = useId();\n const ref = useRef<HTMLDivElement>(null);\n const buttonRef = useRef<HTMLButtonElement>(null);\n const [internal, setInternal] = useState<string | null>(defaultValue);\n const current = value !== undefined ? value : internal;\n const total = toMinutes(current);\n const { open, mounted, toggle, close, setFloating, panelStyle } = useFloatingPanel(ref, {\n onEscape: () => buttonRef.current?.focus(),\n });\n const messageId = error || hint ? `${uid}-message` : undefined;\n\n const lo = toMinutes(min) ?? 0;\n const hi = toMinutes(max) ?? 24 * 60 - 1;\n const hour = total === null ? null : Math.floor(total / 60);\n const minute = total === null ? null : total % 60;\n\n const set = (t: string | null) => {\n setInternal(t);\n onChange?.(t);\n };\n\n /* Picks a time, nudging the minutes into range when the hour changes */\n const choose = (h: number, m: number) => {\n let t = h * 60 + m;\n if (t < lo) t = lo;\n if (t > hi) t = hi;\n set(fromMinutes(t));\n };\n\n const hours = hour12 ? Array.from({ length: 12 }, (_, i) => (i === 0 ? 12 : i)) : Array.from({ length: 24 }, (_, i) => i);\n const minutes = Array.from({ length: Math.ceil(60 / step) }, (_, i) => i * step).filter((m) => m < 60);\n const isPm = hour !== null && hour >= 12;\n const to24 = (h12: number, pm: boolean) => (h12 % 12) + (pm ? 12 : 0);\n\n const hourOff = (h24: number) => h24 * 60 + 59 < lo || h24 * 60 > hi;\n const minuteOff = (m: number) => hour !== null && (hour * 60 + m < lo || hour * 60 + m > hi);\n\n type Cell = { key: string; label: string; selected: boolean; off: boolean; pick: () => void };\n const columns: { id: Column; label: string; cells: Cell[] }[] = [\n {\n id: \"hour\",\n label: \"hour\",\n cells: hours.map((h) => {\n const h24 = hour12 ? to24(h, isPm) : h;\n return {\n key: `h${h}`,\n label: hour12 ? String(h) : pad(h),\n selected: hour === h24,\n off: hourOff(h24),\n pick: () => choose(h24, minute ?? 0),\n };\n }),\n },\n {\n id: \"minute\",\n label: \"minute\",\n cells: minutes.map((m) => ({\n key: `m${m}`,\n label: pad(m),\n selected: minute === m,\n off: minuteOff(m),\n pick: () => {\n choose(hour ?? Math.floor(lo / 60), m);\n close();\n buttonRef.current?.focus();\n },\n })),\n },\n ];\n if (hour12) {\n columns.push({\n id: \"period\",\n label: \"am or pm\",\n cells: ([\"am\", \"pm\"] as const).map((p) => {\n const pm = p === \"pm\";\n const h24 = to24(hour === null ? 12 : hour % 12 || 12, pm);\n return {\n key: p,\n label: p,\n selected: hour !== null && isPm === pm,\n off: pm ? hi < 12 * 60 : lo >= 12 * 60,\n pick: () => choose(h24, minute ?? 0),\n };\n }),\n });\n }\n\n const colRefs = useRef<Record<string, HTMLDivElement | null>>({});\n\n /* Each column scrolls its chosen cell into the middle when it mounts */\n const attachColumn = (id: Column) => (el: HTMLDivElement | null) => {\n colRefs.current[id] = el;\n const sel = el?.querySelector<HTMLElement>('[aria-selected=\"true\"]');\n if (!el || el.dataset.scrolled) return;\n el.dataset.scrolled = \"1\";\n if (sel) el.scrollTop = sel.offsetTop - el.clientHeight / 2 + sel.offsetHeight / 2;\n // The panel lives at the end of <body>, so focus goes to it rather than waiting for Tab\n if (id === \"hour\") el.focus({ preventScroll: true });\n };\n\n const onColumnKey = (ci: number) => (e: React.KeyboardEvent) => {\n const col = columns[ci];\n const enabled = col.cells.map((c, i) => (c.off ? -1 : i)).filter((i) => i >= 0);\n const at = col.cells.findIndex((c) => c.selected);\n const pos = enabled.indexOf(at);\n const focusCol = (n: number) =>\n colRefs.current[columns[Math.max(0, Math.min(columns.length - 1, n))].id]?.focus();\n if (e.key === \"ArrowDown\" || e.key === \"ArrowUp\") {\n e.preventDefault();\n const d = e.key === \"ArrowDown\" ? 1 : -1;\n const next = enabled[pos < 0 ? 0 : (pos + d + enabled.length) % enabled.length];\n // the minute column closes on pick; from the keyboard, just move\n if (col.id === \"minute\") choose(hour ?? Math.floor(lo / 60), minutes[next]);\n else col.cells[next]?.pick();\n requestAnimationFrame(() =>\n colRefs.current[col.id]?.querySelector('[aria-selected=\"true\"]')?.scrollIntoView({ block: \"nearest\" }),\n );\n } else if (e.key === \"ArrowRight\") {\n e.preventDefault();\n focusCol(ci + 1);\n } else if (e.key === \"ArrowLeft\") {\n e.preventDefault();\n focusCol(ci - 1);\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n close();\n buttonRef.current?.focus();\n }\n };\n\n return (\n <div className={[\"group/field flex flex-col gap-1.5\", fullWidth ? \"w-full\" : \"\"].join(\" \")}>\n {label && <FieldLabel id={`${uid}-label`}>{label}</FieldLabel>}\n <div ref={ref} className={[\"relative\", fullWidth ? \"w-full\" : \"inline-block w-44\", className].join(\" \")}>\n {/* The field's error styling keys off aria-invalid, as Input's does */}\n {/* eslint-disable-next-line jsx-a11y/role-supports-aria-props */}\n <button\n ref={buttonRef}\n type=\"button\"\n disabled={disabled}\n onClick={toggle}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n aria-labelledby={label ? `${uid}-label ${uid}-value` : undefined}\n aria-invalid={error ? true : undefined}\n aria-describedby={messageId}\n data-open={open || undefined}\n className={[\n \"field group/time relative isolate flex w-full cursor-pointer items-center gap-2.5 rounded-xl py-3 pl-3.5 text-left text-[13.5px]\",\n clearable && current ? \"pr-10\" : \"pr-4\",\n \"focus:outline-none disabled:opacity-40 disabled:pointer-events-none\",\n ].join(\" \")}\n >\n <svg\n aria-hidden\n width=\"15\"\n height=\"15\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n viewBox=\"0 0 24 24\"\n className={[\n \"shrink-0 transition-[color,transform] duration-300 ease-[var(--ease-spring)]\",\n open ? \"scale-110 text-ink-soft\" : \"text-ink-faint group-hover/time:text-ink-body\",\n ].join(\" \")}\n >\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M12 7v5l3 2\" />\n </svg>\n <span id={`${uid}-value`} className={[\"tabular-nums\", current ? \"font-medium text-ink\" : \"text-ink-faint\"].join(\" \")}>\n {current ? formatTime(current, hour12) : placeholder}\n </span>\n </button>\n {clearable && current && !disabled && (\n <ClearKey\n label=\"clear time\"\n onClear={() => {\n set(null);\n buttonRef.current?.focus();\n }}\n />\n )}\n {name && <input type=\"hidden\" name={name} value={current ?? \"\"} />}\n </div>\n\n {mounted && (\n <Portal>\n <div\n ref={setFloating}\n role=\"dialog\"\n aria-label={label ? `choose ${label}` : \"choose a time\"}\n className=\"rounded-[16px] p-1.5\"\n style={panelStyle}\n >\n <div className=\"flex gap-1 rounded-[11px] bg-panel-face p-1.5\" style={{ boxShadow: \"var(--panel-face-shadow)\" }}>\n {columns.map((col, ci) => (\n <div\n key={col.id}\n ref={attachColumn(col.id)}\n role=\"listbox\"\n aria-label={col.label}\n tabIndex={0}\n onKeyDown={onColumnKey(ci)}\n className=\"flex max-h-56 w-14 flex-col gap-0.5 overflow-y-auto rounded-[9px] p-0.5 scroll-thin outline-none focus-visible:ring-2 focus-visible:ring-ink\"\n style={{ scrollbarWidth: \"none\" }}\n >\n {col.cells.map((c) => (\n <div\n key={c.key}\n role=\"option\"\n aria-selected={c.selected}\n aria-disabled={c.off || undefined}\n onClick={() => !c.off && c.pick()}\n className={[\n \"grid h-8 shrink-0 place-items-center rounded-[8px] text-[13px] tabular-nums transition-colors duration-100\",\n c.off\n ? \"cursor-not-allowed text-ink-ghost\"\n : c.selected\n ? \"cursor-pointer font-semibold text-ink-inverse\"\n : \"cursor-pointer text-ink-body hover:bg-surface-active hover:text-ink\",\n ].join(\" \")}\n style={\n c.selected && !c.off\n ? {\n background: \"var(--face-inverse)\",\n boxShadow: \"var(--face-inverse-shadow)\",\n textShadow: \"var(--emboss-inverse)\",\n }\n : undefined\n }\n >\n {c.label}\n </div>\n ))}\n </div>\n ))}\n </div>\n </div>\n </Portal>\n )}\n <FieldMessage id={messageId} error={error} hint={hint} />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,19 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+
4
+ type TimelineVariant = "default" | "success" | "warning" | "accent";
5
+ interface TimelineItem {
6
+ key: string;
7
+ title: string;
8
+ description?: string;
9
+ date?: string;
10
+ icon?: React.ReactNode;
11
+ variant?: TimelineVariant;
12
+ }
13
+ interface TimelineProps {
14
+ items: TimelineItem[];
15
+ className?: string;
16
+ }
17
+ declare function Timeline({ items, className }: TimelineProps): react_jsx_runtime.JSX.Element;
18
+
19
+ export { Timeline };