@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
@@ -1,71 +1,98 @@
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 "./Input.css";
4
5
  import "../../fonts/material-symbols.css";
5
- const Input = ({
6
- label,
7
- placeholder = "",
8
- value,
9
- type = "text",
10
- size = "default",
11
- disabled = false,
12
- required = false,
13
- error = false,
14
- helperText,
15
- iconLeft,
16
- iconRight,
17
- onChange,
18
- onFocus,
19
- onBlur,
20
- className = "",
21
- ariaLabel,
22
- name,
23
- id
24
- }) => {
25
- const baseClass = "ds-input";
26
- const sizeClass = size === "compact" ? `${baseClass}--compact` : "";
27
- const errorClass = error ? `${baseClass}--error` : "";
28
- const disabledClass = disabled ? `${baseClass}--disabled` : "";
29
- const hasLeftIcon = iconLeft ? `${baseClass}--has-icon-left` : "";
30
- const hasRightIcon = iconRight ? `${baseClass}--has-icon-right` : "";
31
- const classes = [baseClass, sizeClass, errorClass, disabledClass, hasLeftIcon, hasRightIcon, className].filter(Boolean).join(" ");
32
- const handleChange = (e) => {
33
- if (onChange) {
34
- onChange(e.target.value);
35
- }
36
- };
37
- const inputId = id || name || label?.toLowerCase().replace(/\s+/g, "-");
38
- return /* @__PURE__ */ jsxs("div", { className: classes, children: [
39
- label && /* @__PURE__ */ jsxs("label", { className: `${baseClass}__label`, htmlFor: inputId, children: [
40
- label,
41
- required && /* @__PURE__ */ jsx("span", { className: `${baseClass}__required`, "aria-hidden": "true", children: " *" })
42
- ] }),
43
- /* @__PURE__ */ jsxs("div", { className: `${baseClass}__field-wrapper`, children: [
44
- iconLeft && /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon ${baseClass}__icon--left material-symbols-rounded`, "aria-hidden": "true", children: iconLeft }),
45
- /* @__PURE__ */ jsx(
46
- "input",
47
- {
48
- className: `${baseClass}__field`,
49
- type,
50
- id: inputId,
51
- name,
52
- value,
53
- placeholder,
54
- disabled,
55
- required,
56
- "aria-label": ariaLabel || label,
57
- "aria-invalid": error,
58
- "aria-describedby": helperText ? `${inputId}-helper` : void 0,
59
- onChange: handleChange,
60
- onFocus,
61
- onBlur
62
- }
63
- ),
64
- iconRight && /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon ${baseClass}__icon--right material-symbols-rounded`, "aria-hidden": "true", children: iconRight })
65
- ] }),
66
- helperText && /* @__PURE__ */ jsx("p", { className: `${baseClass}__helper`, id: `${inputId}-helper`, children: helperText })
67
- ] });
68
- };
6
+ const Input = React.forwardRef(
7
+ ({
8
+ label,
9
+ placeholder = "",
10
+ value,
11
+ type = "text",
12
+ size = "default",
13
+ disabled = false,
14
+ required = false,
15
+ error = false,
16
+ helperText,
17
+ iconLeft,
18
+ iconRight,
19
+ onChange,
20
+ onValueChange,
21
+ className = "",
22
+ ariaLabel,
23
+ name,
24
+ id,
25
+ ...rest
26
+ }, ref) => {
27
+ const baseClass = "ds-input";
28
+ const classes = [
29
+ baseClass,
30
+ size === "compact" ? `${baseClass}--compact` : "",
31
+ error ? `${baseClass}--error` : "",
32
+ disabled ? `${baseClass}--disabled` : "",
33
+ iconLeft ? `${baseClass}--has-icon-left` : "",
34
+ iconRight ? `${baseClass}--has-icon-right` : "",
35
+ className
36
+ ].filter(Boolean).join(" ");
37
+ const handleChange = (e) => {
38
+ onChange?.(e);
39
+ onValueChange?.(e.target.value);
40
+ };
41
+ const inputId = id || name || label?.toLowerCase().replace(/\s+/g, "-");
42
+ const describedBy = helperText ? `${inputId}-helper` : rest["aria-describedby"];
43
+ return /* @__PURE__ */ jsx(
44
+ Field,
45
+ {
46
+ className: classes,
47
+ label,
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
+ )
90
+ ] })
91
+ }
92
+ );
93
+ }
94
+ );
95
+ Input.displayName = "Input";
69
96
  export {
70
97
  Input
71
98
  };
