@robr0/design-system 0.1.0 → 0.3.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 (91) hide show
  1. package/README.md +13 -9
  2. package/components/Accordion/Accordion.js +0 -1
  3. package/components/Avatar/Avatar.js +1 -0
  4. package/components/Breadcrumb/Breadcrumb.css +9 -1
  5. package/components/Breadcrumb/Breadcrumb.d.ts +7 -1
  6. package/components/Breadcrumb/Breadcrumb.js +3 -2
  7. package/components/Button/Button.css +18 -0
  8. package/components/Button/Button.d.ts +38 -16
  9. package/components/Button/Button.js +76 -58
  10. package/components/Card/Card.css +6 -0
  11. package/components/Card/Card.d.ts +17 -5
  12. package/components/Card/Card.js +101 -80
  13. package/components/Checkbox/Checkbox.d.ts +24 -12
  14. package/components/Checkbox/Checkbox.js +99 -85
  15. package/components/CircularButton/CircularButton.css +11 -0
  16. package/components/CircularButton/CircularButton.d.ts +7 -1
  17. package/components/CircularButton/CircularButton.js +15 -4
  18. package/components/CodeBlock/CodeBlock.js +10 -1
  19. package/components/ColorPicker/ColorPicker.css +278 -0
  20. package/components/ColorPicker/ColorPicker.d.ts +50 -0
  21. package/components/ColorPicker/ColorPicker.js +338 -0
  22. package/components/Combobox/Combobox.css +0 -36
  23. package/components/Combobox/Combobox.d.ts +19 -7
  24. package/components/Combobox/Combobox.js +106 -89
  25. package/components/CommandPalette/CommandPalette.css +1 -15
  26. package/components/CommandPalette/CommandPalette.js +6 -5
  27. package/components/ContextMenu/ContextMenu.css +262 -0
  28. package/components/ContextMenu/ContextMenu.d.ts +26 -0
  29. package/components/ContextMenu/ContextMenu.js +350 -0
  30. package/components/DateInput/DateInput.css +0 -36
  31. package/components/DateInput/DateInput.d.ts +18 -21
  32. package/components/DateInput/DateInput.js +76 -64
  33. package/components/DatePicker/DatePicker.d.ts +9 -2
  34. package/components/DatePicker/DatePicker.js +137 -128
  35. package/components/Dialog/Dialog.d.ts +15 -4
  36. package/components/Dialog/Dialog.js +136 -118
  37. package/components/Drawer/Drawer.d.ts +15 -4
  38. package/components/Drawer/Drawer.js +138 -119
  39. package/components/Dropdown/Dropdown.css +0 -36
  40. package/components/Dropdown/Dropdown.d.ts +24 -8
  41. package/components/Dropdown/Dropdown.js +219 -169
  42. package/components/DropdownMenu/DropdownMenu.js +10 -7
  43. package/components/Field/Field.css +80 -0
  44. package/components/Field/Field.d.ts +50 -0
  45. package/components/Field/Field.js +58 -0
  46. package/components/Field/FieldContext.d.ts +42 -0
  47. package/components/Field/FieldContext.js +7 -0
  48. package/components/FileInput/FileInput.css +0 -36
  49. package/components/FileInput/FileInput.d.ts +13 -17
  50. package/components/FileInput/FileInput.js +168 -141
  51. package/components/Input/Input.css +0 -43
  52. package/components/Input/Input.d.ts +23 -22
  53. package/components/Input/Input.js +93 -66
  54. package/components/Kbd/Kbd.css +28 -0
  55. package/components/Kbd/Kbd.d.ts +19 -0
  56. package/components/Kbd/Kbd.js +14 -0
  57. package/components/Popover/Popover.d.ts +0 -6
  58. package/components/Popover/Popover.js +22 -16
  59. package/components/ProgressBar/ProgressBar.js +1 -1
  60. package/components/RadioButton/RadioButton.d.ts +26 -13
  61. package/components/RadioButton/RadioButton.js +95 -70
  62. package/components/SelectionCard/SelectionCard.css +6 -2
  63. package/components/SelectionCard/SelectionCard.d.ts +10 -4
  64. package/components/SelectionCard/SelectionCard.js +72 -56
  65. package/components/Skeleton/Skeleton.css +2 -0
  66. package/components/Slider/Slider.d.ts +19 -10
  67. package/components/Slider/Slider.js +50 -40
  68. package/components/Spinner/Spinner.css +11 -0
  69. package/components/Spinner/Spinner.d.ts +2 -2
  70. package/components/Swatch/Swatch.css +52 -0
  71. package/components/Swatch/Swatch.d.ts +34 -0
  72. package/components/Swatch/Swatch.js +44 -0
  73. package/components/Table/Table.css +19 -0
  74. package/components/Table/Table.d.ts +10 -2
  75. package/components/Table/Table.js +58 -58
  76. package/components/Textarea/Textarea.css +0 -43
  77. package/components/Textarea/Textarea.d.ts +21 -22
  78. package/components/Textarea/Textarea.js +78 -67
  79. package/components/ToggleGroup/ToggleGroup.d.ts +18 -8
  80. package/components/ToggleGroup/ToggleGroup.js +61 -44
  81. package/components/ToggleSwitch/ToggleSwitch.css +7 -2
  82. package/components/ToggleSwitch/ToggleSwitch.d.ts +17 -9
  83. package/components/ToggleSwitch/ToggleSwitch.js +57 -44
  84. package/components/registry.d.ts +47 -10
  85. package/components/registry.js +5 -1
  86. package/components/registry.json +498 -56
  87. package/components/registry.json.d.ts +498 -56
  88. package/components/registry.json.js +4 -1
  89. package/index.d.ts +8 -1
  90. package/index.js +16 -1
  91. package/package.json +1 -1
