@yarcl/react 0.1.0 → 0.2.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 (223) hide show
  1. package/README.md +14 -2
  2. package/{src/apply.ts → dist/apply.d.ts} +4 -23
  3. package/dist/apply.js +37 -0
  4. package/dist/classes.d.ts +13 -0
  5. package/dist/classes.js +22 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +58 -0
  8. package/dist/color.js +49 -32
  9. package/dist/components/Accordion.d.ts +96 -0
  10. package/dist/components/Accordion.js +148 -0
  11. package/dist/components/Alert.d.ts +60 -0
  12. package/dist/components/Alert.js +61 -0
  13. package/dist/components/Avatar.d.ts +100 -0
  14. package/dist/components/Avatar.js +89 -0
  15. package/dist/components/Badge.d.ts +44 -0
  16. package/dist/components/Badge.js +43 -0
  17. package/dist/components/Breadcrumb.d.ts +77 -0
  18. package/dist/components/Breadcrumb.js +126 -0
  19. package/dist/components/Button.d.ts +18 -0
  20. package/dist/components/Button.js +33 -0
  21. package/dist/components/ButtonGroup.d.ts +29 -0
  22. package/dist/components/ButtonGroup.js +37 -0
  23. package/dist/components/Card.d.ts +36 -0
  24. package/dist/components/Card.js +26 -0
  25. package/dist/components/Checkbox.d.ts +28 -0
  26. package/dist/components/Checkbox.js +38 -0
  27. package/dist/components/Combobox.d.ts +75 -0
  28. package/dist/components/Combobox.js +362 -0
  29. package/dist/components/CommandPalette.d.ts +92 -0
  30. package/dist/components/CommandPalette.js +293 -0
  31. package/dist/components/DatePicker.d.ts +157 -0
  32. package/dist/components/DatePicker.js +230 -0
  33. package/dist/components/Dialog.d.ts +25 -0
  34. package/dist/components/Dialog.js +29 -0
  35. package/dist/components/Divider.d.ts +23 -0
  36. package/dist/components/Divider.js +25 -0
  37. package/dist/components/Drawer.d.ts +21 -0
  38. package/dist/components/Drawer.js +25 -0
  39. package/dist/components/EmptyState.d.ts +51 -0
  40. package/dist/components/EmptyState.js +47 -0
  41. package/dist/components/Field.d.ts +27 -0
  42. package/dist/components/Field.js +59 -0
  43. package/dist/components/Heading.d.ts +26 -0
  44. package/dist/components/Heading.js +25 -0
  45. package/dist/components/HoverCard.d.ts +47 -0
  46. package/dist/components/HoverCard.js +53 -0
  47. package/dist/components/IconButton.d.ts +19 -0
  48. package/dist/components/IconButton.js +32 -0
  49. package/dist/components/Inline.d.ts +21 -0
  50. package/dist/components/Inline.js +24 -0
  51. package/dist/components/Input.d.ts +19 -0
  52. package/dist/components/Input.js +25 -0
  53. package/dist/components/Label.d.ts +46 -0
  54. package/dist/components/Label.js +33 -0
  55. package/dist/components/Link.d.ts +27 -0
  56. package/dist/components/Link.js +26 -0
  57. package/dist/components/Menu.d.ts +73 -0
  58. package/dist/components/Menu.js +143 -0
  59. package/dist/components/Modal.d.ts +45 -0
  60. package/dist/components/Modal.js +110 -0
  61. package/dist/components/NumberInput.d.ts +49 -0
  62. package/dist/components/NumberInput.js +130 -0
  63. package/dist/components/Pagination.d.ts +86 -0
  64. package/dist/components/Pagination.js +119 -0
  65. package/dist/components/Popover.d.ts +72 -0
  66. package/dist/components/Popover.js +87 -0
  67. package/dist/components/Progress.d.ts +60 -0
  68. package/dist/components/Progress.js +59 -0
  69. package/dist/components/Radio.d.ts +32 -0
  70. package/dist/components/Radio.js +47 -0
  71. package/dist/components/RadioGroup.d.ts +57 -0
  72. package/dist/components/RadioGroup.js +68 -0
  73. package/dist/components/Select.d.ts +47 -0
  74. package/dist/components/Select.js +138 -0
  75. package/dist/components/Skeleton.d.ts +46 -0
  76. package/dist/components/Skeleton.js +40 -0
  77. package/dist/components/Slider.d.ts +126 -0
  78. package/dist/components/Slider.js +166 -0
  79. package/dist/components/Spinner.d.ts +23 -0
  80. package/dist/components/Spinner.js +23 -0
  81. package/dist/components/Stack.d.ts +35 -0
  82. package/dist/components/Stack.js +24 -0
  83. package/dist/components/Switch.d.ts +27 -0
  84. package/dist/components/Switch.js +33 -0
  85. package/dist/components/Table.d.ts +71 -0
  86. package/dist/components/Table.js +61 -0
  87. package/dist/components/Tabs.d.ts +66 -0
  88. package/dist/components/Tabs.js +109 -0
  89. package/dist/components/Text.d.ts +32 -0
  90. package/dist/components/Text.js +28 -0
  91. package/dist/components/Textarea.d.ts +15 -0
  92. package/dist/components/Textarea.js +24 -0
  93. package/dist/components/Toast.d.ts +90 -0
  94. package/dist/components/Toast.js +156 -0
  95. package/dist/components/ToggleGroup.d.ts +79 -0
  96. package/dist/components/ToggleGroup.js +101 -0
  97. package/dist/components/Tooltip.d.ts +46 -0
  98. package/dist/components/Tooltip.js +62 -0
  99. package/dist/components/VisuallyHidden.d.ts +18 -0
  100. package/dist/components/VisuallyHidden.js +22 -0
  101. package/dist/contrast.d.ts +32 -0
  102. package/dist/css.d.ts +2 -2
  103. package/dist/css.js +193 -157
  104. package/dist/define.d.ts +40 -15
  105. package/dist/define.js +30 -27
  106. package/dist/demo.d.ts +2 -0
  107. package/dist/demo.js +2 -0
  108. package/dist/esbuild.cjs +5 -0
  109. package/dist/esbuild.d.ts +4 -0
  110. package/dist/esbuild.js +6 -0
  111. package/dist/field-context.d.ts +16 -0
  112. package/dist/field-context.js +17 -0
  113. package/dist/floating.d.ts +12 -0
  114. package/dist/floating.js +52 -0
  115. package/dist/hooks.d.ts +1 -0
  116. package/dist/hooks.js +12 -0
  117. package/{src/index.ts → dist/index.d.ts} +11 -33
  118. package/dist/index.js +50 -0
  119. package/dist/init.d.ts +18 -0
  120. package/dist/init.js +224 -0
  121. package/dist/plugin.d.ts +10 -26
  122. package/dist/plugin.js +152 -78
  123. package/dist/reference/DemoYarcl.d.ts +12 -0
  124. package/dist/reference/DemoYarcl.js +312 -0
  125. package/dist/reference/DesignReference.d.ts +24 -0
  126. package/dist/reference/DesignReference.js +403 -0
  127. package/dist/reference/index.js +2 -0
  128. package/dist/rollup.cjs +5 -0
  129. package/dist/rollup.d.ts +4 -0
  130. package/dist/rollup.js +6 -0
  131. package/dist/rspack.cjs +5 -0
  132. package/dist/rspack.d.ts +4 -0
  133. package/dist/rspack.js +6 -0
  134. package/dist/runtime.d.ts +13 -0
  135. package/dist/runtime.js +30 -0
  136. package/dist/setup.d.ts +6 -0
  137. package/dist/setup.js +33 -0
  138. package/{src → dist}/styles.css +224 -106
  139. package/dist/themes/bloom.d.ts +295 -0
  140. package/dist/themes/bloom.js +202 -0
  141. package/dist/themes/brutalist.d.ts +277 -0
  142. package/dist/themes/brutalist.js +199 -0
  143. package/dist/themes/compact.d.ts +274 -0
  144. package/dist/themes/compact.js +200 -0
  145. package/dist/themes/contract.d.ts +23 -0
  146. package/dist/themes/editorial.d.ts +277 -0
  147. package/dist/themes/editorial.js +183 -0
  148. package/dist/themes/index.d.ts +1425 -0
  149. package/dist/themes/index.js +45 -0
  150. package/{src/types.ts → dist/types.d.ts} +40 -37
  151. package/dist/vite.cjs +5 -0
  152. package/dist/vite.d.ts +4 -0
  153. package/dist/vite.js +6 -0
  154. package/dist/webpack.cjs +5 -0
  155. package/dist/webpack.d.ts +4 -0
  156. package/dist/webpack.js +6 -0
  157. package/dist/yarcl.config.d.ts +276 -0
  158. package/dist/yarcl.config.js +273 -0
  159. package/package.json +69 -17
  160. package/src/classes.ts +0 -23
  161. package/src/color.ts +0 -48
  162. package/src/components/Accordion.tsx +0 -274
  163. package/src/components/Alert.tsx +0 -85
  164. package/src/components/Badge.tsx +0 -79
  165. package/src/components/Breadcrumb.tsx +0 -239
  166. package/src/components/Button.tsx +0 -58
  167. package/src/components/ButtonGroup.tsx +0 -61
  168. package/src/components/Card.tsx +0 -50
  169. package/src/components/Checkbox.tsx +0 -49
  170. package/src/components/Combobox.tsx +0 -522
  171. package/src/components/CommandPalette.tsx +0 -452
  172. package/src/components/DatePicker.tsx +0 -432
  173. package/src/components/Dialog.tsx +0 -39
  174. package/src/components/Divider.tsx +0 -34
  175. package/src/components/Drawer.tsx +0 -31
  176. package/src/components/Field.tsx +0 -61
  177. package/src/components/Heading.tsx +0 -40
  178. package/src/components/HoverCard.tsx +0 -108
  179. package/src/components/IconButton.tsx +0 -59
  180. package/src/components/Inline.tsx +0 -42
  181. package/src/components/Input.tsx +0 -28
  182. package/src/components/Label.tsx +0 -58
  183. package/src/components/Link.tsx +0 -41
  184. package/src/components/Menu.tsx +0 -207
  185. package/src/components/Modal.tsx +0 -164
  186. package/src/components/NumberInput.tsx +0 -208
  187. package/src/components/Pagination.tsx +0 -227
  188. package/src/components/Popover.tsx +0 -140
  189. package/src/components/Progress.tsx +0 -122
  190. package/src/components/Radio.tsx +0 -59
  191. package/src/components/RadioGroup.tsx +0 -114
  192. package/src/components/Select.tsx +0 -197
  193. package/src/components/Skeleton.tsx +0 -95
  194. package/src/components/Slider.tsx +0 -315
  195. package/src/components/Spinner.tsx +0 -38
  196. package/src/components/Stack.tsx +0 -57
  197. package/src/components/Switch.tsx +0 -42
  198. package/src/components/Table.tsx +0 -99
  199. package/src/components/Tabs.tsx +0 -159
  200. package/src/components/Text.tsx +0 -67
  201. package/src/components/Textarea.tsx +0 -26
  202. package/src/components/Toast.tsx +0 -190
  203. package/src/components/ToggleGroup.tsx +0 -227
  204. package/src/components/Tooltip.tsx +0 -78
  205. package/src/contrast.ts +0 -82
  206. package/src/css.ts +0 -228
  207. package/src/define.check.ts +0 -259
  208. package/src/define.ts +0 -379
  209. package/src/field-context.ts +0 -31
  210. package/src/floating.tsx +0 -61
  211. package/src/hooks.ts +0 -19
  212. package/src/plugin.ts +0 -94
  213. package/src/reference/DesignReference.tsx +0 -374
  214. package/src/runtime.ts +0 -38
  215. package/src/themes/bloom.ts +0 -72
  216. package/src/themes/brutalist.ts +0 -63
  217. package/src/themes/compact.ts +0 -63
  218. package/src/themes/contract.ts +0 -24
  219. package/src/themes/editorial.ts +0 -55
  220. package/src/themes/index.ts +0 -47
  221. package/src/yarcl.config.ts +0 -104
  222. /package/{src/reference/index.ts → dist/reference/index.d.ts} +0 -0
  223. /package/{src → dist}/reference/reference.css +0 -0