@@ -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
+ }
@@ -0,0 +1,19 @@
1
+ import { default as React } from 'react';
2
+ /** Props owned by Kbd itself — everything else falls through to the <kbd> element. */
3
+ type KbdOwnProps = {
4
+ /** Key size */
5
+ size?: 'default' | 'compact';
6
+ /** Additional CSS classes */
7
+ className?: string;
8
+ /** The key legend — e.g. ⌘, K, Esc, Shift */
9
+ children?: React.ReactNode;
10
+ };
11
+ export interface KbdProps extends KbdOwnProps, Omit<React.ComponentPropsWithoutRef<'kbd'>, keyof KbdOwnProps> {
12
+ }
13
+ /**
14
+ * Kbd renders a single keyboard key as a keycap, for shortcut hints in
15
+ * menus and docs prose. Compose several for a chord: `<Kbd>⌘</Kbd><Kbd>K</Kbd>`.
16
+ * Purely presentational — safe to render from a Server Component.
17
+ */
18
+ export declare const Kbd: React.ForwardRefExoticComponent<KbdProps & React.RefAttributes<HTMLElement>>;
19
+ export {};
@@ -0,0 +1,14 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import "./Kbd.css";
4
+ const Kbd = React.forwardRef(
5
+ ({ size = "default", className = "", children, ...rest }, ref) => {
6
+ const baseClass = "ds-kbd";
7
+ const classes = [baseClass, `${baseClass}--${size}`, className].filter(Boolean).join(" ");
8
+ return /* @__PURE__ */ jsx("kbd", { ...rest, ref, className: classes, children });
9
+ }
10
+ );
11
+ Kbd.displayName = "Kbd";
12
+ export {
13
+ Kbd
14
+ };
@@ -19,10 +19,4 @@ export interface PopoverProps {
19
19
  /** Additional CSS classes for the popover panel */
20
20
  className?: string;
21
21
  }
22
- /**
23
- * Popover component for contextual overlays.
24
- * Wraps a trigger element and displays content in a
25
- * positioned panel. Supports click and hover triggers
26
- * with automatic outside-click dismissal.
27
- */
28
22
  export declare const Popover: ({ children, content, size, position, trigger, open: controlledOpen, onOpenChange, ariaLabel, className, }: PopoverProps) => React.JSX.Element;
@@ -1,6 +1,8 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
- import { useState, useRef, useCallback, useEffect } from "react";
2
+ import React, { useState, useRef, useCallback, useEffect } from "react";
3
3
  import "./Popover.css";
