@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,42 @@
1
+ import { default as React } from 'react';
2
+ /**
3
+ * Everything a labelled form control needs to be announced correctly.
4
+ * Derived once by `Field`; read by a control via `useField()`.
5
+ *
6
+ * Lives in its own module so `Field.tsx` exports only a component — the
7
+ * react-refresh lint rule that keeps Fast Refresh working.
8
+ */
9
+ export interface FieldContextValue {
10
+ /** id for the control itself — the label's `htmlFor` points at it */
11
+ controlId: string;
12
+ /**
13
+ * id of the rendered `<label>`, or undefined when there is no label.
14
+ *
15
+ * `htmlFor` only associates with *labelable* elements (input, select,
16
+ * textarea, button…). A composite widget built from a `div` — a
17
+ * `role="combobox"` trigger, for instance — is not labelable, so it must
18
+ * point `aria-labelledby` at this id instead.
19
+ */
20
+ labelId: string | undefined;
21
+ /** id of the helper/error text, or undefined when there is none */
22
+ describedBy: string | undefined;
23
+ /** whether the control should report itself invalid */
24
+ invalid: boolean;
25
+ /** whether the control is required */
26
+ required: boolean;
27
+ /** whether the control is disabled */
28
+ disabled: boolean;
29
+ }
30
+ export declare const FieldContext: React.Context<FieldContextValue | null>;
31
+ /**
32
+ * Read the wiring `Field` derived.
33
+ *
34
+ * Returns `null` outside a `Field`, so a control that uses it still renders
35
+ * standalone — always optional-chain the result.
36
+ *
37
+ * ```tsx
38
+ * const field = useField();
39
+ * <input id={field?.controlId} aria-describedby={field?.describedBy} />
40
+ * ```
41
+ */
42
+ export declare const useField: () => FieldContextValue | null;
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ const FieldContext = React.createContext(null);
3
+ const useField = () => React.useContext(FieldContext);
4
+ export {
5
+ FieldContext,
6
+ useField
7
+ };
@@ -14,19 +14,6 @@
14
14
  LABEL
15
15
  ============================================ */
16
16
 
17
- .ds-file-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-file-input__required {
27
- color: var(--color-core-accent-coral);
28
- }
29
-
30
17
  /* ============================================
31
18
  DROPZONE
32
19
  ============================================ */
@@ -237,16 +224,6 @@
237
224
  HELPER TEXT
238
225
  ============================================ */
239
226
 
240
- .ds-file-input__helper {
241
- font-family: var(--font-paragraph-sm-family);
242
- font-size: var(--font-paragraph-sm-size);
243
- font-weight: var(--font-paragraph-sm-weight);
244
- line-height: var(--font-paragraph-sm-line-height);
245
- letter-spacing: var(--font-paragraph-sm-letter-spacing);
246
- color: var(--color-text-tertiary);
247
- margin: 0;
248
- }
249
-
250
227
  /* ============================================
251
228
  ERROR STATE
252
229
  ============================================ */
@@ -255,18 +232,10 @@
255
232
  border-color: var(--color-status-error-border);
256
233
  }
257
234
 
258
- .ds-file-input--error .ds-file-input__helper {
259
- color: var(--color-status-error-border);
260
- }
261
-
262
235
  /* ============================================
263
236
  DISABLED STATE
264
237
  ============================================ */
265
238
 
266
- .ds-file-input--disabled .ds-file-input__label {
267
- color: var(--color-input-text-disabled);
268
- }
269
-
270
239
  .ds-file-input--disabled .ds-file-input__dropzone {
271
240
  background-color: var(--color-input-bg-disabled);
272
241
  border-color: var(--color-input-border-disabled);
@@ -287,11 +256,6 @@
287
256
  padding: var(--padding-lg); /* 20px */
288
257
  }
289
258
 
290
- .ds-file-input--compact .ds-file-input__label {
291
- font-size: var(--font-paragraph-sm-size);
292
- line-height: var(--font-paragraph-sm-line-height);
293
- }
294
-
295
259
  .ds-file-input--compact .ds-file-input__dropzone-icon {
296
260
  --icon-size: var(--icon-size-sm);
297
261
  }
