@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
@@ -14,19 +14,6 @@
14
14
  LABEL
15
15
  ============================================ */
16
16
 
17
- .ds-textarea__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-textarea__required {
27
- color: var(--color-core-accent-coral);
28
- }
29
-
30
17
  /* ============================================
31
18
  FIELD
32
19
  ============================================ */
@@ -69,24 +56,6 @@
69
56
  FOOTER (helper + counter)
70
57
  ============================================ */
71
58
 
72
- .ds-textarea__footer {
73
- display: flex;
74
- align-items: flex-start;
75
- justify-content: space-between;
76
- gap: var(--gap-sm);
77
- }
78
-
79
- .ds-textarea__helper {
80
- font-family: var(--font-paragraph-sm-family);
81
- font-size: var(--font-paragraph-sm-size);
82
- font-weight: var(--font-paragraph-sm-weight);
83
- line-height: var(--font-paragraph-sm-line-height);
84
- letter-spacing: var(--font-paragraph-sm-letter-spacing);
85
- color: var(--color-text-tertiary);
86
- margin: 0;
87
- flex: 1;
88
- }
89
-
90
59
  .ds-textarea__counter {
91
60
  font-family: var(--font-paragraph-sm-family);
92
61
  font-size: var(--font-paragraph-sm-size);
@@ -108,10 +77,6 @@
108
77
  border-color: var(--color-status-error-border);
109
78
  }
110
79
 
111
- .ds-textarea--error .ds-textarea__helper {
112
- color: var(--color-status-error-border);
113
- }
114
-
115
80
  .ds-textarea--error .ds-textarea__counter {
116
81
  color: var(--color-status-error-border);
117
82
  }
@@ -120,10 +85,6 @@
120
85
  DISABLED STATE
121
86
  ============================================ */
122
87
 
123
- .ds-textarea--disabled .ds-textarea__label {
124
- color: var(--color-input-text-disabled);
125
- }
126
-
127
88
  .ds-textarea--disabled .ds-textarea__field {
128
89
  background-color: var(--color-input-bg-disabled);
129
90
  border-color: var(--color-input-border-disabled);
@@ -146,7 +107,3 @@
146
107
  letter-spacing: var(--font-paragraph-sm-letter-spacing);
147
108
  }
148
109
 
149
- .ds-textarea--compact .ds-textarea__label {
150
- font-size: var(--font-paragraph-sm-size);
151
- line-height: var(--font-paragraph-sm-line-height);
152
- }
@@ -1,19 +1,12 @@
1
1
  import { default as React } from 'react';
