@robr0/design-system 0.2.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 (66) hide show
  1. package/README.md +8 -6
  2. package/components/Accordion/Accordion.js +0 -1
  3. package/components/Avatar/Avatar.js +1 -0
  4. package/components/Breadcrumb/Breadcrumb.d.ts +7 -1
  5. package/components/Breadcrumb/Breadcrumb.js +3 -2
  6. package/components/Button/Button.css +18 -0
  7. package/components/Button/Button.d.ts +6 -0
  8. package/components/Button/Button.js +13 -3
  9. package/components/Card/Card.css +6 -0
  10. package/components/CircularButton/CircularButton.css +11 -0
  11. package/components/CircularButton/CircularButton.d.ts +7 -1
  12. package/components/CircularButton/CircularButton.js +15 -4
  13. package/components/CodeBlock/CodeBlock.js +10 -1
  14. package/components/ColorPicker/ColorPicker.css +278 -0
  15. package/components/ColorPicker/ColorPicker.d.ts +50 -0
  16. package/components/ColorPicker/ColorPicker.js +338 -0
  17. package/components/Combobox/Combobox.css +0 -36
  18. package/components/Combobox/Combobox.js +90 -82
  19. package/components/CommandPalette/CommandPalette.css +1 -15
  20. package/components/CommandPalette/CommandPalette.js +6 -5
  21. package/components/ContextMenu/ContextMenu.css +262 -0
  22. package/components/ContextMenu/ContextMenu.d.ts +26 -0
  23. package/components/ContextMenu/ContextMenu.js +350 -0
  24. package/components/DateInput/DateInput.css +0 -36
  25. package/components/DateInput/DateInput.js +37 -33
  26. package/components/DatePicker/DatePicker.js +45 -34
  27. package/components/Dialog/Dialog.js +5 -4
  28. package/components/Drawer/Drawer.js +7 -2
  29. package/components/Dropdown/Dropdown.css +0 -36
  30. package/components/Dropdown/Dropdown.js +119 -109
  31. package/components/DropdownMenu/DropdownMenu.js +10 -7
  32. package/components/Field/Field.css +80 -0
  33. package/components/Field/Field.d.ts +50 -0
  34. package/components/Field/Field.js +58 -0
  35. package/components/Field/FieldContext.d.ts +42 -0
  36. package/components/Field/FieldContext.js +7 -0
  37. package/components/FileInput/FileInput.css +0 -36
  38. package/components/FileInput/FileInput.js +111 -103
  39. package/components/Input/Input.css +0 -43
  40. package/components/Input/Input.js +51 -46
  41. package/components/Kbd/Kbd.css +28 -0
  42. package/components/Kbd/Kbd.d.ts +19 -0
  43. package/components/Kbd/Kbd.js +14 -0
  44. package/components/Popover/Popover.d.ts +0 -6
  45. package/components/Popover/Popover.js +22 -16
  46. package/components/ProgressBar/ProgressBar.js +1 -1
  47. package/components/SelectionCard/SelectionCard.css +6 -2
  48. package/components/Skeleton/Skeleton.css +2 -0
  49. package/components/Spinner/Spinner.css +11 -0
  50. package/components/Spinner/Spinner.d.ts +2 -2
  51. package/components/Swatch/Swatch.css +52 -0
  52. package/components/Swatch/Swatch.d.ts +34 -0
  53. package/components/Swatch/Swatch.js +44 -0
  54. package/components/Table/Table.css +19 -0
  55. package/components/Table/Table.js +1 -1
  56. package/components/Textarea/Textarea.css +0 -43
  57. package/components/Textarea/Textarea.js +37 -35
  58. package/components/ToggleSwitch/ToggleSwitch.css +7 -2
  59. package/components/registry.d.ts +47 -10
  60. package/components/registry.js +5 -1
  61. package/components/registry.json +498 -56
  62. package/components/registry.json.d.ts +498 -56
  63. package/components/registry.json.js +4 -1
  64. package/index.d.ts +8 -1
  65. package/index.js +16 -1
  66. package/package.json +1 -1
