@robr0/design-system 0.1.0 → 0.2.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 (39) hide show
  1. package/README.md +6 -4
  2. package/components/Breadcrumb/Breadcrumb.css +9 -1
  3. package/components/Button/Button.d.ts +32 -16
  4. package/components/Button/Button.js +66 -58
  5. package/components/Card/Card.d.ts +17 -5
  6. package/components/Card/Card.js +101 -80
  7. package/components/Checkbox/Checkbox.d.ts +24 -12
  8. package/components/Checkbox/Checkbox.js +99 -85
  9. package/components/Combobox/Combobox.d.ts +19 -7
  10. package/components/Combobox/Combobox.js +18 -9
  11. package/components/DateInput/DateInput.d.ts +18 -21
  12. package/components/DateInput/DateInput.js +71 -63
  13. package/components/DatePicker/DatePicker.d.ts +9 -2
  14. package/components/DatePicker/DatePicker.js +127 -129
  15. package/components/Dialog/Dialog.d.ts +15 -4
  16. package/components/Dialog/Dialog.js +133 -116
  17. package/components/Drawer/Drawer.d.ts +15 -4
  18. package/components/Drawer/Drawer.js +133 -119
  19. package/components/Dropdown/Dropdown.d.ts +24 -8
  20. package/components/Dropdown/Dropdown.js +226 -186
  21. package/components/FileInput/FileInput.d.ts +13 -17
  22. package/components/FileInput/FileInput.js +177 -158
  23. package/components/Input/Input.d.ts +23 -22
  24. package/components/Input/Input.js +87 -65
  25. package/components/RadioButton/RadioButton.d.ts +26 -13
  26. package/components/RadioButton/RadioButton.js +95 -70
  27. package/components/SelectionCard/SelectionCard.d.ts +10 -4
  28. package/components/SelectionCard/SelectionCard.js +72 -56
  29. package/components/Slider/Slider.d.ts +19 -10
  30. package/components/Slider/Slider.js +50 -40
  31. package/components/Table/Table.d.ts +10 -2
  32. package/components/Table/Table.js +58 -58
  33. package/components/Textarea/Textarea.d.ts +21 -22
  34. package/components/Textarea/Textarea.js +77 -68
  35. package/components/ToggleGroup/ToggleGroup.d.ts +18 -8
  36. package/components/ToggleGroup/ToggleGroup.js +61 -44
  37. package/components/ToggleSwitch/ToggleSwitch.d.ts +17 -9
  38. package/components/ToggleSwitch/ToggleSwitch.js +57 -44
  39. package/package.json +1 -1
@@ -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,83 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
- import "react";
2
+ import React from "react";
3
3
  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",
43
- {
44
- className: `${baseClass}__field`,
45
- id: inputId,
46
- name,
47
- value,
48
- placeholder,
49
- rows,
50
- disabled,
51
- 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 }
60
- }
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
4
+ const Textarea = React.forwardRef(
5
+ ({
6
+ label,
7
+ placeholder = "",
8
+ value = "",
9
+ rows = 4,
10
+ size = "default",
11
+ disabled = false,
12
+ required = false,
13
+ error = false,
14
+ helperText,
15
+ resize = "vertical",
16
+ maxLength,
17
+ onChange,
18
+ onValueChange,
19
+ className = "",
20
+ ariaLabel,
21
+ name,
22
+ id,
23
+ style,
24
+ ...rest
25
+ }, ref) => {
26
+ const baseClass = "ds-textarea";
27
+ const classes = [
28
+ baseClass,
29
+ size === "compact" ? `${baseClass}--compact` : "",
30
+ error ? `${baseClass}--error` : "",
31
+ disabled ? `${baseClass}--disabled` : "",
32
+ className
33
+ ].filter(Boolean).join(" ");
34
+ const handleChange = (e) => {
35
+ onChange?.(e);
36
+ onValueChange?.(e.target.value);
37
+ };
38
+ const inputId = id || name || label?.toLowerCase().replace(/\s+/g, "-");
39
+ const charCount = value?.length || 0;
40
+ return /* @__PURE__ */ jsxs("div", { className: classes, children: [
41
+ label && /* @__PURE__ */ jsxs("label", { className: `${baseClass}__label`, htmlFor: inputId, children: [
42
+ label,
43
+ required && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__required`, "aria-hidden": "true", children: [
44
+ " ",
45
+ "*"
46
+ ] })
47
+ ] }),
48
+ /* @__PURE__ */ jsx(
49
+ "textarea",
50
+ {
51
+ ...rest,
52
+ ref,
53
+ className: `${baseClass}__field`,
54
+ id: inputId,
55
+ name,
56
+ value,
57
+ placeholder,
58
+ rows,
59
+ disabled,
60
+ required,
61
+ maxLength,
62
+ "aria-label": ariaLabel || rest["aria-label"] || label,
63
+ "aria-invalid": error,
64
+ "aria-describedby": helperText ? `${inputId}-helper` : rest["aria-describedby"],
65
+ onChange: handleChange,
66
+ style: { resize, ...style }
67
+ }
68
+ ),
69
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__footer`, children: [
70
+ helperText && /* @__PURE__ */ jsx("p", { className: `${baseClass}__helper`, id: `${inputId}-helper`, children: helperText }),
71
+ maxLength && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__counter`, "aria-live": "polite", "aria-atomic": "true", children: [
72
+ charCount,
73
+ "/",
74
+ maxLength
75
+ ] })
68
76
  ] })
69
- ] })
70
- ] });
71
- };
77
+ ] });
78
+ }
79
+ );
80
+ Textarea.displayName = "Textarea";
72
81
  export {
73
82
  Textarea
74
83
  };
@@ -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
  };
@@ -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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robr0/design-system",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A token-driven React design system — accessible components, light/dark theming, and CSS-variable customization.",
5
5
  "license": "MIT",
6
6
  "repository": {