@@ -11,23 +11,16 @@ export interface FileInputFile {
11
11
  /** Per-file error message — replaces the size line */
12
12
  error?: string;
13
13
  }
14
- export interface FileInputProps {
14
+ /** Props owned by FileInput itself — everything else falls through to the native <input type="file">. */
15
+ type FileInputOwnProps = {
15
16
  /** Field label above the dropzone */
16
17
  label?: string;
17
18
  /** Instructional copy inside the dropzone */
18
19
  placeholder?: string;
19
- /** Comma-separated accept list, e.g. 'image/*,.pdf' */
20
- accept?: string;
21
- /** Allow selecting more than one file */
22
- multiple?: boolean;
23
20
  /** Files to list under the dropzone */
24
21
  files?: FileInputFile[];
25
- /** Component size */
22
+ /** Component size (not the native character-width `size` attribute) */
26
23
  size?: 'default' | 'compact';
27
- /** Whether the input is disabled */
28
- disabled?: boolean;
29
- /** Whether the input is required */
30
- required?: boolean;
31
24
  /** Error state — shows error styling and message */
32
25
  error?: boolean;
33
26
  /** Helper or error message displayed below the field */
@@ -36,19 +29,22 @@ export interface FileInputProps {
36
29
  onFilesSelected?: (files: File[]) => void;
37
30
  /** Called with the id of a file whose remove button was pressed */
38
31
  onFileRemove?: (id: string) => void;
39
- /** Additional CSS classes */
32
+ /** Additional CSS classes — applied to the wrapper, not the <input> */
40
33
  className?: string;
41
- /** Accessible name override */
34
+ /** @deprecated Pass the native `aria-label` attribute instead. */
42
35
  ariaLabel?: string;
43
- /** HTML name attribute */
44
- name?: string;
45
- /** HTML id attribute */
46
- id?: string;
36
+ };
37
+ export interface FileInputProps extends FileInputOwnProps, Omit<React.ComponentPropsWithoutRef<'input'>, keyof FileInputOwnProps | 'type'> {
47
38
  }
48
39
  /**
49
40
  * FileInput component — a click-or-drop zone paired with a list of the
50
41
  * chosen files. The list is fully controlled: pass `files` and handle
51
42
  * `onFilesSelected` / `onFileRemove` so upload progress and per-file
52
43
  * errors stay owned by the host app.
44
+ *
45
+ * Forwards a ref to the underlying `<input type="file">` — useful for
46
+ * programmatically opening the picker or clearing the selection — and spreads
47
+ * unrecognised props onto it.
53
48
  */
54
- export declare const FileInput: ({ label, placeholder, accept, multiple, files, size, disabled, required, error, helperText, onFilesSelected, onFileRemove, className, ariaLabel, name, id, }: FileInputProps) => React.JSX.Element;
49
+ export declare const FileInput: React.ForwardRefExoticComponent<FileInputProps & React.RefAttributes<HTMLInputElement>>;
50
+ export {};
@@ -1,5 +1,6 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
- import { useState, useRef, useId } from "react";
2
+ import React, { useState, useRef, useId } from "react";
3
+ import { Field } from "../Field/Field.js";
3
4
  import "./FileInput.css";
4
5
  import "../../fonts/material-symbols.css";
5
6
  const formatBytes = (bytes) => {
@@ -9,101 +10,126 @@ const formatBytes = (bytes) => {
9
10
  const value = bytes / Math.pow(1024, exponent);
10
11
  return `${exponent === 0 ? value : value.toFixed(1)} ${units[exponent]}`;
11
12
  };
12
- const FileInput = ({
13
- label,
14
- placeholder = "Drag and drop, or click to browse",
15
- accept,
16
- multiple = false,
17
- files = [],
18
- size = "default",
19
- disabled = false,
20
- required = false,
21
- error = false,
22
- helperText,
23
- onFilesSelected,
24
- onFileRemove,
25
- className = "",
26
- ariaLabel,
27
- name,
28
- id
29
- }) => {
30
- const [isDragging, setIsDragging] = useState(false);
31
- const inputRef = useRef(null);
32
- const generatedId = useId();
33
- const inputId = id || name || generatedId;
34
- const baseClass = "ds-file-input";
35
- const emitFiles = (fileList) => {
36
- if (!fileList || fileList.length === 0) return;
37
- onFilesSelected?.(Array.from(fileList));
38
- };
39
- const handleChange = (e) => {
40
- emitFiles(e.target.files);
41
- e.target.value = "";
42
- };
43
- const handleDrop = (e) => {
44
- e.preventDefault();
45
- setIsDragging(false);
46
- if (disabled) return;
47
- emitFiles(e.dataTransfer.files);
48
- };
49
- const handleDragOver = (e) => {
50
- e.preventDefault();
51
- if (!disabled) setIsDragging(true);
52
- };
53
- const handleDragLeave = (e) => {
54
- if (e.currentTarget.contains(e.relatedTarget)) return;
55
- setIsDragging(false);
56
- };
57
- const openPicker = () => {
58
- if (!disabled) inputRef.current?.click();
59
- };
60
- const handleKeyDown = (e) => {
61
- if (e.key === "Enter" || e.key === " ") {
13
+ const FileInput = React.forwardRef(
14
+ ({
15
+ label,
16
+ placeholder = "Drag and drop, or click to browse",
17
+ accept,
18
+ multiple = false,
19
+ files = [],
20
+ size = "default",
21
+ disabled = false,
22
+ required = false,
23
+ error = false,
24
+ helperText,
25
+ onFilesSelected,
26
+ onFileRemove,
27
+ onChange,
28
+ className = "",
29
+ ariaLabel,
30
+ name,
31
+ id,
32
+ ...rest
33
+ }, ref) => {
34
+ const [isDragging, setIsDragging] = useState(false);
35
+ const inputRef = useRef(null);
36
+ const generatedId = useId();
37
+ const inputId = id || name || generatedId;
38
+ const baseClass = "ds-file-input";
39
+ const setInputRef = (node) => {
40
+ inputRef.current = node;
41
+ if (typeof ref === "function") ref(node);
42
+ else if (ref) ref.current = node;
43
+ };
44
+ const emitFiles = (fileList) => {
45
+ if (!fileList || fileList.length === 0) return;
46
+ onFilesSelected?.(Array.from(fileList));
47
+ };
48
+ const handleChange = (e) => {
49
+ onChange?.(e);
50
+ emitFiles(e.target.files);
51
+ e.target.value = "";
52
+ };
53
+ const handleDrop = (e) => {
62
54
  e.preventDefault();
63
- openPicker();
64
- }
65
- };
66
- const classes = [
67
- baseClass,
68
- size === "compact" ? `${baseClass}--compact` : "",
69
- error ? `${baseClass}--error` : "",
70
- disabled ? `${baseClass}--disabled` : "",
71
- isDragging ? `${baseClass}--dragging` : "",
72
- className
73
- ].filter(Boolean).join(" ");
74
- return /* @__PURE__ */ jsxs("div", { className: classes, children: [
75
- label && /* @__PURE__ */ jsxs("label", { className: `${baseClass}__label`, htmlFor: inputId, children: [
76
- label,
77
- required && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__required`, "aria-hidden": "true", children: [
78
- " ",
79
- "*"
80
- ] })
81
- ] }),
82
- /* @__PURE__ */ jsxs(
83
- "div",
55
+ setIsDragging(false);
56
+ if (disabled) return;
57
+ emitFiles(e.dataTransfer.files);
58
+ };
59
+ const handleDragOver = (e) => {
60
+ e.preventDefault();
61
+ if (!disabled) setIsDragging(true);
62
+ };
63
+ const handleDragLeave = (e) => {
64
+ if (e.currentTarget.contains(e.relatedTarget)) return;
65
+ setIsDragging(false);
66
+ };
67
+ const openPicker = () => {
68
+ if (!disabled) inputRef.current?.click();
69
+ };
70
+ const handleKeyDown = (e) => {
71
+ if (e.key === "Enter" || e.key === " ") {
72
+ e.preventDefault();
73
+ openPicker();
74
+ }
75
+ };
76
+ const classes = [
77
+ baseClass,
78
+ size === "compact" ? `${baseClass}--compact` : "",
79
+ error ? `${baseClass}--error` : "",
80
+ disabled ? `${baseClass}--disabled` : "",
81
+ isDragging ? `${baseClass}--dragging` : "",
82
+ className
83
+ ].filter(Boolean).join(" ");
84
+ return /* @__PURE__ */ jsxs(
85
+ Field,
84
86
  {
85
- className: `${baseClass}__dropzone`,
86
- role: "button",
87
- tabIndex: disabled ? -1 : 0,
88
- "aria-label": ariaLabel || placeholder,
89
- "aria-describedby": helperText ? `${inputId}-helper` : void 0,
90
- "aria-disabled": disabled || void 0,
91
- onClick: openPicker,
92
- onKeyDown: handleKeyDown,
93
- onDrop: handleDrop,
94
- onDragOver: handleDragOver,
95
- onDragLeave: handleDragLeave,
87
+ className: classes,
88
+ label,
89
+ helperText,
90
+ error,
91
+ required,
92
+ disabled,
93
+ size,
94
+ id: inputId,
96
95
  children: [
97
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__dropzone-icon material-symbols-rounded`, "aria-hidden": "true", children: "upload_file" }),
98
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__dropzone-text`, children: placeholder }),
99
- accept && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__dropzone-hint`, children: [
100
- "Accepts ",
101
- accept
102
- ] }),
96
+ /* @__PURE__ */ jsxs(
97
+ "div",
98
+ {
99
+ className: `${baseClass}__dropzone`,
100
+ role: "button",
101
+ tabIndex: disabled ? -1 : 0,
102
+ "aria-label": ariaLabel || rest["aria-label"] || placeholder,
103
+ "aria-describedby": helperText ? `${inputId}-helper` : void 0,
104
+ "aria-invalid": error || void 0,
105
+ "aria-disabled": disabled || void 0,
106
+ onClick: openPicker,
107
+ onKeyDown: handleKeyDown,
108
+ onDrop: handleDrop,
109
+ onDragOver: handleDragOver,
110
+ onDragLeave: handleDragLeave,
111
+ children: [
112
+ /* @__PURE__ */ jsx(
113
+ "span",
114
+ {
115
+ className: `${baseClass}__dropzone-icon material-symbols-rounded`,
116
+ "aria-hidden": "true",
117
+ children: "upload_file"
118
+ }
119
+ ),
120
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__dropzone-text`, children: placeholder }),
121
+ accept && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__dropzone-hint`, children: [
122
+ "Accepts ",
123
+ accept
124
+ ] })
125
+ ]
126
+ }
127
+ ),
103
128
  /* @__PURE__ */ jsx(
104
129
  "input",
105
130
  {
106
- ref: inputRef,
131
+ ...rest,
132
+ ref: setInputRef,
107
133
  id: inputId,
108
134
  name,
109
135
  type: "file",
@@ -113,65 +139,66 @@ const FileInput = ({
113
139
  disabled,
114
140
  required,
115
141
  tabIndex: -1,
142
+ "aria-label": label ? void 0 : ariaLabel || rest["aria-label"] || placeholder,
116
143
  onChange: handleChange
117
144
  }
118
- )
119
- ]
120
- }
121
- ),
122
- files.length > 0 && /* @__PURE__ */ jsx("ul", { className: `${baseClass}__list`, children: files.map((file) => /* @__PURE__ */ jsxs(
123
- "li",
124
- {
125
- className: [`${baseClass}__file`, file.error ? `${baseClass}__file--error` : ""].filter(Boolean).join(" "),
126
- children: [
127
- /* @__PURE__ */ jsx(
128
- "span",
129
- {
130
- className: `${baseClass}__file-icon material-symbols-rounded`,
131
- "aria-hidden": "true",
132
- children: file.error ? "error" : "description"
133
- }
134
145
  ),
135
- /* @__PURE__ */ jsxs("span", { className: `${baseClass}__file-details`, children: [
136
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__file-name`, children: file.name }),
137
- file.error ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__file-error`, children: file.error }) : file.size !== void 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__file-size`, children: formatBytes(file.size) }),
138
- file.error === void 0 && file.progress !== void 0 && /* @__PURE__ */ jsx(
139
- "span",
140
- {
141
- className: `${baseClass}__progress`,
142
- role: "progressbar",
143
- "aria-valuenow": file.progress,
144
- "aria-valuemin": 0,
145
- "aria-valuemax": 100,
146
- "aria-label": `Uploading ${file.name}`,
147
- children: /* @__PURE__ */ jsx(
146
+ files.length > 0 && /* @__PURE__ */ jsx("ul", { className: `${baseClass}__list`, children: files.map((file) => /* @__PURE__ */ jsxs(
147
+ "li",
148
+ {
149
+ className: [`${baseClass}__file`, file.error ? `${baseClass}__file--error` : ""].filter(Boolean).join(" "),
150
+ children: [
151
+ /* @__PURE__ */ jsx(
148
152
  "span",
149
153
  {
150
- className: `${baseClass}__progress-fill`,
151
- style: { width: `${Math.min(100, Math.max(0, file.progress))}%` }
154
+ className: `${baseClass}__file-icon material-symbols-rounded`,
155
+ "aria-hidden": "true",
156
+ children: file.error ? "error" : "description"
157
+ }
158
+ ),
159
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__file-details`, children: [
160
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__file-name`, children: file.name }),
161
+ file.error ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__file-error`, children: file.error }) : file.size !== void 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__file-size`, children: formatBytes(file.size) }),
162
+ file.error === void 0 && file.progress !== void 0 && /* @__PURE__ */ jsx(
163
+ "span",
164
+ {
165
+ className: `${baseClass}__progress`,
166
+ role: "progressbar",
167
+ "aria-valuenow": file.progress,
168
+ "aria-valuemin": 0,
169
+ "aria-valuemax": 100,
170
+ "aria-label": `Uploading ${file.name}`,
171
+ children: /* @__PURE__ */ jsx(
172
+ "span",
173
+ {
174
+ className: `${baseClass}__progress-fill`,
175
+ style: { width: `${Math.min(100, Math.max(0, file.progress))}%` }
176
+ }
177
+ )
178
+ }
179
+ )
180
+ ] }),
181
+ onFileRemove && /* @__PURE__ */ jsx(
182
+ "button",
183
+ {
184
+ type: "button",
185
+ className: `${baseClass}__file-remove`,
186
+ onClick: () => onFileRemove(file.id),
187
+ disabled,
188
+ "aria-label": `Remove ${file.name}`,
189
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", "aria-hidden": "true", children: "close" })
152
190
  }
153
191
  )
154
- }
155
- )
156
- ] }),
157
- onFileRemove && /* @__PURE__ */ jsx(
158
- "button",
159
- {
160
- type: "button",
161
- className: `${baseClass}__file-remove`,
162
- onClick: () => onFileRemove(file.id),
163
- disabled,
164
- "aria-label": `Remove ${file.name}`,
165
- children: /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", "aria-hidden": "true", children: "close" })
166
- }
167
- )
192
+ ]
193
+ },
194
+ file.id
195
+ )) })
168
196
  ]
169
- },
170
- file.id
171
- )) }),
172
- helperText && /* @__PURE__ */ jsx("p", { className: `${baseClass}__helper`, id: `${inputId}-helper`, children: helperText })
173
- ] });
174
- };
197
+ }
198
+ );
199
+ }
200
+ );
201
+ FileInput.displayName = "FileInput";
175
202
  export {
176
203
  FileInput
177
204
  };
@@ -10,23 +10,6 @@
10
10
  width: 100%;
11
11
  }
12
12
 
13
- /* ============================================
14
- LABEL
15
- ============================================ */
16
-
17
- .ds-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-input__required {
27
- color: var(--color-core-accent-coral);
28
- }
29
-
30
13
  /* ============================================
31
14
  FIELD WRAPPER
32
15
  ============================================ */
@@ -103,20 +86,6 @@
103
86
  padding-right: calc(var(--padding-md) + 24px + var(--gap-sm)); /* 16px + 24px icon + 8px gap */
104
87
  }
105
88
 
106
- /* ============================================
107
- HELPER TEXT
108
- ============================================ */
109
-
110
- .ds-input__helper {
111
- font-family: var(--font-paragraph-sm-family);
112
- font-size: var(--font-paragraph-sm-size);
113
- font-weight: var(--font-paragraph-sm-weight);
114
- line-height: var(--font-paragraph-sm-line-height);
115
- letter-spacing: var(--font-paragraph-sm-letter-spacing);
116
- color: var(--color-text-tertiary);
117
- margin: 0;
118
- }
119
-
120
89
  /* ============================================
121
90
  ERROR STATE
122
91
  ============================================ */
@@ -129,18 +98,10 @@
129
98
  border-color: var(--color-status-error-border);
130
99
  }
131
100
 
132
- .ds-input--error .ds-input__helper {
133
- color: var(--color-status-error-border);
134
- }
135
-
136
101
  /* ============================================
137
102
  DISABLED STATE
138
103
  ============================================ */
139
104
 
140
- .ds-input--disabled .ds-input__label {
141
- color: var(--color-input-text-disabled);
142
- }
143
-
144
105
  .ds-input--disabled .ds-input__field {
145
106
  background-color: var(--color-input-bg-disabled);
146
107
  border-color: var(--color-input-border-disabled);
@@ -187,7 +148,3 @@
187
148
  padding-right: calc(var(--padding-sm-md) + 20px + var(--gap-sm)); /* 12px + 20px icon + 8px gap */
188
149
  }
189
150
 
190
- .ds-input--compact .ds-input__label {
191
- font-size: var(--font-paragraph-sm-size);
192
- line-height: var(--font-paragraph-sm-line-height);
193
- }
@@ -1,19 +1,14 @@
1
1
  import { default as React } from 'react';
2
- export interface InputProps {
2
+ /** Props owned by Input itself — everything else falls through to the <input>. */
3
+ type InputOwnProps = {
3
4
  /** Input label text */
4
5
  label?: string;
5
- /** Placeholder text */
6
- placeholder?: string;
7
6
  /** Current value */
8
7
  value?: string;
9
- /** Input type */
8
+ /** Input type — curated subset; use a dedicated component for checkbox/radio/file */
10
9
  type?: 'text' | 'email' | 'password' | 'number' | 'search' | 'tel' | 'url';
11
- /** Component size */
10
+ /** Component size (not the native character-width `size` attribute) */
12
11
  size?: 'default' | 'compact';
13
- /** Whether the input is disabled */
14
- disabled?: boolean;
15
- /** Whether the input is required */
16
- required?: boolean;
17
12
  /** Error state — shows error styling and message */
18
13
  error?: boolean;
19
14
  /** Helper or error message displayed below the input */
@@ -22,19 +17,25 @@ export interface InputProps {
22
17
  iconLeft?: string;
23
18
  /** Material Symbol icon name on the right */
24
19
  iconRight?: string;
25
- /** Callback when value changes */
26
- onChange?: (value: string) => void;
27
- /** Callback on focus */
28
- onFocus?: () => void;
29
- /** Callback on blur */
30
- onBlur?: () => void;
31
- /** Additional CSS classes */
20
+ /**
21
+ * Convenience callback receiving the value directly.
22
+ * Fires alongside `onChange`, which keeps the standard React event signature
23
+ * so form libraries (react-hook-form, Formik, TanStack Form) work unmodified.
24
+ */
25
+ onValueChange?: (value: string) => void;
26
+ /** Additional CSS classes — applied to the wrapper, not the <input> */
32
27
  className?: string;
33
- /** Accessible name override */
28
+ /** @deprecated Pass the native `aria-label` attribute instead. */
34
29
  ariaLabel?: string;
35
- /** HTML name attribute */
36
- name?: string;
37
- /** HTML id attribute */
38
- id?: string;
30
+ };
31
+ export interface InputProps extends InputOwnProps, Omit<React.ComponentPropsWithoutRef<'input'>, keyof InputOwnProps> {
39
32
  }
40
- export declare const Input: ({ label, placeholder, value, type, size, disabled, required, error, helperText, iconLeft, iconRight, onChange, onFocus, onBlur, className, ariaLabel, name, id, }: InputProps) => React.JSX.Element;
33
+ /**
34
+ * Text input with optional label, helper text, icons and error state.
35
+ *
36
+ * Forwards a ref to the underlying `<input>` and spreads unrecognised props onto
37
+ * it, so `autoComplete`, `maxLength`, `inputMode`, `data-*` and form-library
38
+ * registration all work.
39
+ */
40
+ export declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
41
+ export {};