@@ -0,0 +1,293 @@
1
+ import { useConfig, useDefaults } from "../runtime.js";
2
+ import { colorClass, cx, radiusClass, sizeClass } from "../classes.js";
3
+ import { useControllable } from "../hooks.js";
4
+ import { useModalDialog } from "./Modal.js";
5
+ import { cloneElement, useEffect, useId, useState, useSyncExternalStore } from "react";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/components/CommandPalette.tsx
8
+ function detectApple() {
9
+ return typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.platform || navigator.userAgent);
10
+ }
11
+ var noop = () => () => {};
12
+ function useIsApple() {
13
+ return useSyncExternalStore(noop, detectApple, () => false);
14
+ }
15
+ function parseShortcut(shortcut, apple) {
16
+ const parts = shortcut.split("+").map((part) => part.trim().toLowerCase());
17
+ const key = parts[parts.length - 1] ?? "";
18
+ const mods = new Set(parts.slice(0, -1));
19
+ const mod = mods.has("mod");
20
+ return {
21
+ key,
22
+ ctrl: mods.has("ctrl") || mods.has("control") || mod && !apple,
23
+ meta: mods.has("meta") || mods.has("cmd") || mod && apple,
24
+ alt: mods.has("alt") || mods.has("option"),
25
+ shift: mods.has("shift")
26
+ };
27
+ }
28
+ function matchesShortcut(event, shortcut, apple) {
29
+ const s = parseShortcut(shortcut, apple);
30
+ if (event.ctrlKey !== s.ctrl || event.metaKey !== s.meta || event.altKey !== s.alt || event.shiftKey !== s.shift) return false;
31
+ const code = s.key.length === 1 ? /[a-z]/.test(s.key) ? `Key${s.key.toUpperCase()}` : /\d/.test(s.key) ? `Digit${s.key}` : "" : "";
32
+ return event.key?.toLowerCase() === s.key || code !== "" && event.code === code;
33
+ }
34
+ function ariaChord(chord, apple) {
35
+ const s = parseShortcut(chord, apple);
36
+ const key = s.key.length === 1 ? s.key.toUpperCase() : s.key.charAt(0).toUpperCase() + s.key.slice(1);
37
+ return [
38
+ s.ctrl && "Control",
39
+ s.alt && "Alt",
40
+ s.shift && "Shift",
41
+ s.meta && "Meta",
42
+ key
43
+ ].filter(Boolean).join("+");
44
+ }
45
+ function ariaShortcut(shortcut, apple) {
46
+ return shortcut.trim().split(/\s+/).map((chord) => ariaChord(chord, apple)).join(" ");
47
+ }
48
+ function shortcutKeys(shortcut, apple) {
49
+ return shortcut.trim().split(/[\s+]+/).filter(Boolean).map((raw) => {
50
+ const part = raw.trim();
51
+ const lower = part.toLowerCase();
52
+ if (lower === "mod") return apple ? "⌘" : "Ctrl";
53
+ if (lower === "ctrl" || lower === "control") return apple ? "⌃" : "Ctrl";
54
+ if (lower === "alt" || lower === "option") return apple ? "⌥" : "Alt";
55
+ if (lower === "shift") return apple ? "⇧" : "Shift";
56
+ if (lower === "meta" || lower === "cmd") return apple ? "⌘" : "Meta";
57
+ return part.length === 1 ? part.toUpperCase() : part;
58
+ });
59
+ }
60
+ function ShortcutHint({ shortcut, apple }) {
61
+ return /* @__PURE__ */ jsx("span", {
62
+ className: "yarcl-command-palette-shortcut",
63
+ "aria-hidden": "true",
64
+ children: shortcutKeys(shortcut, apple).map((key, i) => /* @__PURE__ */ jsx("kbd", { children: key }, i))
65
+ });
66
+ }
67
+ function SearchIcon() {
68
+ return /* @__PURE__ */ jsxs("svg", {
69
+ viewBox: "0 0 24 24",
70
+ fill: "none",
71
+ stroke: "currentColor",
72
+ strokeWidth: "2",
73
+ strokeLinecap: "round",
74
+ "aria-hidden": "true",
75
+ children: [/* @__PURE__ */ jsx("circle", {
76
+ cx: "11",
77
+ cy: "11",
78
+ r: "7"
79
+ }), /* @__PURE__ */ jsx("path", { d: "m20 20-3.5-3.5" })]
80
+ });
81
+ }
82
+ function contains(command, text) {
83
+ const haystack = [
84
+ command.label,
85
+ command.group ?? "",
86
+ ...command.keywords ?? []
87
+ ].join(" ").toLowerCase();
88
+ return text.toLowerCase().split(/\s+/).filter(Boolean).every((word) => haystack.includes(word));
89
+ }
90
+ function firstEnabled(list, from = 0, step = 1) {
91
+ for (let i = 0; i < list.length; i++) {
92
+ const index = ((from + i * step) % list.length + list.length) % list.length;
93
+ if (!list[index]?.disabled) return index;
94
+ }
95
+ return null;
96
+ }
97
+ function CommandPaletteBody({ commands, inputValue, onInputValueChange, filter = true, placeholder = "Search commands…", label = "Command palette", emptyMessage = "No results", apple, choose }) {
98
+ const [text, setText] = useControllable(inputValue, "", onInputValueChange);
99
+ const [activeIndex, setActiveIndex] = useState(null);
100
+ const idPrefix = useId();
101
+ const listId = `${idPrefix}-list`;
102
+ const optionId = (i) => `${idPrefix}-option-${i}`;
103
+ const match = filter === true ? contains : filter === false ? () => true : filter;
104
+ const sections = [];
105
+ for (const command of commands) {
106
+ if (!match(command, text)) continue;
107
+ let section = sections.find((s) => s.heading === command.group);
108
+ if (!section) sections.push(section = {
109
+ heading: command.group,
110
+ commands: []
111
+ });
112
+ section.commands.push(command);
113
+ }
114
+ const visible = sections.flatMap((s) => s.commands);
115
+ const shown = activeIndex === null || activeIndex >= visible.length ? firstEnabled(visible) : activeIndex;
116
+ useEffect(() => {
117
+ if (shown != null) document.getElementById(`${idPrefix}-option-${shown}`)?.scrollIntoView({ block: "nearest" });
118
+ }, [shown, idPrefix]);
119
+ function onKeyDown(event) {
120
+ if (visible.length === 0) return;
121
+ let next;
122
+ if (event.key === "ArrowDown") next = firstEnabled(visible, shown == null ? 0 : shown + 1);
123
+ else if (event.key === "ArrowUp") next = firstEnabled(visible, shown == null ? visible.length - 1 : shown - 1, -1);
124
+ else if (event.key === "Home") next = firstEnabled(visible);
125
+ else if (event.key === "End") next = firstEnabled(visible, visible.length - 1, -1);
126
+ else if (event.key === "Enter") {
127
+ event.preventDefault();
128
+ const command = shown != null ? visible[shown] : void 0;
129
+ if (command) choose(command);
130
+ return;
131
+ } else return;
132
+ event.preventDefault();
133
+ setActiveIndex(next);
134
+ }
135
+ let index = 0;
136
+ return /* @__PURE__ */ jsxs("div", {
137
+ className: "yarcl-command-palette-content",
138
+ children: [
139
+ /* @__PURE__ */ jsxs("div", {
140
+ className: "yarcl-command-palette-search",
141
+ children: [/* @__PURE__ */ jsx(SearchIcon, {}), /* @__PURE__ */ jsx("input", {
142
+ className: "yarcl-command-palette-input",
143
+ type: "text",
144
+ role: "combobox",
145
+ "aria-label": label,
146
+ "aria-autocomplete": "list",
147
+ "aria-expanded": visible.length > 0,
148
+ "aria-controls": visible.length > 0 ? listId : void 0,
149
+ "aria-activedescendant": shown != null ? optionId(shown) : void 0,
150
+ autoComplete: "off",
151
+ autoCorrect: "off",
152
+ spellCheck: false,
153
+ placeholder,
154
+ value: text,
155
+ onChange: (event) => {
156
+ setText(event.target.value);
157
+ setActiveIndex(null);
158
+ },
159
+ onKeyDown
160
+ })]
161
+ }),
162
+ visible.length > 0 && /* @__PURE__ */ jsx("div", {
163
+ id: listId,
164
+ role: "listbox",
165
+ "aria-label": label,
166
+ className: "yarcl-listbox yarcl-command-palette-list",
167
+ children: sections.map((section, s) => {
168
+ const headingId = `${idPrefix}-group-${s}`;
169
+ const options = section.commands.map((command) => {
170
+ const i = index++;
171
+ const isActive = i === shown;
172
+ return /* @__PURE__ */ jsxs("div", {
173
+ id: optionId(i),
174
+ role: "option",
175
+ "aria-selected": isActive,
176
+ "aria-disabled": command.disabled || void 0,
177
+ "aria-keyshortcuts": command.shortcut ? ariaShortcut(command.shortcut, apple) : void 0,
178
+ "data-active": isActive || void 0,
179
+ className: "yarcl-option",
180
+ onMouseDown: (event) => event.preventDefault(),
181
+ onMouseMove: () => {
182
+ if (!command.disabled && i !== shown) setActiveIndex(i);
183
+ },
184
+ onClick: () => choose(command),
185
+ children: [/* @__PURE__ */ jsxs("span", {
186
+ className: "yarcl-command-palette-item",
187
+ children: [command.icon, /* @__PURE__ */ jsx("span", {
188
+ className: "yarcl-option-label",
189
+ children: command.label
190
+ })]
191
+ }), command.shortcut && /* @__PURE__ */ jsx(ShortcutHint, {
192
+ shortcut: command.shortcut,
193
+ apple
194
+ })]
195
+ }, command.id);
196
+ });
197
+ return /* @__PURE__ */ jsxs("div", {
198
+ role: "group",
199
+ "aria-labelledby": section.heading ? headingId : void 0,
200
+ className: "yarcl-command-palette-group",
201
+ children: [section.heading && /* @__PURE__ */ jsx("div", {
202
+ id: headingId,
203
+ className: "yarcl-command-palette-heading",
204
+ "aria-hidden": "true",
205
+ children: section.heading
206
+ }), options]
207
+ }, section.heading ?? "");
208
+ })
209
+ }),
210
+ /* @__PURE__ */ jsx("div", {
211
+ role: "status",
212
+ className: "yarcl-listbox-message yarcl-command-palette-empty",
213
+ children: visible.length === 0 ? emptyMessage : null
214
+ })
215
+ ]
216
+ });
217
+ }
218
+ /**
219
+ * A searchable command menu in a modal dialog, opened with a keyboard shortcut (⌘K or Ctrl+K by default).
220
+ * The search input is a combobox: arrow keys, Home and End move through the matching commands,
221
+ * Enter runs the highlighted one, Esc closes the palette and returns focus.
222
+ *
223
+ * @example
224
+ * ```tsx
225
+ * <CommandPalette
226
+ * commands={[
227
+ * { id: 'new', label: 'New file', group: 'File', shortcut: 'Mod+N', onSelect: createFile },
228
+ * { id: 'theme', label: 'Toggle dark mode', group: 'View', keywords: ['theme'], onSelect: toggleTheme },
229
+ * ]}
230
+ * />
231
+ * ```
232
+ */
233
+ function CommandPalette({ commands, open: openProp, defaultOpen = false, onOpenChange, shortcut = "Mod+K", trigger, onSelect, inputValue, onInputValueChange, filter, placeholder, label = "Command palette", emptyMessage, size, radius, color, className, onClick, onClose, ...props }) {
234
+ const own = useDefaults("CommandPalette");
235
+ const config = useConfig();
236
+ const apple = useIsApple();
237
+ const [open, setOpen] = useControllable(openProp, defaultOpen, onOpenChange);
238
+ const dialog = useModalDialog(open, setOpen);
239
+ useEffect(() => {
240
+ if (!shortcut) return;
241
+ function onKeyDown(event) {
242
+ if (event.defaultPrevented || !shortcut || !matchesShortcut(event, shortcut, detectApple())) return;
243
+ event.preventDefault();
244
+ setOpen(!open);
245
+ }
246
+ document.addEventListener("keydown", onKeyDown);
247
+ return () => document.removeEventListener("keydown", onKeyDown);
248
+ }, [
249
+ shortcut,
250
+ open,
251
+ setOpen
252
+ ]);
253
+ function choose(command) {
254
+ if (command.disabled) return;
255
+ setOpen(false);
256
+ command.onSelect?.();
257
+ onSelect?.(command);
258
+ }
259
+ const resolvedSize = size ?? own.size;
260
+ return /* @__PURE__ */ jsxs(Fragment, { children: [trigger && cloneElement(trigger, {
261
+ onClick(event) {
262
+ trigger.props.onClick?.(event);
263
+ setOpen(true);
264
+ },
265
+ ...shortcut ? { "aria-keyshortcuts": ariaShortcut(shortcut, apple) } : {}
266
+ }), /* @__PURE__ */ jsx("dialog", {
267
+ "aria-label": label,
268
+ ...props,
269
+ ref: dialog.ref,
270
+ className: cx("yarcl-modal yarcl-dialog yarcl-command-palette", `yarcl-modal-size-${config.defaults.modalSize}`, sizeClass(resolvedSize, "CommandPalette"), radiusClass(radius ?? own.radius, resolvedSize), colorClass(color ?? own.color), className),
271
+ onClose: (event) => {
272
+ dialog.onClose(event);
273
+ onClose?.(event);
274
+ },
275
+ onClick: (event) => {
276
+ dialog.onClick(event);
277
+ onClick?.(event);
278
+ },
279
+ children: open && /* @__PURE__ */ jsx(CommandPaletteBody, {
280
+ commands,
281
+ inputValue,
282
+ onInputValueChange,
283
+ filter,
284
+ placeholder,
285
+ label,
286
+ emptyMessage,
287
+ apple,
288
+ choose
289
+ })
290
+ })] });
291
+ }
292
+ //#endregion
293
+ export { CommandPalette };
@@ -0,0 +1,157 @@
1
+ import { type Day, type Locale } from 'date-fns';
2
+ import { type ComponentProps, type ReactNode } from 'react';
3
+ import type { TokenProps, VariantProps } from '../types';
4
+ /** A date range picked with a {@link DatePicker} in `mode="range"`. `to` is `null` while only the start is picked. */
5
+ export interface DateRange {
6
+ /** First day of the range. */
7
+ from: Date;
8
+ /** Last day of the range, or `null` until the second day is picked. */
9
+ to: Date | null;
10
+ }
11
+ /** Text for the parts of a {@link DatePicker} that have no visible label. */
12
+ export interface DatePickerLabels {
13
+ /** Accessible name of the calendar popover. */
14
+ dialog: string;
15
+ /** Accessible name of the previous month button. */
16
+ previousMonth: string;
17
+ /** Accessible name of the next month button. */
18
+ nextMonth: string;
19
+ }
20
+ /** Props shared by both modes of {@link DatePicker}. */
21
+ export interface DatePickerBaseProps extends TokenProps<'DatePicker'>, VariantProps, Omit<ComponentProps<'button'>, 'color' | 'value' | 'defaultValue' | 'onChange' | 'children' | 'type' | 'name'> {
22
+ /**
23
+ * Earliest day that can be picked. Earlier days are shown disabled and the calendar can't move before its month.
24
+ * @example
25
+ * ```tsx
26
+ * <DatePicker min={new Date()} />
27
+ * ```
28
+ */
29
+ min?: Date;
30
+ /**
31
+ * Latest day that can be picked.
32
+ * @example
33
+ * ```tsx
34
+ * <DatePicker max={addYears(new Date(), 1)} />
35
+ * ```
36
+ */
37
+ max?: Date;
38
+ /**
39
+ * Returns `true` for days that can't be picked, e.g. weekends or booked dates. They stay focusable but can't be chosen.
40
+ * @example
41
+ * ```tsx
42
+ * <DatePicker isDateDisabled={isWeekend} />
43
+ * ```
44
+ */
45
+ isDateDisabled?: (date: Date) => boolean;
46
+ /**
47
+ * A date-fns locale. Sets month and weekday names, the display format and the first day of the week.
48
+ * @default enUS
49
+ * @example
50
+ * ```tsx
51
+ * import { de } from 'date-fns/locale';
52
+ * <DatePicker locale={de} />
53
+ * ```
54
+ */
55
+ locale?: Locale;
56
+ /**
57
+ * First day of the week, from 0 (Sunday) to 6 (Saturday).
58
+ * @default the locale's first day of the week
59
+ * @example
60
+ * ```tsx
61
+ * <DatePicker weekStartsOn={1} />
62
+ * ```
63
+ */
64
+ weekStartsOn?: Day;
65
+ /**
66
+ * A date-fns `format` pattern for the value shown in the trigger.
67
+ * @default 'PP'
68
+ * @example
69
+ * ```tsx
70
+ * <DatePicker displayFormat="yyyy-MM-dd" />
71
+ * ```
72
+ */
73
+ displayFormat?: string;
74
+ /**
75
+ * Text shown when nothing is picked.
76
+ * @example
77
+ * ```tsx
78
+ * <DatePicker placeholder="Pick a date" />
79
+ * ```
80
+ */
81
+ placeholder?: ReactNode;
82
+ /**
83
+ * Form field name. Renders a hidden input with the value as `yyyy-MM-dd`, or `yyyy-MM-dd/yyyy-MM-dd` for a range.
84
+ * @example
85
+ * ```tsx
86
+ * <DatePicker name="birthday" />
87
+ * ```
88
+ */
89
+ name?: string;
90
+ /**
91
+ * Accessible names for the popover and the month buttons, for translation.
92
+ * @default { dialog: 'Choose date', previousMonth: 'Previous month', nextMonth: 'Next month' }
93
+ * @example
94
+ * ```tsx
95
+ * <DatePicker labels={{ dialog: 'Datum wählen', previousMonth: 'Vorheriger Monat', nextMonth: 'Nächster Monat' }} />
96
+ * ```
97
+ */
98
+ labels?: Partial<DatePickerLabels>;
99
+ }
100
+ /** Props for {@link DatePicker} picking one day. */
101
+ export interface DatePickerSingleProps extends DatePickerBaseProps {
102
+ /**
103
+ * Picks one day.
104
+ * @default 'single'
105
+ */
106
+ mode?: 'single';
107
+ /** Controlled value. `null` means nothing is picked. */
108
+ value?: Date | null;
109
+ /**
110
+ * Initial value when uncontrolled.
111
+ * @default null
112
+ */
113
+ defaultValue?: Date | null;
114
+ /**
115
+ * Called with the picked day.
116
+ * @example
117
+ * ```tsx
118
+ * <DatePicker value={date} onValueChange={setDate} />
119
+ * ```
120
+ */
121
+ onValueChange?: (value: Date | null) => void;
122
+ }
123
+ /** Props for {@link DatePicker} picking a range of days. */
124
+ export interface DatePickerRangeProps extends DatePickerBaseProps {
125
+ /** Picks a start and an end day. */
126
+ mode: 'range';
127
+ /** Controlled value. `null` means nothing is picked. */
128
+ value?: DateRange | null;
129
+ /**
130
+ * Initial value when uncontrolled.
131
+ * @default null
132
+ */
133
+ defaultValue?: DateRange | null;
134
+ /**
135
+ * Called when the start is picked (with `to: null`) and again when the range is complete.
136
+ * @example
137
+ * ```tsx
138
+ * <DatePicker mode="range" value={range} onValueChange={setRange} />
139
+ * ```
140
+ */
141
+ onValueChange?: (value: DateRange | null) => void;
142
+ }
143
+ /** Props for {@link DatePicker}. `mode` decides whether one day or a range is picked. */
144
+ export type DatePickerProps = DatePickerSingleProps | DatePickerRangeProps;
145
+ /**
146
+ * Picks a day or a range of days from a calendar popover. Built on {@link Popover}, with dates handled by date-fns.
147
+ * The trigger shares the size scale with {@link Input}. Picked days use `variant`, days inside a range use
148
+ * `defaults.softVariant`. The calendar follows the WAI-ARIA date picker dialog pattern. Works inside a {@link Field}.
149
+ *
150
+ * @example
151
+ * ```tsx
152
+ * <Field label="Check-in">
153
+ * <DatePicker value={date} onValueChange={setDate} min={new Date()} placeholder="Pick a date" />
154
+ * </Field>
155
+ * ```
156
+ */
157
+ export declare function DatePicker(props: DatePickerProps): import("react").JSX.Element;
@@ -0,0 +1,230 @@
1
+ import { useDefaults } from "../runtime.js";
2
+ import { colorClass, cx, radiusClass, sizeClass, softVariantClass, variantClass } from "../classes.js";
3
+ import { useControllable } from "../hooks.js";
4
+ import { FieldContext, useFieldProps } from "../field-context.js";
5
+ import { Popover } from "./Popover.js";
6
+ import { useContext, useEffect, useId, useRef, useState } from "react";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ import { addDays, addMonths, addYears, endOfMonth, endOfWeek, format, isAfter, isBefore, isSameDay, isSameMonth, isToday, startOfDay, startOfMonth, startOfWeek } from "date-fns";
9
+ import { enUS } from "date-fns/locale/en-US";
10
+ //#region src/components/DatePicker.tsx
11
+ var defaultLabels = {
12
+ dialog: "Choose date",
13
+ previousMonth: "Previous month",
14
+ nextMonth: "Next month"
15
+ };
16
+ function Icon({ d }) {
17
+ return /* @__PURE__ */ jsx("svg", {
18
+ viewBox: "0 0 24 24",
19
+ fill: "none",
20
+ stroke: "currentColor",
21
+ strokeWidth: "2",
22
+ strokeLinecap: "round",
23
+ strokeLinejoin: "round",
24
+ "aria-hidden": "true",
25
+ children: /* @__PURE__ */ jsx("path", { d })
26
+ });
27
+ }
28
+ var calendarPath = "M8 3v4M16 3v4M4 10h16M5 5h14a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1z";
29
+ var iso = (date) => format(date, "yyyy-MM-dd");
30
+ /**
31
+ * Picks a day or a range of days from a calendar popover. Built on {@link Popover}, with dates handled by date-fns.
32
+ * The trigger shares the size scale with {@link Input}. Picked days use `variant`, days inside a range use
33
+ * `defaults.softVariant`. The calendar follows the WAI-ARIA date picker dialog pattern. Works inside a {@link Field}.
34
+ *
35
+ * @example
36
+ * ```tsx
37
+ * <Field label="Check-in">
38
+ * <DatePicker value={date} onValueChange={setDate} min={new Date()} placeholder="Pick a date" />
39
+ * </Field>
40
+ * ```
41
+ */
42
+ function DatePicker(props) {
43
+ const own = useDefaults("DatePicker");
44
+ const field = useContext(FieldContext);
45
+ const { mode = "single", value: valueProp, defaultValue = null, onValueChange, min, max, isDateDisabled, locale = enUS, weekStartsOn, displayFormat = "PP", placeholder, name, labels: labelsProp, size, radius, color, variant, className, required: _required, ...rest } = useFieldProps(props);
46
+ const range = mode === "range";
47
+ const labels = {
48
+ ...defaultLabels,
49
+ ...labelsProp
50
+ };
51
+ const [value, setValue] = useControllable(valueProp, defaultValue, onValueChange);
52
+ const from = value == null ? null : value instanceof Date ? value : value.from;
53
+ const to = value == null ? null : value instanceof Date ? value : value.to;
54
+ const [open, setOpen] = useState(false);
55
+ const [focused, setFocused] = useState(() => startOfDay(/* @__PURE__ */ new Date()));
56
+ const [hovered, setHovered] = useState(null);
57
+ const cellRef = useRef(null);
58
+ const moveFocus = useRef(false);
59
+ const id = useId();
60
+ const titleId = `${id}-title`;
61
+ const valueId = `${id}-value`;
62
+ const minDay = min && startOfDay(min);
63
+ const maxDay = max && startOfDay(max);
64
+ const outOfRange = (day) => minDay != null && isBefore(day, minDay) || maxDay != null && isAfter(day, maxDay);
65
+ const unavailable = (day) => outOfRange(day) || !!isDateDisabled?.(day);
66
+ const clamp = (day) => minDay && isBefore(day, minDay) ? minDay : maxDay && isAfter(day, maxDay) ? maxDay : day;
67
+ const week = {
68
+ locale,
69
+ weekStartsOn
70
+ };
71
+ useEffect(() => {
72
+ if (!open || !moveFocus.current) return;
73
+ moveFocus.current = false;
74
+ cellRef.current?.focus();
75
+ }, [open, focused]);
76
+ function onOpenChange(next) {
77
+ if (next) {
78
+ setFocused(clamp(startOfDay(from ?? /* @__PURE__ */ new Date())));
79
+ setHovered(null);
80
+ }
81
+ setOpen(next);
82
+ }
83
+ function pick(day) {
84
+ if (unavailable(day)) return;
85
+ if (!range) {
86
+ setValue(day);
87
+ setOpen(false);
88
+ } else if (!from || to) setValue({
89
+ from: day,
90
+ to: null
91
+ });
92
+ else {
93
+ setValue(isBefore(day, from) ? {
94
+ from: day,
95
+ to: from
96
+ } : {
97
+ from,
98
+ to: day
99
+ });
100
+ setOpen(false);
101
+ }
102
+ }
103
+ function navigate(day) {
104
+ moveFocus.current = true;
105
+ setFocused(clamp(day));
106
+ }
107
+ function onGridKeyDown(event) {
108
+ const move = {
109
+ ArrowLeft: () => addDays(focused, -1),
110
+ ArrowRight: () => addDays(focused, 1),
111
+ ArrowUp: () => addDays(focused, -7),
112
+ ArrowDown: () => addDays(focused, 7),
113
+ Home: () => startOfWeek(focused, week),
114
+ End: () => startOfDay(endOfWeek(focused, week)),
115
+ PageUp: () => event.shiftKey ? addYears(focused, -1) : addMonths(focused, -1),
116
+ PageDown: () => event.shiftKey ? addYears(focused, 1) : addMonths(focused, 1)
117
+ }[event.key];
118
+ if (move) {
119
+ event.preventDefault();
120
+ navigate(move());
121
+ } else if (event.key === "Enter" || event.key === " ") {
122
+ event.preventDefault();
123
+ pick(focused);
124
+ }
125
+ }
126
+ const month = startOfMonth(focused);
127
+ const first = startOfWeek(month, week);
128
+ const days = Array.from({ length: 42 }, (_, i) => addDays(first, i));
129
+ const weeks = Array.from({ length: 6 }, (_, i) => days.slice(i * 7, i * 7 + 7));
130
+ const prevDisabled = minDay != null && isBefore(endOfMonth(addMonths(month, -1)), minDay);
131
+ const nextDisabled = maxDay != null && isAfter(addMonths(month, 1), maxDay);
132
+ const previewEnd = range && from && !to ? hovered ?? focused : null;
133
+ const [spanStart, spanEnd] = previewEnd && from ? isBefore(previewEnd, from) ? [previewEnd, from] : [from, previewEnd] : [from, to];
134
+ const inSpan = (day) => spanStart != null && spanEnd != null && isAfter(day, spanStart) && isBefore(day, spanEnd);
135
+ const isEndpoint = (day) => from != null && isSameDay(day, from) || to != null && isSameDay(day, to);
136
+ const show = (date) => format(date, displayFormat, { locale });
137
+ const display = from ? range && to ? `${show(from)} – ${show(to)}` : show(from) : null;
138
+ const labelled = field != null || rest["aria-label"] != null || rest["aria-labelledby"] != null;
139
+ const s = size ?? own.size;
140
+ const hidden = value == null ? "" : range ? `${from ? iso(from) : ""}/${to ? iso(to) : ""}` : from ? iso(from) : "";
141
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Popover, {
142
+ open,
143
+ onOpenChange,
144
+ placement: "bottom-start",
145
+ modal: true,
146
+ children: [/* @__PURE__ */ jsx(Popover.Trigger, { children: /* @__PURE__ */ jsxs("button", {
147
+ type: "button",
148
+ className: cx("yarcl-input yarcl-select yarcl-date-picker", sizeClass(s, "DatePicker"), radiusClass(radius ?? own.radius, s), colorClass(color ?? own.color), className),
149
+ ...rest,
150
+ "aria-describedby": cx(labelled && display && valueId, rest["aria-describedby"]) || void 0,
151
+ children: [/* @__PURE__ */ jsx("span", {
152
+ id: valueId,
153
+ className: cx("yarcl-select-value", !display && "yarcl-select-placeholder"),
154
+ children: display ?? placeholder
155
+ }), /* @__PURE__ */ jsx(Icon, { d: calendarPath })]
156
+ }) }), /* @__PURE__ */ jsxs(Popover.Content, {
157
+ "aria-label": labels.dialog,
158
+ initialFocus: cellRef,
159
+ className: cx("yarcl-date-picker-panel", sizeClass(s, "DatePicker"), colorClass(color ?? own.color)),
160
+ children: [/* @__PURE__ */ jsxs("div", {
161
+ className: "yarcl-date-picker-header",
162
+ children: [
163
+ /* @__PURE__ */ jsx("button", {
164
+ type: "button",
165
+ className: "yarcl-date-picker-nav",
166
+ "aria-label": labels.previousMonth,
167
+ "aria-disabled": prevDisabled || void 0,
168
+ onClick: () => !prevDisabled && setFocused(clamp(addMonths(focused, -1))),
169
+ children: /* @__PURE__ */ jsx(Icon, { d: "m15 6-6 6 6 6" })
170
+ }),
171
+ /* @__PURE__ */ jsx("div", {
172
+ id: titleId,
173
+ className: "yarcl-date-picker-title",
174
+ "aria-live": "polite",
175
+ children: format(month, "LLLL y", { locale })
176
+ }),
177
+ /* @__PURE__ */ jsx("button", {
178
+ type: "button",
179
+ className: "yarcl-date-picker-nav",
180
+ "aria-label": labels.nextMonth,
181
+ "aria-disabled": nextDisabled || void 0,
182
+ onClick: () => !nextDisabled && setFocused(clamp(addMonths(focused, 1))),
183
+ children: /* @__PURE__ */ jsx(Icon, { d: "m9 6 6 6-6 6" })
184
+ })
185
+ ]
186
+ }), /* @__PURE__ */ jsxs("table", {
187
+ role: "grid",
188
+ "aria-labelledby": titleId,
189
+ "aria-multiselectable": range || void 0,
190
+ className: cx("yarcl-date-picker-grid", radiusClass(radius ?? own.radius, s)),
191
+ onKeyDown: onGridKeyDown,
192
+ onMouseLeave: () => setHovered(null),
193
+ children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: weeks[0].map((day) => /* @__PURE__ */ jsx("th", {
194
+ scope: "col",
195
+ abbr: format(day, "EEEE", { locale }),
196
+ children: format(day, "EEEEEE", { locale })
197
+ }, day.getDay())) }) }), /* @__PURE__ */ jsx("tbody", { children: weeks.map((row) => /* @__PURE__ */ jsx("tr", { children: row.map((day) => {
198
+ if (!isSameMonth(day, month)) return /* @__PURE__ */ jsx("td", { role: "gridcell" }, iso(day));
199
+ const isFocused = isSameDay(day, focused);
200
+ const endpoint = isEndpoint(day);
201
+ const between = inSpan(day);
202
+ return /* @__PURE__ */ jsx("td", {
203
+ ref: isFocused ? cellRef : void 0,
204
+ role: "gridcell",
205
+ tabIndex: isFocused ? 0 : -1,
206
+ "aria-selected": endpoint || between && !previewEnd,
207
+ "aria-disabled": unavailable(day) || void 0,
208
+ "aria-current": isToday(day) ? "date" : void 0,
209
+ "aria-label": format(day, "PPPP", { locale }),
210
+ "data-selected": endpoint || void 0,
211
+ "data-in-range": between || void 0,
212
+ className: cx("yarcl-date-picker-day", endpoint ? variantClass(variant ?? own.variant) : between && softVariantClass()),
213
+ onClick: () => {
214
+ setFocused(day);
215
+ pick(day);
216
+ },
217
+ onMouseEnter: () => range && setHovered(day),
218
+ children: format(day, "d", { locale })
219
+ }, iso(day));
220
+ }) }, iso(row[0]))) })]
221
+ })]
222
+ })]
223
+ }), name && /* @__PURE__ */ jsx("input", {
224
+ type: "hidden",
225
+ name,
226
+ value: hidden
227
+ })] });
228
+ }
229
+ //#endregion
230
+ export { DatePicker };