2
- export interface TextareaProps {
2
+ /** Props owned by Textarea itself — everything else falls through to the <textarea>. */
3
+ type TextareaOwnProps = {
3
4
  /** Textarea label text */
4
5
  label?: string;
5
- /** Placeholder text */
6
- placeholder?: string;
7
6
  /** Current value */
8
7
  value?: string;
9
- /** Number of visible rows */
10
- rows?: number;
11
8
  /** Component size */
12
9
  size?: 'default' | 'compact';
13
- /** Whether the textarea is disabled */
14
- disabled?: boolean;
15
- /** Whether the textarea is required */
16
- required?: boolean;
17
10
  /** Error state — shows error styling and message */
18
11
  error?: boolean;
19
12
  /** Helper or error message displayed below */
@@ -22,19 +15,25 @@ export interface TextareaProps {
22
15
  resize?: 'none' | 'vertical' | 'horizontal' | 'both';
23
16
  /** Max character count — shows counter when set */
24
17
  maxLength?: number;
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 */
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 <textarea> */
32
25
  className?: string;
33
- /** Accessible label override */
26
+ /** @deprecated Pass the native `aria-label` attribute instead. */
34
27
  ariaLabel?: string;
35
- /** HTML name attribute */
36
- name?: string;
37
- /** HTML id attribute */
38
- id?: string;
28
+ };
29
+ export interface TextareaProps extends TextareaOwnProps, Omit<React.ComponentPropsWithoutRef<'textarea'>, keyof TextareaOwnProps> {
39
30
  }
40
- export declare const Textarea: ({ label, placeholder, value, rows, size, disabled, required, error, helperText, resize, maxLength, onChange, onFocus, onBlur, className, ariaLabel, name, id, }: TextareaProps) => React.JSX.Element;
31
+ /**
32
+ * Multi-line text input with optional label, helper text, character counter
33
+ * and error state.
34
+ *
35
+ * Forwards a ref to the underlying `<textarea>` and spreads unrecognised props
36
+ * onto it.
37
+ */
38
+ export declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
39
+ export {};
@@ -1,74 +1,85 @@
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 "./Textarea.css";
4
- const Textarea = ({
5
- label,
6
- placeholder = "",
7
- value = "",
8
- rows = 4,
9
- size = "default",
10
- disabled = false,
11
- required = false,
12
- error = false,
13
- helperText,
14
- resize = "vertical",
15
- maxLength,
16
- onChange,
17
- onFocus,
18
- onBlur,
19
- className = "",
20
- ariaLabel,
21
- name,
22
- id
23
- }) => {
24
- const baseClass = "ds-textarea";
25
- const sizeClass = size === "compact" ? `${baseClass}--compact` : "";
26
- const errorClass = error ? `${baseClass}--error` : "";
27
- const disabledClass = disabled ? `${baseClass}--disabled` : "";
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
- const charCount = value?.length || 0;
36
- return /* @__PURE__ */ jsxs("div", { className: classes, children: [
37
- label && /* @__PURE__ */ jsxs("label", { className: `${baseClass}__label`, htmlFor: inputId, children: [
38
- label,
39
- required && /* @__PURE__ */ jsx("span", { className: `${baseClass}__required`, "aria-hidden": "true", children: " *" })
40
- ] }),
41
- /* @__PURE__ */ jsx(
42
- "textarea",
5
+ const Textarea = React.forwardRef(
6
+ ({
7
+ label,
8
+ placeholder = "",
9
+ value = "",
10
+ rows = 4,
11
+ size = "default",
12
+ disabled = false,
13
+ required = false,
14
+ error = false,
15
+ helperText,
16
+ resize = "vertical",
17
+ maxLength,
18
+ onChange,
19
+ onValueChange,
20
+ className = "",
21
+ ariaLabel,
22
+ name,
23
+ id,
24
+ style,
25
+ ...rest
26
+ }, ref) => {
27
+ const baseClass = "ds-textarea";
28
+ const classes = [
29
+ baseClass,
30
+ size === "compact" ? `${baseClass}--compact` : "",
31
+ error ? `${baseClass}--error` : "",
32
+ disabled ? `${baseClass}--disabled` : "",
33
+ className
34
+ ].filter(Boolean).join(" ");
35
+ const handleChange = (e) => {
36
+ onChange?.(e);
37
+ onValueChange?.(e.target.value);
38
+ };
39
+ const inputId = id || name || label?.toLowerCase().replace(/\s+/g, "-");
40
+ const charCount = value?.length || 0;
41
+ return /* @__PURE__ */ jsx(
42
+ Field,
43
43
  {
44
- className: `${baseClass}__field`,
45
- id: inputId,
46
- name,
47
- value,
48
- placeholder,
49
- rows,
50
- disabled,
44
+ className: classes,
45
+ label,
46
+ helperText,
47
+ error,
51
48
  required,
52
- maxLength,
53
- "aria-label": ariaLabel || label,
54
- "aria-invalid": error,
55
- "aria-describedby": helperText ? `${inputId}-helper` : void 0,
56
- onChange: handleChange,
57
- onFocus,
58
- onBlur,
59
- style: { resize }
49
+ disabled,
50
+ size,
51
+ id: inputId,
52
+ aside: maxLength ? /* @__PURE__ */ jsxs("span", { className: `${baseClass}__counter`, "aria-live": "polite", "aria-atomic": "true", children: [
53
+ charCount,
54
+ "/",
55
+ maxLength
56
+ ] }) : void 0,
57
+ children: /* @__PURE__ */ jsx(
58
+ "textarea",
59
+ {
60
+ ...rest,
61
+ ref,
62
+ className: `${baseClass}__field`,
63
+ id: inputId,
64
+ name,
65
+ value,
66
+ placeholder,
67
+ rows,
68
+ disabled,
69
+ required,
70
+ maxLength,
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
+ style: { resize, ...style }
76
+ }
77
+ )
60
78
  }
61
- ),
62
- /* @__PURE__ */ jsxs("div", { className: `${baseClass}__footer`, children: [
63
- helperText && /* @__PURE__ */ jsx("p", { className: `${baseClass}__helper`, id: `${inputId}-helper`, children: helperText }),
64
- maxLength && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__counter`, "aria-live": "polite", "aria-atomic": "true", children: [
65
- charCount,
66
- "/",
67
- maxLength
68
- ] })
69
- ] })
70
- ] });
71
- };
79
+ );
80
+ }
81
+ );
82
+ Textarea.displayName = "Textarea";
72
83
  export {
73
84
  Textarea
74
85
  };
@@ -1,3 +1,4 @@
1
+ import { default as React } from 'react';
1
2
  export interface ToggleGroupItem {
2
3
  /** Unique value */
3
4
  value: string;
@@ -6,25 +7,34 @@ export interface ToggleGroupItem {
6
7
  /** Use Material Symbol icon instead of text */
7
8
  icon?: boolean;
8
9
  }
9
- export interface ToggleGroupProps {
10
+ /** Props owned by ToggleGroup itself — everything else falls through to the wrapper. */
11
+ type ToggleGroupOwnProps = {
10
12
  /** Available items */
11
13
  items: ToggleGroupItem[];
12
14
  /** Currently active value(s) */
13
15
  value?: string | string[];
14
16
  /** Allow multiple selection */
15
17
  multiple?: boolean;
16
- /** Size */
18
+ /** Component size */
17
19
  size?: 'default' | 'compact';
18
- /** Disabled state */
20
+ /** Whether the whole group is disabled */
19
21
  disabled?: boolean;
20
- /** Change handler */
21
- onChange?: (value: string | string[]) => void;
22
- /** Accessible label for the group */
23
- ariaLabel?: string;
22
+ /** Called with the next selection — a string when single, an array when `multiple` */
23
+ onValueChange?: (value: string | string[]) => void;
24
24
  /** Additional CSS classes */
25
25
  className?: string;
26
+ /** @deprecated Use `onValueChange` instead. */
27
+ onChange?: (value: string | string[]) => void;
28
+ /** @deprecated Pass the native `aria-label` attribute instead. */
29
+ ariaLabel?: string;
30
+ };
31
+ export interface ToggleGroupProps extends ToggleGroupOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof ToggleGroupOwnProps> {
26
32
  }
27
33
  /**
28
34
  * ToggleGroup is a set of two-state buttons that can be toggled on or off.
35
+ *
36
+ * Forwards a ref to the wrapping group element and spreads unrecognised props
37
+ * onto it.
29
38
  */
30
- export declare const ToggleGroup: ({ items, value, multiple, size, disabled, onChange, ariaLabel, className, }: ToggleGroupProps) => import("react").JSX.Element;
39
+ export declare const ToggleGroup: React.ForwardRefExoticComponent<ToggleGroupProps & React.RefAttributes<HTMLDivElement>>;
40
+ export {};
@@ -1,53 +1,70 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
+ import React from "react";
2
3
  import "./ToggleGroup.css";
3
- const ToggleGroup = ({
4
- items,
5
- value = [],
6
- multiple = false,
7
- size = "default",
8
- disabled = false,
9
- onChange,
10
- ariaLabel,
11
- className = ""
12
- }) => {
13
- const baseClass = "ds-toggle-group";
14
- const classes = [
15
- baseClass,
16
- `${baseClass}--${size}`,
17
- disabled ? `${baseClass}--disabled` : "",
18
- className
19
- ].filter(Boolean).join(" ");
20
- const activeValues = Array.isArray(value) ? value : [value];
21
- const handleClick = (itemValue) => {
22
- if (disabled) return;
23
- if (multiple) {
24
- const next = activeValues.includes(itemValue) ? activeValues.filter((v) => v !== itemValue) : [...activeValues, itemValue];
25
- onChange?.(next);
26
- } else {
27
- onChange?.(itemValue);
28
- }
29
- };
30
- return /* @__PURE__ */ jsx("div", { className: classes, role: "group", "aria-label": ariaLabel, children: items.map((item) => {
31
- const isActive = activeValues.includes(item.value);
32
- const btnClass = [
33
- `${baseClass}__item`,
34
- isActive ? `${baseClass}__item--active` : ""
4
+ const ToggleGroup = React.forwardRef(
5
+ ({
6
+ items,
7
+ value = [],
8
+ multiple = false,
9
+ size = "default",
10
+ disabled = false,
11
+ onValueChange,
12
+ onChange,
13
+ ariaLabel,
14
+ className = "",
15
+ ...rest
16
+ }, ref) => {
17
+ const baseClass = "ds-toggle-group";
18
+ const classes = [
19
+ baseClass,
20
+ `${baseClass}--${size}`,
21
+ disabled ? `${baseClass}--disabled` : "",
22
+ className
35
23
  ].filter(Boolean).join(" ");
24
+ const activeValues = Array.isArray(value) ? value : [value];
25
+ const emit = (next) => {
26
+ onValueChange?.(next);
27
+ onChange?.(next);
28
+ };
29
+ const handleClick = (itemValue) => {
30
+ if (disabled) return;
31
+ if (multiple) {
32
+ const next = activeValues.includes(itemValue) ? activeValues.filter((v) => v !== itemValue) : [...activeValues, itemValue];
33
+ emit(next);
34
+ } else {
35
+ emit(itemValue);
36
+ }
37
+ };
36
38
  return /* @__PURE__ */ jsx(
37
- "button",
39
+ "div",
38
40
  {
39
- className: btnClass,
40
- onClick: () => handleClick(item.value),
41
- "aria-pressed": isActive,
42
- "aria-label": item.icon ? item.label : void 0,
43
- disabled,
44
- type: "button",
45
- children: item.icon ? /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", "aria-hidden": "true", children: item.label }) : item.label
46
- },
47
- item.value
41
+ ...rest,
42
+ ref,
43
+ className: classes,
44
+ role: "group",
45
+ "aria-label": ariaLabel || rest["aria-label"],
46
+ children: items.map((item) => {
47
+ const isActive = activeValues.includes(item.value);
48
+ const btnClass = [`${baseClass}__item`, isActive ? `${baseClass}__item--active` : ""].filter(Boolean).join(" ");
49
+ return /* @__PURE__ */ jsx(
50
+ "button",
51
+ {
52
+ className: btnClass,
53
+ onClick: () => handleClick(item.value),
54
+ "aria-pressed": isActive,
55
+ "aria-label": item.icon ? item.label : void 0,
56
+ disabled,
57
+ type: "button",
58
+ children: item.icon ? /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", "aria-hidden": "true", children: item.label }) : item.label
59
+ },
60
+ item.value
61
+ );
62
+ })
63
+ }
48
64
  );
49
- }) });
50
- };
65
+ }
66
+ );
67
+ ToggleGroup.displayName = "ToggleGroup";
51
68
  export {
52
69
  ToggleGroup
53
70
  };
@@ -59,7 +59,10 @@
59
59
  Set via --icon-size (not font-size): .material-symbols-rounded derives
60
60
  font-size, width, and height from it together. Overriding font-size
61
61
  alone leaves the box at the 24px default, so the glyph hangs off-centre
62
- inside the thumb. */
62
+ inside the thumb.
63
+
64
+ ds-allow(icon-size): 14px glyph inside the 20px thumb — the smallest
65
+ scale step would fill it edge to edge. */
63
66
  --icon-size: 14px;
64
67
  color: var(--color-action-primary-bg);
65
68
  }
@@ -123,7 +126,9 @@
123
126
 
124
127
  .ds-toggle-switch--compact .ds-toggle-switch__thumb-icon {
125
128
  /* Off-scale for the same reason as the default size — the compact thumb
126
- is only 16x16. */
129
+ is only 16x16.
130
+
131
+ ds-allow(icon-size): 12px glyph inside the 16px compact thumb. */
127
132
  --icon-size: 12px;
128
133
  }
129
134
 
@@ -1,23 +1,31 @@
1
- export interface ToggleSwitchProps {
1
+ import { default as React } from 'react';
2
+ /** Props owned by ToggleSwitch itself — everything else falls through to the <button>. */
3
+ type ToggleSwitchOwnProps = {
2
4
  /** Whether the toggle is on (checked) */
3
5
  checked?: boolean;
4
6
  /** Label text displayed next to the toggle */
5
7
  label?: string;
6
8
  /** Whether to show the label */
7
9
  showLabel?: boolean;
8
- /** Whether the toggle is disabled */
9
- disabled?: boolean;
10
10
  /** Component size */
11
11
  size?: 'default' | 'compact';
12
- /** Callback when toggled */
13
- onChange?: (checked: boolean) => void;
12
+ /** Called with the next checked state when toggled */
13
+ onCheckedChange?: (checked: boolean) => void;
14
14
  /** Additional CSS classes */
15
15
  className?: string;
16
- /** Accessible label for the button */
16
+ /** @deprecated Use `onCheckedChange` instead. */
17
+ onChange?: (checked: boolean) => void;
18
+ /** @deprecated Pass the native `aria-label` attribute instead. */
17
19
  ariaLabel?: string;
20
+ };
21
+ export interface ToggleSwitchProps extends ToggleSwitchOwnProps, Omit<React.ComponentPropsWithoutRef<'button'>, keyof ToggleSwitchOwnProps | 'type'> {
18
22
  }
19
23
  /**
20
- * ToggleSwitch component from Figma design system
21
- * Used for binary on/off settings like theme switching
24
+ * ToggleSwitch component from Figma design system.
25
+ * Used for binary on/off settings like theme switching.
26
+ *
27
+ * Renders a `<button role="switch">`, forwards a ref to it, and spreads
28
+ * unrecognised props onto it.
22
29
  */
23
- export declare const ToggleSwitch: ({ checked, label, showLabel, disabled, size, onChange, className, ariaLabel, }: ToggleSwitchProps) => import("react").JSX.Element;
30
+ export declare const ToggleSwitch: React.ForwardRefExoticComponent<ToggleSwitchProps & React.RefAttributes<HTMLButtonElement>>;
31
+ export {};
@@ -1,50 +1,63 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
+ import React from "react";
2
3
  import "./ToggleSwitch.css";
3
4
  import "../../fonts/material-symbols.css";
4
- const ToggleSwitch = ({
5
- checked = true,
6
- label = "Toggle",
7
- showLabel = true,
8
- disabled = false,
9
- size = "default",
10
- onChange,
11
- className = "",
12
- ariaLabel
13
- }) => {
14
- const baseClass = "ds-toggle-switch";
15
- const stateClass = checked ? "" : `${baseClass}--off`;
16
- const disabledClass = disabled ? `${baseClass}--disabled` : "";
17
- const sizeClass = size === "compact" ? `${baseClass}--compact` : "";
18
- const classes = [baseClass, sizeClass, stateClass, disabledClass, className].filter(Boolean).join(" ");
19
- const handleClick = () => {
20
- if (!disabled && onChange) {
21
- onChange(!checked);
22
- }
23
- };
24
- return /* @__PURE__ */ jsxs(
25
- "button",
26
- {
27
- type: "button",
28
- className: classes,
29
- onClick: handleClick,
30
- disabled,
31
- role: "switch",
32
- "aria-checked": checked,
33
- "aria-label": ariaLabel || label,
34
- children: [
35
- /* @__PURE__ */ jsx("div", { className: `${baseClass}__track`, children: /* @__PURE__ */ jsx("div", { className: `${baseClass}__thumb`, children: /* @__PURE__ */ jsx(
36
- "span",
37
- {
38
- className: `${baseClass}__thumb-icon material-symbols-rounded`,
39
- "aria-hidden": "true",
40
- children: "check"
41
- }
42
- ) }) }),
43
- showLabel && /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: label })
44
- ]
45
- }
46
- );
47
- };
5
+ const ToggleSwitch = React.forwardRef(
6
+ ({
7
+ checked = true,
8
+ label = "Toggle",
9
+ showLabel = true,
10
+ disabled = false,
11
+ size = "default",
12
+ onCheckedChange,
13
+ onChange,
14
+ onClick,
15
+ className = "",
16
+ ariaLabel,
17
+ ...rest
18
+ }, ref) => {
19
+ const baseClass = "ds-toggle-switch";
20
+ const classes = [
21
+ baseClass,
22
+ size === "compact" ? `${baseClass}--compact` : "",
23
+ checked ? "" : `${baseClass}--off`,
24
+ disabled ? `${baseClass}--disabled` : "",
25
+ className
26
+ ].filter(Boolean).join(" ");
27
+ const handleClick = (e) => {
28
+ onClick?.(e);
29
+ if (disabled) return;
30
+ onCheckedChange?.(!checked);
31
+ onChange?.(!checked);
32
+ };
33
+ return /* @__PURE__ */ jsxs(
34
+ "button",
35
+ {
36
+ ...rest,
37
+ ref,
38
+ type: "button",
39
+ className: classes,
40
+ onClick: handleClick,
41
+ disabled,
42
+ role: "switch",
43
+ "aria-checked": checked,
44
+ "aria-label": ariaLabel || rest["aria-label"] || label,
45
+ children: [
46
+ /* @__PURE__ */ jsx("div", { className: `${baseClass}__track`, children: /* @__PURE__ */ jsx("div", { className: `${baseClass}__thumb`, children: /* @__PURE__ */ jsx(
47
+ "span",
48
+ {
49
+ className: `${baseClass}__thumb-icon material-symbols-rounded`,
50
+ "aria-hidden": "true",
51
+ children: "check"
52
+ }
53
+ ) }) }),
54
+ showLabel && /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: label })
55
+ ]
56
+ }
57
+ );
58
+ }
59
+ );
60
+ ToggleSwitch.displayName = "ToggleSwitch";
48
61
  export {
49
62
  ToggleSwitch
50
63
  };