@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,134 +1,148 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
- import { useRef, useId, useSyncExternalStore, useEffect } from "react";
2
+ import React, { useRef, useId, useSyncExternalStore, useEffect } from "react";
3
3
  import ReactDOM from "react-dom";
4
4
  import "./Drawer.css";
5
5
  import "../../fonts/material-symbols.css";
6
6
  const emptySubscribe = () => () => {
7
7
  };
8
8
  const FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
9
- const Drawer = ({
10
- open,
11
- onOpenChange,
12
- title,
13
- description,
14
- children,
15
- footer,
16
- side = "right",
17
- size = "md",
18
- dismissible = true,
19
- className = ""
20
- }) => {
21
- const panelRef = useRef(null);
22
- const previousFocusRef = useRef(null);
23
- const titleId = useId();
24
- const descId = useId();
25
- const baseClass = "ds-drawer";
26
- const mounted = useSyncExternalStore(emptySubscribe, () => true, () => false);
27
- useEffect(() => {
28
- if (open) {
29
- previousFocusRef.current = document.activeElement;
30
- }
31
- }, [open]);
32
- useEffect(() => {
33
- if (!open) return;
34
- const panel = panelRef.current;
35
- if (panel) {
36
- const first = panel.querySelector(FOCUSABLE);
37
- (first ?? panel).focus();
38
- }
39
- const handleKeyDown = (e) => {
40
- if (e.key === "Escape" && dismissible) {
41
- onOpenChange(false);
42
- return;
9
+ const Drawer = React.forwardRef(
10
+ ({
11
+ open,
12
+ onOpenChange,
13
+ title,
14
+ description,
15
+ children,
16
+ footer,
17
+ side = "right",
18
+ size = "md",
19
+ dismissible = true,
20
+ className = "",
21
+ ...rest
22
+ }, ref) => {
23
+ const panelRef = useRef(null);
24
+ const previousFocusRef = useRef(null);
25
+ const titleId = useId();
26
+ const descId = useId();
27
+ const baseClass = "ds-drawer";
28
+ const setPanelRef = (node) => {
29
+ panelRef.current = node;
30
+ if (typeof ref === "function") ref(node);
31
+ else if (ref) ref.current = node;
32
+ };
33
+ const mounted = useSyncExternalStore(
34
+ emptySubscribe,
35
+ () => true,
36
+ () => false
37
+ );
38
+ useEffect(() => {
39
+ if (open) {
40
+ previousFocusRef.current = document.activeElement;
41
+ }
42
+ }, [open]);
43
+ useEffect(() => {
44
+ if (!open) return;
45
+ const panel = panelRef.current;
46
+ if (panel) {
47
+ const first = panel.querySelector(FOCUSABLE);
48
+ (first ?? panel).focus();
43
49
  }
44
- if (e.key === "Tab" && panel) {
45
- const focusable = panel.querySelectorAll(FOCUSABLE);
46
- if (focusable.length === 0) return;
47
- const first = focusable[0];
48
- const last = focusable[focusable.length - 1];
49
- if (e.shiftKey && document.activeElement === first) {
50
- e.preventDefault();
51
- last.focus();
52
- } else if (!e.shiftKey && document.activeElement === last) {
53
- e.preventDefault();
54
- first.focus();
50
+ const handleKeyDown = (e) => {
51
+ if (e.key === "Escape" && dismissible) {
52
+ onOpenChange(false);
53
+ return;
55
54
  }
55
+ if (e.key === "Tab" && panel) {
56
+ const focusable = panel.querySelectorAll(FOCUSABLE);
57
+ if (focusable.length === 0) return;
58
+ const first = focusable[0];
59
+ const last = focusable[focusable.length - 1];
60
+ if (e.shiftKey && document.activeElement === first) {
61
+ e.preventDefault();
62
+ last.focus();
63
+ } else if (!e.shiftKey && document.activeElement === last) {
64
+ e.preventDefault();
65
+ first.focus();
66
+ }
67
+ }
68
+ };
69
+ document.addEventListener("keydown", handleKeyDown);
70
+ return () => document.removeEventListener("keydown", handleKeyDown);
71
+ }, [open, dismissible, onOpenChange]);
72
+ useEffect(() => {
73
+ if (!open && previousFocusRef.current) {
74
+ previousFocusRef.current.focus();
75
+ previousFocusRef.current = null;
56
76
  }
77
+ }, [open]);
78
+ useEffect(() => {
79
+ if (open) {
80
+ document.body.style.overflow = "hidden";
81
+ } else {
82
+ document.body.style.overflow = "";
83
+ }
84
+ return () => {
85
+ document.body.style.overflow = "";
86
+ };
87
+ }, [open]);
88
+ const handleScrimClick = () => {
89
+ if (dismissible) onOpenChange(false);
57
90
  };
58
- document.addEventListener("keydown", handleKeyDown);
59
- return () => document.removeEventListener("keydown", handleKeyDown);
60
- }, [open, dismissible, onOpenChange]);
61
- useEffect(() => {
62
- if (!open && previousFocusRef.current) {
63
- previousFocusRef.current.focus();
64
- previousFocusRef.current = null;
65
- }
66
- }, [open]);
67
- useEffect(() => {
68
- if (open) {
69
- document.body.style.overflow = "hidden";
70
- } else {
71
- document.body.style.overflow = "";
72
- }
73
- return () => {
74
- document.body.style.overflow = "";
75
- };
76
- }, [open]);
77
- const handleScrimClick = () => {
78
- if (dismissible) onOpenChange(false);
79
- };
80
- const classes = [
81
- baseClass,
82
- `${baseClass}--${side}`,
83
- `${baseClass}--${size}`,
84
- open ? `${baseClass}--open` : "",
85
- className
86
- ].filter(Boolean).join(" ");
87
- const drawer = /* @__PURE__ */ jsxs("div", { className: classes, children: [
88
- /* @__PURE__ */ jsx("div", { className: `${baseClass}__scrim`, onClick: handleScrimClick }),
89
- /* @__PURE__ */ jsxs(
90
- "div",
91
- {
92
- className: `${baseClass}__panel`,
93
- ref: panelRef,
94
- role: "dialog",
95
- "aria-modal": "true",
96
- "aria-labelledby": titleId,
97
- "aria-describedby": description ? descId : void 0,
98
- tabIndex: -1,
99
- children: [
100
- /* @__PURE__ */ jsxs("div", { className: `${baseClass}__header`, children: [
101
- /* @__PURE__ */ jsxs("div", { className: `${baseClass}__heading`, children: [
102
- /* @__PURE__ */ jsx("h2", { className: `${baseClass}__title`, id: titleId, children: title }),
103
- description && /* @__PURE__ */ jsx("p", { className: `${baseClass}__description`, id: descId, children: description })
91
+ const classes = [
92
+ baseClass,
93
+ `${baseClass}--${side}`,
94
+ `${baseClass}--${size}`,
95
+ open ? `${baseClass}--open` : "",
96
+ className
97
+ ].filter(Boolean).join(" ");
98
+ const drawer = /* @__PURE__ */ jsxs("div", { className: classes, children: [
99
+ /* @__PURE__ */ jsx("div", { className: `${baseClass}__scrim`, onClick: handleScrimClick }),
100
+ /* @__PURE__ */ jsxs(
101
+ "div",
102
+ {
103
+ ...rest,
104
+ className: `${baseClass}__panel`,
105
+ ref: setPanelRef,
106
+ role: "dialog",
107
+ "aria-modal": "true",
108
+ "aria-labelledby": titleId,
109
+ "aria-describedby": description ? descId : rest["aria-describedby"],
110
+ tabIndex: -1,
111
+ children: [
112
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__header`, children: [
113
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__heading`, children: [
114
+ /* @__PURE__ */ jsx("h2", { className: `${baseClass}__title`, id: titleId, children: title }),
115
+ description && /* @__PURE__ */ jsx("p", { className: `${baseClass}__description`, id: descId, children: description })
116
+ ] }),
117
+ dismissible && /* @__PURE__ */ jsx(
118
+ "button",
119
+ {
120
+ type: "button",
121
+ className: `${baseClass}__close`,
122
+ onClick: () => onOpenChange(false),
123
+ "aria-label": "Close drawer",
124
+ children: /* @__PURE__ */ jsx(
125
+ "span",
126
+ {
127
+ className: `${baseClass}__close-icon material-symbols-rounded`,
128
+ "aria-hidden": "true",
129
+ children: "close"
130
+ }
131
+ )
132
+ }
133
+ )
104
134
  ] }),
105
- dismissible && /* @__PURE__ */ jsx(
106
- "button",
107
- {
108
- type: "button",
109
- className: `${baseClass}__close`,
110
- onClick: () => onOpenChange(false),
111
- "aria-label": "Close drawer",
112
- children: /* @__PURE__ */ jsx(
113
- "span",
114
- {
115
- className: `${baseClass}__close-icon material-symbols-rounded`,
116
- "aria-hidden": "true",
117
- children: "close"
118
- }
119
- )
120
- }
121
- )
122
- ] }),
123
- children && /* @__PURE__ */ jsx("div", { className: `${baseClass}__body`, children }),
124
- footer && /* @__PURE__ */ jsx("div", { className: `${baseClass}__footer`, children: footer })
125
- ]
126
- }
127
- )
128
- ] });
129
- if (!mounted) return null;
130
- return ReactDOM.createPortal(drawer, document.body);
131
- };
135
+ children && /* @__PURE__ */ jsx("div", { className: `${baseClass}__body`, children }),
136
+ footer && /* @__PURE__ */ jsx("div", { className: `${baseClass}__footer`, children: footer })
137
+ ]
138
+ }
139
+ )
140
+ ] });
141
+ if (!mounted) return null;
142
+ return ReactDOM.createPortal(drawer, document.body);
143
+ }
144
+ );
145
+ Drawer.displayName = "Drawer";
132
146
  export {
133
147
  Drawer
134
148
  };
@@ -13,7 +13,8 @@ export interface DropdownOptionGroup {
13
13
  /** Options within the group */
14
14
  options: DropdownOption[];
15
15
  }
16
- export interface DropdownProps {
16
+ /** Props owned by Dropdown itself — everything else falls through to the wrapper. */
17
+ type DropdownOwnProps = {
17
18
  /** Dropdown label text */
18
19
  label?: string;
19
20
  /** Placeholder when no value selected */
@@ -34,15 +35,30 @@ export interface DropdownProps {
34
35
  error?: boolean;
35
36
  /** Helper or error message */
36
37
  helperText?: string;
37
- /** Callback when selection changes */
38
- onChange?: (value: string) => void;
38
+ /** Called with the newly selected value */
39
+ onValueChange?: (value: string) => void;
39
40
  /** Additional CSS classes */
40
41
  className?: string;
41
- /** Accessible label override */
42
+ /** @deprecated Use `onValueChange` instead. */
43
+ onChange?: (value: string) => void;
44
+ /** @deprecated Pass the native `aria-label` attribute instead. */
42
45
  ariaLabel?: string;
43
- /** HTML name attribute */
46
+ /**
47
+ * Used only as a fallback for deriving the element id (`id || name || label`).
48
+ *
49
+ * Note: Dropdown renders a `<div role="combobox">`, not a native `<select>`,
50
+ * so `name` does **not** make it participate in native form submission.
51
+ */
44
52
  name?: string;
45
- /** HTML id attribute */
46
- id?: string;
53
+ };
54
+ export interface DropdownProps extends DropdownOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof DropdownOwnProps> {
47
55
  }
48
- export declare const Dropdown: ({ label, placeholder, value, options, groups, size, disabled, required, error, helperText, onChange, className, ariaLabel, name, id, }: DropdownProps) => React.JSX.Element;
56
+ /**
57
+ * Static single-select dropdown. Renders a `<div role="combobox">` paired with a
58
+ * `role="listbox"` menu; supports flat and grouped options with full keyboard
59
+ * navigation.
60
+ *
61
+ * Forwards a ref to the wrapping element and spreads unrecognised props onto it.
62
+ */
63
+ export declare const Dropdown: React.ForwardRefExoticComponent<DropdownProps & React.RefAttributes<HTMLDivElement>>;
64
+ export {};