@@ -0,0 +1,50 @@
1
+ import { default as React } from 'react';
2
+ /** Props owned by Field itself — everything else falls through to the wrapper. */
3
+ type FieldOwnProps = {
4
+ /** Label text rendered above the control */
5
+ label?: string;
6
+ /** The form control this field labels */
7
+ children?: React.ReactNode;
8
+ /** Helper or error message rendered below the control */
9
+ helperText?: string;
10
+ /**
11
+ * Optional content rendered opposite the helper text — a character counter,
12
+ * a unit, a "0/280". Present only when supplied; without it the helper sits
13
+ * directly in the field's column and no extra wrapper is introduced.
14
+ */
15
+ aside?: React.ReactNode;
16
+ /** Error state — recolours the helper text and marks the control invalid */
17
+ error?: boolean;
18
+ /** Marks the field required and renders the required marker */
19
+ required?: boolean;
20
+ /** Whether the control is disabled — dims the label */
21
+ disabled?: boolean;
22
+ /** Component size */
23
+ size?: 'default' | 'compact';
24
+ /**
25
+ * id for the control. Generated when omitted, so the label/control/helper
26
+ * association works with no configuration.
27
+ */
28
+ id?: string;
29
+ /** Additional CSS classes */
30
+ className?: string;
31
+ };
32
+ export interface FieldProps extends FieldOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof FieldOwnProps> {
33
+ }
34
+ /**
35
+ * Field owns the scaffolding shared by every labelled form control: the label
36
+ * and its `htmlFor`, the required marker, the helper/error message, the ids
37
+ * that tie them together, and the `aria-describedby` / `aria-invalid` wiring.
38
+ *
39
+ * Controls compose inside it and read the wiring through `useField()` rather
40
+ * than deriving ids themselves — which is what keeps accessibility correct
41
+ * systemically instead of one component at a time.
42
+ *
43
+ * ```tsx
44
+ * <Field label="Email" helperText="We never share it." required>
45
+ * <MyControl />
46
+ * </Field>
47
+ * ```
48
+ */
49
+ export declare const Field: React.ForwardRefExoticComponent<FieldProps & React.RefAttributes<HTMLDivElement>>;
50
+ export {};
@@ -0,0 +1,58 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import React, { useId } from "react";
3
+ import { FieldContext } from "./FieldContext.js";
4
+ import "./Field.css";
5
+ const Field = React.forwardRef(
6
+ ({
7
+ label,
8
+ children,
9
+ helperText,
10
+ aside,
11
+ error = false,
12
+ required = false,
13
+ disabled = false,
14
+ size = "default",
15
+ id,
16
+ className = "",
17
+ ...rest
18
+ }, ref) => {
19
+ const baseClass = "ds-field";
20
+ const generatedId = useId();
21
+ const controlId = id || generatedId;
22
+ const describedBy = helperText ? `${controlId}-helper` : void 0;
23
+ const labelId = label ? `${controlId}-label` : void 0;
24
+ const classes = [
25
+ baseClass,
26
+ size === "compact" ? `${baseClass}--compact` : "",
27
+ error ? `${baseClass}--error` : "",
28
+ disabled ? `${baseClass}--disabled` : "",
29
+ className
30
+ ].filter(Boolean).join(" ");
31
+ const context = {
32
+ controlId,
33
+ labelId,
34
+ describedBy,
35
+ invalid: error,
36
+ required,
37
+ disabled
38
+ };
39
+ return /* @__PURE__ */ jsx(FieldContext.Provider, { value: context, children: /* @__PURE__ */ jsxs("div", { ...rest, ref, className: classes, children: [
40
+ label && /* @__PURE__ */ jsxs("label", { className: `${baseClass}__label`, id: labelId, htmlFor: controlId, children: [
41
+ label,
42
+ required && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__required`, "aria-hidden": "true", children: [
43
+ " ",
44
+ "*"
45
+ ] })
46
+ ] }),
47
+ children,
48
+ aside ? /* @__PURE__ */ jsxs("div", { className: `${baseClass}__footer`, children: [
49
+ helperText ? /* @__PURE__ */ jsx("p", { className: `${baseClass}__helper`, id: describedBy, children: helperText }) : /* @__PURE__ */ jsx("span", {}),
50
+ aside
51
+ ] }) : helperText && /* @__PURE__ */ jsx("p", { className: `${baseClass}__helper`, id: describedBy, children: helperText })
52
+ ] }) });
53
+ }
54
+ );
55
+ Field.displayName = "Field";
56
+ export {
57
+ Field
58
+ };
@@ -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
  }
@@ -1,5 +1,6 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
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) => {
@@ -80,114 +81,121 @@ const FileInput = React.forwardRef(
80
81
  isDragging ? `${baseClass}--dragging` : "",
81
82
  className
82
83
  ].filter(Boolean).join(" ");
83
- return /* @__PURE__ */ jsxs("div", { className: classes, children: [
84
- label && /* @__PURE__ */ jsxs("label", { className: `${baseClass}__label`, htmlFor: inputId, children: [
84
+ return /* @__PURE__ */ jsxs(
85
+ Field,
86
+ {
87
+ className: classes,
85
88
  label,
86
- required && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__required`, "aria-hidden": "true", children: [
87
- " ",
88
- "*"
89
- ] })
90
- ] }),
91
- /* @__PURE__ */ jsxs(
92
- "div",
93
- {
94
- className: `${baseClass}__dropzone`,
95
- role: "button",
96
- tabIndex: disabled ? -1 : 0,
97
- "aria-label": ariaLabel || rest["aria-label"] || placeholder,
98
- "aria-describedby": helperText ? `${inputId}-helper` : void 0,
99
- "aria-disabled": disabled || void 0,
100
- onClick: openPicker,
101
- onKeyDown: handleKeyDown,
102
- onDrop: handleDrop,
103
- onDragOver: handleDragOver,
104
- onDragLeave: handleDragLeave,
105
- children: [
106
- /* @__PURE__ */ jsx(
107
- "span",
108
- {
109
- className: `${baseClass}__dropzone-icon material-symbols-rounded`,
110
- "aria-hidden": "true",
111
- children: "upload_file"
112
- }
113
- ),
114
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__dropzone-text`, children: placeholder }),
115
- accept && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__dropzone-hint`, children: [
116
- "Accepts ",
117
- accept
118
- ] }),
119
- /* @__PURE__ */ jsx(
120
- "input",
121
- {
122
- ...rest,
123
- ref: setInputRef,
124
- id: inputId,
125
- name,
126
- type: "file",
127
- className: `${baseClass}__native`,
128
- accept,
129
- multiple,
130
- disabled,
131
- required,
132
- tabIndex: -1,
133
- onChange: handleChange
134
- }
135
- )
136
- ]
137
- }
138
- ),
139
- files.length > 0 && /* @__PURE__ */ jsx("ul", { className: `${baseClass}__list`, children: files.map((file) => /* @__PURE__ */ jsxs(
140
- "li",
141
- {
142
- className: [`${baseClass}__file`, file.error ? `${baseClass}__file--error` : ""].filter(Boolean).join(" "),
143
- children: [
144
- /* @__PURE__ */ jsx(
145
- "span",
146
- {
147
- className: `${baseClass}__file-icon material-symbols-rounded`,
148
- "aria-hidden": "true",
149
- children: file.error ? "error" : "description"
150
- }
151
- ),
152
- /* @__PURE__ */ jsxs("span", { className: `${baseClass}__file-details`, children: [
153
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__file-name`, children: file.name }),
154
- 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) }),
155
- file.error === void 0 && file.progress !== void 0 && /* @__PURE__ */ jsx(
156
- "span",
157
- {
158
- className: `${baseClass}__progress`,
159
- role: "progressbar",
160
- "aria-valuenow": file.progress,
161
- "aria-valuemin": 0,
162
- "aria-valuemax": 100,
163
- "aria-label": `Uploading ${file.name}`,
164
- children: /* @__PURE__ */ jsx(
89
+ helperText,
90
+ error,
91
+ required,
92
+ disabled,
93
+ size,
94
+ id: inputId,
95
+ children: [
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
+ ),
128
+ /* @__PURE__ */ jsx(
129
+ "input",
130
+ {
131
+ ...rest,
132
+ ref: setInputRef,
133
+ id: inputId,
134
+ name,
135
+ type: "file",
136
+ className: `${baseClass}__native`,
137
+ accept,
138
+ multiple,
139
+ disabled,
140
+ required,
141
+ tabIndex: -1,
142
+ "aria-label": label ? void 0 : ariaLabel || rest["aria-label"] || placeholder,
143
+ onChange: handleChange
144
+ }
145
+ ),
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(
152
+ "span",
153
+ {
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(
165
163
  "span",
166
164
  {
167
- className: `${baseClass}__progress-fill`,
168
- style: { width: `${Math.min(100, Math.max(0, file.progress))}%` }
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
+ )
169
178
  }
170
179
  )
171
- }
172
- )
173
- ] }),
174
- onFileRemove && /* @__PURE__ */ jsx(
175
- "button",
176
- {
177
- type: "button",
178
- className: `${baseClass}__file-remove`,
179
- onClick: () => onFileRemove(file.id),
180
- disabled,
181
- "aria-label": `Remove ${file.name}`,
182
- children: /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", "aria-hidden": "true", children: "close" })
183
- }
184
- )
185
- ]
186
- },
187
- file.id
188
- )) }),
189
- helperText && /* @__PURE__ */ jsx("p", { className: `${baseClass}__helper`, id: `${inputId}-helper`, children: helperText })
190
- ] });
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" })
190
+ }
191
+ )
192
+ ]
193
+ },
194
+ file.id
195
+ )) })
196
+ ]
197
+ }
198
+ );
191
199
  }
192
200
  );
193
201
  FileInput.displayName = "FileInput";
@@ -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,5 +1,6 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import React from "react";
3
+ import { Field } from "../Field/Field.js";
3
4
  import "./Input.css";
4
5
  import "../../fonts/material-symbols.css";
5
6
  const Input = React.forwardRef(
@@ -38,53 +39,57 @@ const Input = React.forwardRef(
38
39
  onValueChange?.(e.target.value);
39
40
  };
40
41
  const inputId = id || name || label?.toLowerCase().replace(/\s+/g, "-");
41
- return /* @__PURE__ */ jsxs("div", { className: classes, children: [
42
- label && /* @__PURE__ */ jsxs("label", { className: `${baseClass}__label`, htmlFor: inputId, children: [
42
+ const describedBy = helperText ? `${inputId}-helper` : rest["aria-describedby"];
43
+ return /* @__PURE__ */ jsx(
44
+ Field,
45
+ {
46
+ className: classes,
43
47
  label,
44
- required && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__required`, "aria-hidden": "true", children: [
45
- " ",
46
- "*"
48
+ helperText,
49
+ error,
50
+ required,
51
+ disabled,
52
+ size,
53
+ id: inputId,
54
+ children: /* @__PURE__ */ jsxs("div", { className: `${baseClass}__field-wrapper`, children: [
55
+ iconLeft && /* @__PURE__ */ jsx(
56
+ "span",
57
+ {
58
+ className: `${baseClass}__icon ${baseClass}__icon--left material-symbols-rounded`,
59
+ "aria-hidden": "true",
60
+ children: iconLeft
61
+ }
62
+ ),
63
+ /* @__PURE__ */ jsx(
64
+ "input",
65
+ {
66
+ ...rest,
67
+ ref,
68
+ className: `${baseClass}__field`,
69
+ type,
70
+ id: inputId,
71
+ name,
72
+ value,
73
+ placeholder,
74
+ disabled,
75
+ required,
76
+ "aria-label": ariaLabel || rest["aria-label"] || label,
77
+ "aria-invalid": error,
78
+ "aria-describedby": describedBy,
79
+ onChange: handleChange
80
+ }
81
+ ),
82
+ iconRight && /* @__PURE__ */ jsx(
83
+ "span",
84
+ {
85
+ className: `${baseClass}__icon ${baseClass}__icon--right material-symbols-rounded`,
86
+ "aria-hidden": "true",
87
+ children: iconRight
88
+ }
89
+ )
47
90
  ] })
48
- ] }),
49
- /* @__PURE__ */ jsxs("div", { className: `${baseClass}__field-wrapper`, children: [
50
- iconLeft && /* @__PURE__ */ jsx(
51
- "span",
52
- {
53
- className: `${baseClass}__icon ${baseClass}__icon--left material-symbols-rounded`,
54
- "aria-hidden": "true",
55
- children: iconLeft
56
- }
57
- ),
58
- /* @__PURE__ */ jsx(
59
- "input",
60
- {
61
- ...rest,
62
- ref,
63
- className: `${baseClass}__field`,
64
- type,
65
- id: inputId,
66
- name,
67
- value,
68
- placeholder,
69
- disabled,
70
- required,
71
- "aria-label": ariaLabel || rest["aria-label"] || label,
72
- "aria-invalid": error,
73
- "aria-describedby": helperText ? `${inputId}-helper` : rest["aria-describedby"],
74
- onChange: handleChange
75
- }
76
- ),
77
- iconRight && /* @__PURE__ */ jsx(
78
- "span",
79
- {
80
- className: `${baseClass}__icon ${baseClass}__icon--right material-symbols-rounded`,
81
- "aria-hidden": "true",
82
- children: iconRight
83
- }
84
- )
85
- ] }),
86
- helperText && /* @__PURE__ */ jsx("p", { className: `${baseClass}__helper`, id: `${inputId}-helper`, children: helperText })
87
- ] });
91
+ }
92
+ );
88
93
  }
89
94
  );
90
95
  Input.displayName = "Input";
@@ -0,0 +1,28 @@
1
+ /* ============================================
2
+ KBD COMPONENT
3
+ A single keyboard key rendered as a keycap
4
+ ============================================ */
5
+
6
+ /* Base */
7
+ .ds-kbd {
8
+ display: inline-flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ min-width: 24px;
12
+ padding: var(--padding-xxxs) var(--padding-xs);
13
+ border: var(--border-xs) solid var(--color-bg-container-border);
14
+ border-radius: var(--radius-xs);
15
+ background-color: var(--color-bg-container-primary);
16
+ font-family: var(--font-paragraph-sm-family);
17
+ font-size: var(--font-paragraph-sm-size);
18
+ font-weight: var(--font-paragraph-sm-em-weight);
19
+ line-height: var(--font-paragraph-sm-line-height);
20
+ color: var(--color-text-tertiary);
21
+ white-space: nowrap;
22
+ }
23
+
24
+ /* Sizes */
25
+ .ds-kbd--compact {
26
+ min-width: 20px;
27
+ padding: var(--padding-xxxs) var(--padding-xxs);
28
+ }