@@ -0,0 +1,350 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import React, { useState, useRef, useCallback, useEffect, useLayoutEffect } from "react";
3
+ import "./ContextMenu.css";
4
+ import "../../fonts/material-symbols.css";
5
+ function collectItems(entries) {
6
+ const result = [];
7
+ for (const entry of entries) {
8
+ if (entry.type === "separator") continue;
9
+ if (entry.type === "group") {
10
+ result.push(...collectItems(entry.items));
11
+ } else {
12
+ result.push(entry);
13
+ }
14
+ }
15
+ return result;
16
+ }
17
+ const SubMenuItem = ({ item, baseClass, focused, size, onActivate }) => {
18
+ const [subOpen, setSubOpen] = useState(false);
19
+ const timeoutRef = useRef(null);
20
+ const handleMouseEnter = () => {
21
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
22
+ setSubOpen(true);
23
+ };
24
+ const handleMouseLeave = () => {
25
+ timeoutRef.current = setTimeout(() => setSubOpen(false), 150);
26
+ };
27
+ useEffect(() => {
28
+ return () => {
29
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
30
+ };
31
+ }, []);
32
+ useEffect(() => {
33
+ if (focused && item.children && item.children.length > 0) {
34
+ setSubOpen(true);
35
+ }
36
+ }, [focused, item.children]);
37
+ const classes = [
38
+ `${baseClass}__item`,
39
+ focused ? `${baseClass}__item--focused` : "",
40
+ item.disabled ? `${baseClass}__item--disabled` : "",
41
+ item.destructive ? `${baseClass}__item--destructive` : "",
42
+ `${baseClass}__item--has-submenu`
43
+ ].filter(Boolean).join(" ");
44
+ return /* @__PURE__ */ jsxs(
45
+ "li",
46
+ {
47
+ className: classes,
48
+ role: "menuitem",
49
+ "aria-haspopup": "menu",
50
+ "aria-expanded": subOpen,
51
+ "aria-disabled": item.disabled || void 0,
52
+ onMouseEnter: handleMouseEnter,
53
+ onMouseLeave: handleMouseLeave,
54
+ onClick: (e) => {
55
+ e.stopPropagation();
56
+ if (!item.disabled) setSubOpen((prev) => !prev);
57
+ },
58
+ children: [
59
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__item-label`, children: [
60
+ item.icon && /* @__PURE__ */ jsx("span", { className: `${baseClass}__item-icon material-symbols-rounded`, "aria-hidden": "true", children: item.icon }),
61
+ item.label
62
+ ] }),
63
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__submenu-icon material-symbols-rounded`, "aria-hidden": "true", children: "chevron_right" }),
64
+ subOpen && item.children && /* @__PURE__ */ jsx("ul", { className: `${baseClass}__panel ${baseClass}__submenu`, role: "menu", children: item.children.map((entry, index) => /* @__PURE__ */ jsx(
65
+ SubEntry,
66
+ {
67
+ entry,
68
+ baseClass,
69
+ size,
70
+ onActivate
71
+ },
72
+ index
73
+ )) })
74
+ ]
75
+ }
76
+ );
77
+ };
78
+ const SubEntry = ({
79
+ entry,
80
+ baseClass,
81
+ size,
82
+ onActivate
83
+ }) => {
84
+ if (entry.type === "separator") {
85
+ return /* @__PURE__ */ jsx("li", { className: `${baseClass}__separator`, role: "separator" });
86
+ }
87
+ if (entry.type === "group") {
88
+ return /* @__PURE__ */ jsxs("li", { className: `${baseClass}__group`, role: "none", children: [
89
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__group-label`, role: "presentation", children: entry.label }),
90
+ /* @__PURE__ */ jsx("ul", { className: `${baseClass}__group-list`, role: "group", "aria-label": entry.label, children: entry.items.map((subEntry, subIndex) => /* @__PURE__ */ jsx(
91
+ SubEntry,
92
+ {
93
+ entry: subEntry,
94
+ baseClass,
95
+ size,
96
+ onActivate
97
+ },
98
+ subIndex
99
+ )) })
100
+ ] });
101
+ }
102
+ if (entry.children && entry.children.length > 0) {
103
+ return /* @__PURE__ */ jsx(
104
+ SubMenuItem,
105
+ {
106
+ item: entry,
107
+ baseClass,
108
+ focused: false,
109
+ size,
110
+ onActivate
111
+ }
112
+ );
113
+ }
114
+ const itemClasses = [
115
+ `${baseClass}__item`,
116
+ entry.disabled ? `${baseClass}__item--disabled` : "",
117
+ entry.destructive ? `${baseClass}__item--destructive` : ""
118
+ ].filter(Boolean).join(" ");
119
+ return /* @__PURE__ */ jsxs(
120
+ "li",
121
+ {
122
+ className: itemClasses,
123
+ role: "menuitem",
124
+ "aria-disabled": entry.disabled || void 0,
125
+ onClick: (e) => {
126
+ e.stopPropagation();
127
+ if (!entry.disabled) {
128
+ entry.onClick?.();
129
+ onActivate();
130
+ }
131
+ },
132
+ children: [
133
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__item-label`, children: [
134
+ entry.icon && /* @__PURE__ */ jsx("span", { className: `${baseClass}__item-icon material-symbols-rounded`, "aria-hidden": "true", children: entry.icon }),
135
+ entry.label
136
+ ] }),
137
+ entry.shortcut && /* @__PURE__ */ jsx("span", { className: `${baseClass}__shortcut`, children: entry.shortcut })
138
+ ]
139
+ }
140
+ );
141
+ };
142
+ const ContextMenu = React.forwardRef(
143
+ ({
144
+ items,
145
+ size = "default",
146
+ ariaLabel,
147
+ className = "",
148
+ children,
149
+ onContextMenu,
150
+ ...rest
151
+ }, ref) => {
152
+ const [position, setPosition] = useState(null);
153
+ const [focusedIndex, setFocusedIndex] = useState(-1);
154
+ const rootRef = useRef(null);
155
+ const panelRef = useRef(null);
156
+ const baseClass = "ds-context-menu";
157
+ const classes = [baseClass, className].filter(Boolean).join(" ");
158
+ const isOpen = position !== null;
159
+ const flatItems = collectItems(items);
160
+ const setRootRef = (node) => {
161
+ rootRef.current = node;
162
+ if (typeof ref === "function") ref(node);
163
+ else if (ref) ref.current = node;
164
+ };
165
+ const handleClose = useCallback(() => {
166
+ setPosition(null);
167
+ setFocusedIndex(-1);
168
+ }, []);
169
+ const handleContextMenu = (e) => {
170
+ onContextMenu?.(e);
171
+ e.preventDefault();
172
+ let x = e.clientX;
173
+ let y = e.clientY;
174
+ if (x === 0 && y === 0 && rootRef.current) {
175
+ const rect = rootRef.current.getBoundingClientRect();
176
+ x = rect.left + rect.width / 2;
177
+ y = rect.top + rect.height / 2;
178
+ }
179
+ setPosition({ x, y });
180
+ setFocusedIndex(-1);
181
+ };
182
+ useEffect(() => {
183
+ if (!isOpen) return;
184
+ const handler = (e) => {
185
+ if (panelRef.current && !panelRef.current.contains(e.target)) {
186
+ handleClose();
187
+ }
188
+ };
189
+ document.addEventListener("mousedown", handler);
190
+ return () => document.removeEventListener("mousedown", handler);
191
+ }, [isOpen, handleClose]);
192
+ useEffect(() => {
193
+ if (!isOpen) return;
194
+ const handler = () => handleClose();
195
+ window.addEventListener("scroll", handler, true);
196
+ window.addEventListener("resize", handler);
197
+ return () => {
198
+ window.removeEventListener("scroll", handler, true);
199
+ window.removeEventListener("resize", handler);
200
+ };
201
+ }, [isOpen, handleClose]);
202
+ useLayoutEffect(() => {
203
+ if (!isOpen || !panelRef.current || !position) return;
204
+ const panel = panelRef.current;
205
+ const rect = panel.getBoundingClientRect();
206
+ const margin = 8;
207
+ let { x, y } = position;
208
+ if (x + rect.width + margin > window.innerWidth) {
209
+ x = Math.max(margin, window.innerWidth - rect.width - margin);
210
+ }
211
+ if (y + rect.height + margin > window.innerHeight) {
212
+ y = Math.max(margin, window.innerHeight - rect.height - margin);
213
+ }
214
+ if (x !== position.x || y !== position.y) {
215
+ setPosition({ x, y });
216
+ return;
217
+ }
218
+ panel.focus({ preventScroll: true });
219
+ }, [isOpen, position]);
220
+ const handleKeyDown = (e) => {
221
+ switch (e.key) {
222
+ case "Enter":
223
+ case " ": {
224
+ e.preventDefault();
225
+ if (focusedIndex >= 0) {
226
+ const item = flatItems[focusedIndex];
227
+ if (item && !item.disabled && !item.children) {
228
+ item.onClick?.();
229
+ handleClose();
230
+ }
231
+ }
232
+ break;
233
+ }
234
+ case "ArrowDown":
235
+ e.preventDefault();
236
+ setFocusedIndex((prev) => {
237
+ let next = prev + 1;
238
+ while (next < flatItems.length && flatItems[next].disabled) next++;
239
+ return next < flatItems.length ? next : prev;
240
+ });
241
+ break;
242
+ case "ArrowUp":
243
+ e.preventDefault();
244
+ setFocusedIndex((prev) => {
245
+ let next = prev < 0 ? flatItems.length - 1 : prev - 1;
246
+ while (next >= 0 && flatItems[next].disabled) next--;
247
+ return next >= 0 ? next : prev;
248
+ });
249
+ break;
250
+ case "Escape":
251
+ case "Tab":
252
+ e.preventDefault();
253
+ handleClose();
254
+ break;
255
+ }
256
+ };
257
+ const renderEntry = (entry, index, itemCounter2) => {
258
+ if (entry.type === "separator") {
259
+ return /* @__PURE__ */ jsx("li", { className: `${baseClass}__separator`, role: "separator" }, `sep-${index}`);
260
+ }
261
+ if (entry.type === "group") {
262
+ return /* @__PURE__ */ jsxs("li", { className: `${baseClass}__group`, role: "none", children: [
263
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__group-label`, role: "presentation", children: entry.label }),
264
+ /* @__PURE__ */ jsx("ul", { className: `${baseClass}__group-list`, role: "group", "aria-label": entry.label, children: entry.items.map(
265
+ (subEntry, subIndex) => renderEntry(subEntry, subIndex, itemCounter2)
266
+ ) })
267
+ ] }, `group-${index}`);
268
+ }
269
+ const myIndex = itemCounter2.count;
270
+ itemCounter2.count++;
271
+ const isFocused = myIndex === focusedIndex;
272
+ if (entry.children && entry.children.length > 0) {
273
+ return /* @__PURE__ */ jsx(
274
+ SubMenuItem,
275
+ {
276
+ item: entry,
277
+ baseClass,
278
+ focused: isFocused,
279
+ size,
280
+ onActivate: handleClose
281
+ },
282
+ `submenu-${index}`
283
+ );
284
+ }
285
+ const itemClasses = [
286
+ `${baseClass}__item`,
287
+ isFocused ? `${baseClass}__item--focused` : "",
288
+ entry.disabled ? `${baseClass}__item--disabled` : "",
289
+ entry.destructive ? `${baseClass}__item--destructive` : ""
290
+ ].filter(Boolean).join(" ");
291
+ return /* @__PURE__ */ jsxs(
292
+ "li",
293
+ {
294
+ className: itemClasses,
295
+ role: "menuitem",
296
+ "aria-disabled": entry.disabled || void 0,
297
+ onClick: (e) => {
298
+ e.stopPropagation();
299
+ if (!entry.disabled) {
300
+ entry.onClick?.();
301
+ handleClose();
302
+ }
303
+ },
304
+ children: [
305
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__item-label`, children: [
306
+ entry.icon && /* @__PURE__ */ jsx(
307
+ "span",
308
+ {
309
+ className: `${baseClass}__item-icon material-symbols-rounded`,
310
+ "aria-hidden": "true",
311
+ children: entry.icon
312
+ }
313
+ ),
314
+ entry.label
315
+ ] }),
316
+ entry.shortcut && /* @__PURE__ */ jsx("span", { className: `${baseClass}__shortcut`, children: entry.shortcut })
317
+ ]
318
+ },
319
+ `item-${index}`
320
+ );
321
+ };
322
+ const panelClasses = [
323
+ `${baseClass}__panel`,
324
+ size === "compact" ? `${baseClass}__panel--compact` : ""
325
+ ].filter(Boolean).join(" ");
326
+ const itemCounter = { count: 0 };
327
+ return /* @__PURE__ */ jsxs("div", { ...rest, ref: setRootRef, className: classes, onContextMenu: handleContextMenu, children: [
328
+ children,
329
+ isOpen && /* @__PURE__ */ jsx(
330
+ "ul",
331
+ {
332
+ className: panelClasses,
333
+ role: "menu",
334
+ "aria-label": ariaLabel,
335
+ tabIndex: -1,
336
+ ref: (el) => {
337
+ panelRef.current = el;
338
+ },
339
+ style: { left: position.x, top: position.y },
340
+ onKeyDown: handleKeyDown,
341
+ children: items.map((entry, index) => renderEntry(entry, index, itemCounter))
342
+ }
343
+ )
344
+ ] });
345
+ }
346
+ );
347
+ ContextMenu.displayName = "ContextMenu";
348
+ export {
349
+ ContextMenu
350
+ };
@@ -14,19 +14,6 @@
14
14
  LABEL