4
+ const INTERACTIVE_TAGS = /* @__PURE__ */ new Set(["button", "a", "input", "select", "textarea", "summary"]);
5
+ const canCarryPopoverSemantics = (el) => typeof el.type === "string" ? INTERACTIVE_TAGS.has(el.type) : true;
4
6
  const Popover = ({
5
7
  children,
6
8
  content,
@@ -73,26 +75,30 @@ const Popover = ({
73
75
  onMouseEnter: handleMouseEnter,
74
76
  onMouseLeave: handleMouseLeave,
75
77
  children: [
76
- /* @__PURE__ */ jsx(
77
- "div",
78
+ /* @__PURE__ */ jsx("div", { className: `${baseClass}__trigger`, onClick: handleClick, children: React.isValidElement(children) && canCarryPopoverSemantics(children) ? React.cloneElement(children, {
79
+ "aria-expanded": isOpen,
80
+ "aria-haspopup": "dialog",
81
+ ...ariaLabel ? { "aria-label": ariaLabel } : {}
82
+ }) : trigger === "click" ? /* @__PURE__ */ jsx(
83
+ "button",
78
84
  {
79
- className: `${baseClass}__trigger`,
80
- onClick: handleClick,
81
- role: trigger === "click" ? "button" : void 0,
82
- tabIndex: trigger === "click" ? 0 : void 0,
85
+ type: "button",
83
86
  "aria-expanded": isOpen,
84
- "aria-label": ariaLabel,
85
87
  "aria-haspopup": "dialog",
86
- onKeyDown: (e) => {
87
- if (trigger === "click" && (e.key === "Enter" || e.key === " ")) {
88
- e.preventDefault();
89
- setOpen(!isOpen);
90
- }
91
- },
88
+ "aria-label": ariaLabel,
92
89
  children
93
90
  }
94
- ),
95
- /* @__PURE__ */ jsx("div", { className: panelClasses, role: "dialog", "aria-hidden": !isOpen, children: content })
91
+ ) : children }),
92
+ /* @__PURE__ */ jsx(
93
+ "div",
94
+ {
95
+ className: panelClasses,
96
+ role: "dialog",
97
+ "aria-label": ariaLabel || "Popover",
98
+ "aria-hidden": !isOpen,
99
+ children: content
100
+ }
101
+ )
96
102
  ]
97
103
  }
98
104
  );
@@ -14,7 +14,7 @@ const ProgressBar = ({
14
14
  `${baseClass}--${size}`,
15
15
  className
16
16
  ].filter(Boolean).join(" ");
17
- return /* @__PURE__ */ jsxs("div", { className: classes, role: "progressbar", "aria-valuenow": clamped, "aria-valuemin": 0, "aria-valuemax": 100, "aria-label": ariaLabel, children: [
17
+ return /* @__PURE__ */ jsxs("div", { className: classes, role: "progressbar", "aria-valuenow": clamped, "aria-valuemin": 0, "aria-valuemax": 100, "aria-label": ariaLabel || "Progress", children: [
18
18
  /* @__PURE__ */ jsx("div", { className: `${baseClass}__track`, children: /* @__PURE__ */ jsx("div", { className: `${baseClass}__fill`, style: { width: `${clamped}%` } }) }),
19
19
  showLabel && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__label`, children: [
20
20
  clamped,
@@ -1,26 +1,34 @@
1
1
  import { default as React } from 'react';
2
- export interface RadioButtonProps {
2
+ /** Props owned by RadioButton itself — everything else falls through to the wrapper. */
3
+ type RadioButtonOwnProps = {
3
4
  /** Label text */
4
5
  label?: string;
5
6
  /** Whether this radio is selected */
6
7
  checked?: boolean;
7
8
  /** Whether the radio is disabled */
8
9
  disabled?: boolean;
9
- /** Radio group name — groups radios together */
10
- name?: string;
11
10
  /** Value for this radio option */
12
11
  value?: string;
13
- /** Callback when selected */
14
- onChange?: (value: string) => void;
12
+ /** Called with this radio's value when selected */
13
+ onValueChange?: (value: string) => void;
15
14
  /** Additional CSS classes */
16
15
  className?: string;
17
- /** Accessible label override */
16
+ /** @deprecated Use `onValueChange` instead. */
17
+ onChange?: (value: string) => void;
18
+ /** @deprecated Pass the native `aria-label` attribute instead. */
18
19
  ariaLabel?: string;
19
- /** HTML id attribute */
20
- id?: string;
20
+ /**
21
+ * @deprecated No-op. This component renders a `<div role="radio">`, not a
22
+ * native `<input type="radio">`, so it does not group by name or participate
23
+ * in native form submission — `RadioGroup` handles grouping in React state.
24
+ * Declared only so the attribute is not forwarded to an element that rejects it.
25
+ */
26
+ name?: string;
27
+ };
28
+ export interface RadioButtonProps extends RadioButtonOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof RadioButtonOwnProps> {
21
29
  }
22
- export declare const RadioButton: ({ label, checked, disabled, value, onChange, className, ariaLabel, }: RadioButtonProps) => React.JSX.Element;
23
- export interface RadioGroupProps {
30
+ export declare const RadioButton: React.ForwardRefExoticComponent<RadioButtonProps & React.RefAttributes<HTMLDivElement>>;
31
+ type RadioGroupOwnProps = {
24
32
  /** Group label */
25
33
  label?: string;
26
34
  /** Currently selected value */
@@ -35,9 +43,14 @@ export interface RadioGroupProps {
35
43
  }[];
36
44
  /** Layout direction */
37
45
  direction?: 'vertical' | 'horizontal';
38
- /** Callback when selection changes */
39
- onChange?: (value: string) => void;
46
+ /** Called with the newly selected value */
47
+ onValueChange?: (value: string) => void;
40
48
  /** Additional CSS classes */
41
49
  className?: string;
50
+ /** @deprecated Use `onValueChange` instead. */
51
+ onChange?: (value: string) => void;
52
+ };
53
+ export interface RadioGroupProps extends RadioGroupOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof RadioGroupOwnProps> {
42
54
  }
43
- export declare const RadioGroup: ({ label, value, name, options, direction, onChange, className, }: RadioGroupProps) => React.JSX.Element;
55
+ export declare const RadioGroup: React.ForwardRefExoticComponent<RadioGroupProps & React.RefAttributes<HTMLDivElement>>;
56
+ export {};
@@ -1,76 +1,101 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
- import "react";
2
+ import React from "react";
3
3
  import "./RadioButton.css";
4
- const RadioButton = ({
5
- label,
6
- checked = false,
7
- disabled = false,
8
- value = "",
9
- onChange,
10
- className = "",
11
- ariaLabel
12
- }) => {
13
- const baseClass = "ds-radio";
14
- const checkedClass = checked ? `${baseClass}--checked` : "";
15
- const disabledClass = disabled ? `${baseClass}--disabled` : "";
16
- const classes = [baseClass, checkedClass, disabledClass, className].filter(Boolean).join(" ");
17
- const handleClick = () => {
18
- if (!disabled && onChange) {
19
- onChange(value);
20
- }
21
- };
22
- const handleKeyDown = (e) => {
23
- if (e.key === " " || e.key === "Enter") {
24
- e.preventDefault();
25
- handleClick();
26
- }
27
- };
28
- return /* @__PURE__ */ jsxs(
29
- "div",
30
- {
31
- className: classes,
32
- onClick: handleClick,
33
- onKeyDown: handleKeyDown,
34
- role: "radio",
35
- "aria-checked": checked,
36
- "aria-disabled": disabled,
37
- "aria-label": ariaLabel || label,
38
- tabIndex: disabled ? -1 : 0,
39
- children: [
40
- /* @__PURE__ */ jsx("div", { className: `${baseClass}__circle`, children: /* @__PURE__ */ jsx("div", { className: `${baseClass}__dot` }) }),
41
- label && /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: label })
42
- ]
43
- }
44
- );
45
- };
46
- const RadioGroup = ({
47
- label,
48
- value,
49
- name,
50
- options,
51
- direction = "vertical",
52
- onChange,
53
- className = ""
54
- }) => {
55
- const baseClass = "ds-radio-group";
56
- const directionClass = `${baseClass}--${direction}`;
57
- const classes = [baseClass, directionClass, className].filter(Boolean).join(" ");
58
- return /* @__PURE__ */ jsxs("div", { className: classes, role: "radiogroup", "aria-label": label, children: [
59
- label && /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: label }),
60
- /* @__PURE__ */ jsx("div", { className: `${baseClass}__options`, children: options.map((option) => /* @__PURE__ */ jsx(
61
- RadioButton,
4
+ const RadioButton = React.forwardRef(
5
+ ({
6
+ label,
7
+ checked = false,
8
+ disabled = false,
9
+ value = "",
10
+ onValueChange,
11
+ onChange,
12
+ className = "",
13
+ ariaLabel,
14
+ name: _name,
15
+ onClick,
16
+ onKeyDown,
17
+ ...rest
18
+ }, ref) => {
19
+ const baseClass = "ds-radio";
20
+ const classes = [
21
+ baseClass,
22
+ checked ? `${baseClass}--checked` : "",
23
+ disabled ? `${baseClass}--disabled` : "",
24
+ className
25
+ ].filter(Boolean).join(" ");
26
+ const select = () => {
27
+ if (disabled) return;
28
+ onValueChange?.(value);
29
+ onChange?.(value);
30
+ };
31
+ const handleClick = (e) => {
32
+ onClick?.(e);
33
+ select();
34
+ };
35
+ const handleKeyDown = (e) => {
36
+ onKeyDown?.(e);
37
+ if (e.key === " " || e.key === "Enter") {
38
+ e.preventDefault();
39
+ select();
40
+ }
41
+ };
42
+ return /* @__PURE__ */ jsxs(
43
+ "div",
62
44
  {
63
- label: option.label,
64
- value: option.value,
65
- name,
66
- checked: value === option.value,
67
- disabled: option.disabled,
68
- onChange
69
- },
70
- option.value
71
- )) })
72
- ] });
73
- };
45
+ ...rest,
46
+ ref,
47
+ className: classes,
48
+ onClick: handleClick,
49
+ onKeyDown: handleKeyDown,
50
+ role: "radio",
51
+ "aria-checked": checked,
52
+ "aria-disabled": disabled,
53
+ "aria-label": ariaLabel || rest["aria-label"] || label,
54
+ tabIndex: disabled ? -1 : 0,
55
+ children: [
56
+ /* @__PURE__ */ jsx("div", { className: `${baseClass}__circle`, children: /* @__PURE__ */ jsx("div", { className: `${baseClass}__dot` }) }),
57
+ label && /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: label })
58
+ ]
59
+ }
60
+ );
61
+ }
62
+ );
63
+ RadioButton.displayName = "RadioButton";
64
+ const RadioGroup = React.forwardRef(
65
+ ({
66
+ label,
67
+ value,
68
+ name: _name,
69
+ options,
70
+ direction = "vertical",
71
+ onValueChange,
72
+ onChange,
73
+ className = "",
74
+ ...rest
75
+ }, ref) => {
76
+ const baseClass = "ds-radio-group";
77
+ const classes = [baseClass, `${baseClass}--${direction}`, className].filter(Boolean).join(" ");
78
+ const handleSelect = (next) => {
79
+ onValueChange?.(next);
80
+ onChange?.(next);
81
+ };
82
+ return /* @__PURE__ */ jsxs("div", { ...rest, ref, className: classes, role: "radiogroup", "aria-label": label, children: [
83
+ label && /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: label }),
84
+ /* @__PURE__ */ jsx("div", { className: `${baseClass}__options`, children: options.map((option) => /* @__PURE__ */ jsx(
85
+ RadioButton,
86
+ {
87
+ label: option.label,
88
+ value: option.value,
89
+ checked: value === option.value,
90
+ disabled: option.disabled,
91
+ onValueChange: handleSelect
92
+ },
93
+ option.value
94
+ )) })
95
+ ] });
96
+ }
97
+ );
98
+ RadioGroup.displayName = "RadioGroup";
74
99
  export {
75
100
  RadioButton,
76
101
  RadioGroup
@@ -232,8 +232,12 @@
232
232
  .ds-selection-card__toggle-icon {
233
233
  /* Intentionally off the --icon-size-* scale — same reason as ToggleSwitch:
234
234
  this glyph lives inside the 20x20 toggle thumb, and the smallest scale
235
- step (20px) would fill it completely. */
236
- font-size: 14px;
235
+ step (20px) would fill it completely. Sized via --icon-size, not
236
+ font-size, so the icon's box shrinks with the glyph (see ToggleSwitch).
237
+
238
+ ds-allow(icon-size): 14px glyph inside the 20px thumb — the smallest
239
+ scale step would fill it edge to edge. */
240
+ --icon-size: 14px;
237
241
  color: var(--color-action-primary-bg);
238
242
  }
239
243
 
@@ -9,18 +9,24 @@ export interface SelectionCardOption {
9
9
  /** Whether this option is disabled */
10
10
  disabled?: boolean;
11
11
  }
12
- export interface SelectionCardProps {
12
+ /** Props owned by SelectionCard itself — everything else falls through to the group. */
13
+ type SelectionCardOwnProps = {
13
14
  /** Selection mode — radio (single), checkbox (multi), or toggle (each card is an on/off switch) */
14
15
  mode?: 'radio' | 'checkbox' | 'toggle';
15
16
  /** Available options */
16
17
  options: SelectionCardOption[];
17
18
  /** Currently selected value(s) — string for radio, string[] for checkbox */
18
19
  value?: string | string[];
19
- /** Callback when selection changes returns string for radio, string[] for checkbox */
20
- onChange?: (value: string | string[]) => void;
20
+ /** Called with the next selection — a string in radio mode, an array otherwise */
21
+ onValueChange?: (value: string | string[]) => void;
21
22
  /** Group name (used for aria-label on the group) */
22
23
  name?: string;
23
24
  /** Additional CSS classes */
24
25
  className?: string;
26
+ /** @deprecated Use `onValueChange` instead. */
27
+ onChange?: (value: string | string[]) => void;
28
+ };
29
+ export interface SelectionCardProps extends SelectionCardOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof SelectionCardOwnProps> {
25
30
  }
26
- export declare const SelectionCard: ({ mode, options, value, onChange, name, className, }: SelectionCardProps) => React.JSX.Element;
31
+ export declare const SelectionCard: React.ForwardRefExoticComponent<SelectionCardProps & React.RefAttributes<HTMLDivElement>>;
32
+ export {};