15
15
  ============================================ */
16
16
 
17
- .ds-date-input__label {
18
- font-family: var(--font-paragraph-em-family);
19
- font-size: var(--font-paragraph-em-size);
20
- font-weight: var(--font-paragraph-em-weight);
21
- line-height: var(--font-paragraph-em-line-height);
22
- letter-spacing: var(--font-paragraph-em-letter-spacing);
23
- color: var(--color-text-primary);
24
- }
25
-
26
- .ds-date-input__required {
27
- color: var(--color-core-accent-coral);
28
- }
29
-
30
17
  /* ============================================
31
18
  FIELD WRAPPER
32
19
  ============================================ */
@@ -108,25 +95,10 @@
108
95
  --icon-size: var(--icon-size-sm);
109
96
  }
110
97
 
111
- .ds-date-input--compact .ds-date-input__label {
112
- font-size: var(--font-paragraph-sm-size);
113
- line-height: var(--font-paragraph-sm-line-height);
114
- }
115
-
116
98
  /* ============================================
117
99
  HELPER TEXT
118
100
  ============================================ */
119
101
 
120
- .ds-date-input__helper {
121
- font-family: var(--font-paragraph-sm-family);
122
- font-size: var(--font-paragraph-sm-size);
123
- font-weight: var(--font-paragraph-sm-weight);
124
- line-height: var(--font-paragraph-sm-line-height);
125
- letter-spacing: var(--font-paragraph-sm-letter-spacing);
126
- color: var(--color-text-tertiary);
127
- margin: 0;
128
- }
129
-
130
102
  /* ============================================
131
103
  ERROR STATE
132
104
  ============================================ */
@@ -139,18 +111,10 @@
139
111
  border-color: var(--color-status-error-border);
140
112
  }
141
113
 
142
- .ds-date-input--error .ds-date-input__helper {
143
- color: var(--color-status-error-border);
144
- }
145
-
146
114
  /* ============================================
147
115
  DISABLED STATE
148
116
  ============================================ */
149
117
 
150
- .ds-date-input--disabled .ds-date-input__label {
151
- color: var(--color-input-text-disabled);
152
- }
153
-
154
118
  .ds-date-input--disabled .ds-date-input__field {
155
119
  background-color: var(--color-input-bg-disabled);
156
120
  border-color: var(--color-input-border-disabled);
@@ -1,17 +1,12 @@
1
1
  import { default as React } from 'react';
2
- export interface DateInputProps {
2
+ /** Props owned by DateInput itself — everything else falls through to the <input>. */
3
+ type DateInputOwnProps = {
3
4
  /** Input label text */
4
5
  label?: string;
5
6
  /** Current value (YYYY-MM-DD format) */
6
7
  value?: string;
7
- /** Placeholder text */
8
- placeholder?: string;
9
- /** Component size */
8
+ /** Component size (not the native character-width `size` attribute) */
10
9
  size?: 'default' | 'compact';
11
- /** Whether the input is disabled */
12
- disabled?: boolean;
13
- /** Whether the input is required */
14
- required?: boolean;
15
10
  /** Error state */
16
11
  error?: boolean;
17
12
  /** Helper or error message */
@@ -20,24 +15,26 @@ export interface DateInputProps {
20
15
  min?: string;
21
16
  /** Maximum selectable date (YYYY-MM-DD) */
22
17
  max?: string;
23
- /** Callback when value changes */
24
- onChange?: (value: string) => void;
25
- /** Callback on focus */
26
- onFocus?: () => void;
27
- /** Callback on blur */
28
- onBlur?: () => void;
29
- /** Additional CSS classes */
18
+ /**
19
+ * Convenience callback receiving the value directly.
20
+ * Fires alongside `onChange`, which keeps the standard React event signature
21
+ * so form libraries work unmodified.
22
+ */
23
+ onValueChange?: (value: string) => void;
24
+ /** Additional CSS classes — applied to the wrapper, not the <input> */
30
25
  className?: string;
31
- /** Accessible name override */
26
+ /** @deprecated Pass the native `aria-label` attribute instead. */
32
27
  ariaLabel?: string;
33
- /** HTML name attribute */
34
- name?: string;
35
- /** HTML id attribute */
36
- id?: string;
28
+ };
29
+ export interface DateInputProps extends DateInputOwnProps, Omit<React.ComponentPropsWithoutRef<'input'>, keyof DateInputOwnProps | 'type'> {
37
30
  }
38
31
  /**
39
32
  * Date input component with native date picker.
40
33
  * Uses the browser's built-in date input with custom styling,
41
34
  * calendar icon, label, and validation states.
35
+ *
36
+ * Forwards a ref to the underlying `<input type="date">` and spreads
37
+ * unrecognised props onto it.
42
38
  */
43
- export declare const DateInput: ({ label, value, placeholder, size, disabled, required, error, helperText, min, max, onChange, onFocus, onBlur, className, ariaLabel, name, id, }: DateInputProps) => React.JSX.Element;
39
+ export declare const DateInput: React.ForwardRefExoticComponent<DateInputProps & React.RefAttributes<HTMLInputElement>>;
40
+ export {};
@@ -1,69 +1,81 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
2
- import "react";
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { Field } from "../Field/Field.js";
3
4
  import "./DateInput.css";
4
5
  import "../../fonts/material-symbols.css";
5
- const DateInput = ({
6
- label,
7
- value,
8
- placeholder,
9
- size = "default",
10
- disabled = false,
11
- required = false,
12
- error = false,
13
- helperText,
14
- min,
15
- max,
16
- onChange,
17
- onFocus,
18
- onBlur,
19
- className = "",
20
- ariaLabel,
21
- name,
22
- id
23
- }) => {
24
- const baseClass = "ds-date-input";
25
- const errorClass = error ? `${baseClass}--error` : "";
26
- const disabledClass = disabled ? `${baseClass}--disabled` : "";
27
- const sizeClass = `${baseClass}--${size}`;
28
- const classes = [baseClass, sizeClass, errorClass, disabledClass, className].filter(Boolean).join(" ");
29
- const handleChange = (e) => {
30
- if (onChange) {
31
- onChange(e.target.value);
32
- }
33
- };
34
- const inputId = id || name || label?.toLowerCase().replace(/\s+/g, "-");
35
- return /* @__PURE__ */ jsxs("div", { className: classes, children: [
36
- label && /* @__PURE__ */ jsxs("label", { className: `${baseClass}__label`, htmlFor: inputId, children: [
37
- label,
38
- required && /* @__PURE__ */ jsx("span", { className: `${baseClass}__required`, "aria-hidden": "true", children: " *" })
39
- ] }),
40
- /* @__PURE__ */ jsxs("div", { className: `${baseClass}__field-wrapper`, children: [
41
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon material-symbols-rounded`, "aria-hidden": "true", children: "calendar_today" }),
42
- /* @__PURE__ */ jsx(
43
- "input",
44
- {
45
- className: `${baseClass}__field`,
46
- type: "date",
47
- id: inputId,
48
- name,
49
- value,
50
- placeholder,
51
- disabled,
52
- required,
53
- min,
54
- max,
55
- "aria-label": ariaLabel || label,
56
- "aria-invalid": error,
57
- "aria-describedby": helperText ? `${inputId}-helper` : void 0,
58
- onChange: handleChange,
59
- onFocus,
60
- onBlur
61
- }
62
- )
63
- ] }),
64
- helperText && /* @__PURE__ */ jsx("p", { className: `${baseClass}__helper`, id: `${inputId}-helper`, children: helperText })
65
- ] });
66
- };
6
+ const DateInput = React.forwardRef(
7
+ ({
8
+ label,
9
+ value,
10
+ placeholder,
11
+ size = "default",
12
+ disabled = false,
13
+ required = false,
14
+ error = false,
15
+ helperText,
16
+ min,
17
+ max,
18
+ onChange,
19
+ onValueChange,
20
+ className = "",
21
+ ariaLabel,
22
+ name,
23
+ id,
24
+ ...rest
25
+ }, ref) => {
26
+ const baseClass = "ds-date-input";
27
+ const classes = [
28
+ baseClass,
29
+ `${baseClass}--${size}`,
30
+ error ? `${baseClass}--error` : "",
31
+ disabled ? `${baseClass}--disabled` : "",
32
+ className
33
+ ].filter(Boolean).join(" ");
34
+ const handleChange = (e) => {
35
+ onChange?.(e);
36
+ onValueChange?.(e.target.value);
37
+ };
38
+ const inputId = id || name || label?.toLowerCase().replace(/\s+/g, "-");
39
+ return /* @__PURE__ */ jsx(
40
+ Field,
41
+ {
42
+ className: classes,
43
+ label,
44
+ helperText,
45
+ error,
46
+ required,
47
+ disabled,
48
+ size,
49
+ id: inputId,
50
+ children: /* @__PURE__ */ jsxs("div", { className: `${baseClass}__field-wrapper`, children: [
51
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon material-symbols-rounded`, "aria-hidden": "true", children: "calendar_today" }),
52
+ /* @__PURE__ */ jsx(
53
+ "input",
54
+ {
55
+ ...rest,
56
+ ref,
57
+ className: `${baseClass}__field`,
58
+ type: "date",
59
+ id: inputId,
60
+ name,
61
+ value,
62
+ placeholder,
63
+ disabled,
64
+ required,
65
+ min,
66
+ max,
67
+ "aria-label": ariaLabel || rest["aria-label"] || label,
68
+ "aria-invalid": error,
69
+ "aria-describedby": helperText ? `${inputId}-helper` : rest["aria-describedby"],
70
+ onChange: handleChange
71
+ }
72
+ )
73
+ ] })
74
+ }
75
+ );
76
+ }
77
+ );
78
+ DateInput.displayName = "DateInput";
67
79
  export {
68
80
  DateInput
69
81
  };
@@ -1,4 +1,6 @@
1
- export interface DatePickerProps {
1
+ import { default as React } from 'react';
2
+ /** Props owned by DatePicker itself — everything else falls through to the wrapper. */
3
+ type DatePickerOwnProps = {
2
4
  /** Currently selected date (YYYY-MM-DD) */
3
5
  value?: string;
4
6
  /** Component size */
@@ -13,10 +15,15 @@ export interface DatePickerProps {
13
15
  onDateSelect?: (date: string) => void;
14
16
  /** Additional CSS classes */
15
17
  className?: string;
18
+ };
19
+ export interface DatePickerProps extends DatePickerOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof DatePickerOwnProps> {
16
20
  }
17
21
  /**
18
22
  * Inline date picker calendar component.
19
23
  * Displays a full month grid with navigation,
20
24
  * day headers, and selectable date cells.
25
+ *
26
+ * Forwards a ref to the wrapping element and spreads unrecognised props onto it.
21
27
  */
22
- export declare const DatePicker: ({ value, size, disabled, min, max, onDateSelect, className, }: DatePickerProps) => import("react").JSX.Element;
28
+ export declare const DatePicker: React.ForwardRefExoticComponent<DatePickerProps & React.RefAttributes<HTMLDivElement>>;
29
+ export {};