@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,5 +1,5 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
- import { useMemo, useState } from "react";
2
+ import React, { useMemo, useState } from "react";
3
3
  import "./DatePicker.css";
4
4
  import "../../fonts/material-symbols.css";
5
5
  const DAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
@@ -31,140 +31,138 @@ function formatDate(date) {
31
31
  function isSameDay(a, b) {
32
32
  return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
33
33
  }
34
- const DatePicker = ({
35
- value,
36
- size = "default",
37
- disabled = false,
38
- min,
39
- max,
40
- onDateSelect,
41
- className = ""
42
- }) => {
43
- const selectedDate = parseDate(value);
44
- const minDate = parseDate(min);
45
- const maxDate = parseDate(max);
46
- const today = useMemo(() => /* @__PURE__ */ new Date(), []);
47
- const [viewMonth, setViewMonth] = useState(
48
- selectedDate ? selectedDate.getMonth() : today.getMonth()
49
- );
50
- const [viewYear, setViewYear] = useState(
51
- selectedDate ? selectedDate.getFullYear() : today.getFullYear()
52
- );
53
- const baseClass = "ds-date-picker";
54
- const sizeClass = `${baseClass}--${size}`;
55
- const disabledClass = disabled ? `${baseClass}--disabled` : "";
56
- const classes = [baseClass, sizeClass, disabledClass, className].filter(Boolean).join(" ");
57
- const firstDay = new Date(viewYear, viewMonth, 1).getDay();
58
- const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
59
- const daysInPrevMonth = new Date(viewYear, viewMonth, 0).getDate();
60
- const cells = [];
61
- for (let i = firstDay - 1; i >= 0; i--) {
62
- const d = daysInPrevMonth - i;
63
- cells.push({ day: d, inMonth: false, date: new Date(viewYear, viewMonth - 1, d) });
64
- }
65
- for (let d = 1; d <= daysInMonth; d++) {
66
- cells.push({ day: d, inMonth: true, date: new Date(viewYear, viewMonth, d) });
67
- }
68
- const remaining = 42 - cells.length;
69
- for (let d = 1; d <= remaining; d++) {
70
- cells.push({ day: d, inMonth: false, date: new Date(viewYear, viewMonth + 1, d) });
71
- }
72
- const goToPrevMonth = () => {
73
- if (viewMonth === 0) {
74
- setViewMonth(11);
75
- setViewYear(viewYear - 1);
76
- } else {
77
- setViewMonth(viewMonth - 1);
34
+ const DatePicker = React.forwardRef(
35
+ ({ value, size = "default", disabled = false, min, max, onDateSelect, className = "", ...rest }, ref) => {
36
+ const selectedDate = parseDate(value);
37
+ const minDate = parseDate(min);
38
+ const maxDate = parseDate(max);
39
+ const today = useMemo(() => /* @__PURE__ */ new Date(), []);
40
+ const [viewMonth, setViewMonth] = useState(
41
+ selectedDate ? selectedDate.getMonth() : today.getMonth()
42
+ );
43
+ const [viewYear, setViewYear] = useState(
44
+ selectedDate ? selectedDate.getFullYear() : today.getFullYear()
45
+ );
46
+ const baseClass = "ds-date-picker";
47
+ const classes = [
48
+ baseClass,
49
+ `${baseClass}--${size}`,
50
+ disabled ? `${baseClass}--disabled` : "",
51
+ className
52
+ ].filter(Boolean).join(" ");
53
+ const firstDay = new Date(viewYear, viewMonth, 1).getDay();
54
+ const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
55
+ const daysInPrevMonth = new Date(viewYear, viewMonth, 0).getDate();
56
+ const cells = [];
57
+ for (let i = firstDay - 1; i >= 0; i--) {
58
+ const d = daysInPrevMonth - i;
59
+ cells.push({ day: d, inMonth: false, date: new Date(viewYear, viewMonth - 1, d) });
78
60
  }
79
- };
80
- const goToNextMonth = () => {
81
- if (viewMonth === 11) {
82
- setViewMonth(0);
83
- setViewYear(viewYear + 1);
84
- } else {
85
- setViewMonth(viewMonth + 1);
61
+ for (let d = 1; d <= daysInMonth; d++) {
62
+ cells.push({ day: d, inMonth: true, date: new Date(viewYear, viewMonth, d) });
86
63
  }
87
- };
88
- const isDateDisabled = (date) => {
89
- if (disabled) return true;
90
- if (minDate && date < minDate) return true;
91
- if (maxDate && date > maxDate) return true;
92
- return false;
93
- };
94
- const handleSelect = (cell) => {
95
- if (isDateDisabled(cell.date)) return;
96
- if (!cell.inMonth) {
97
- setViewMonth(cell.date.getMonth());
98
- setViewYear(cell.date.getFullYear());
64
+ const remaining = 42 - cells.length;
65
+ for (let d = 1; d <= remaining; d++) {
66
+ cells.push({ day: d, inMonth: false, date: new Date(viewYear, viewMonth + 1, d) });
99
67
  }
100
- onDateSelect?.(formatDate(cell.date));
101
- };
102
- return /* @__PURE__ */ jsxs("div", { className: classes, children: [
103
- /* @__PURE__ */ jsxs("div", { className: `${baseClass}__header`, children: [
104
- /* @__PURE__ */ jsx(
105
- "button",
106
- {
107
- type: "button",
108
- className: `${baseClass}__nav-btn material-symbols-rounded`,
109
- onClick: goToPrevMonth,
110
- disabled,
111
- "aria-label": "Previous month",
112
- children: "chevron_left"
113
- }
114
- ),
115
- /* @__PURE__ */ jsxs("span", { className: `${baseClass}__month-year`, children: [
116
- MONTHS[viewMonth],
117
- " ",
118
- viewYear
119
- ] }),
120
- /* @__PURE__ */ jsx(
121
- "button",
122
- {
123
- type: "button",
124
- className: `${baseClass}__nav-btn material-symbols-rounded`,
125
- onClick: goToNextMonth,
126
- disabled,
127
- "aria-label": "Next month",
128
- children: "chevron_right"
129
- }
130
- )
131
- ] }),
132
- /* @__PURE__ */ jsxs("div", { className: `${baseClass}__grid`, role: "grid", children: [
133
- DAYS.map((day) => /* @__PURE__ */ jsx("div", { className: `${baseClass}__day-header`, role: "columnheader", children: day }, day)),
134
- cells.map((cell, idx) => {
135
- const isSelected = selectedDate && isSameDay(cell.date, selectedDate);
136
- const isToday = isSameDay(cell.date, today);
137
- const isCellDisabled = isDateDisabled(cell.date);
138
- const cellClasses = [
139
- `${baseClass}__cell`,
140
- !cell.inMonth ? `${baseClass}__cell--outside` : "",
141
- isSelected ? `${baseClass}__cell--selected` : "",
142
- isToday && !isSelected ? `${baseClass}__cell--today` : "",
143
- isCellDisabled ? `${baseClass}__cell--disabled` : ""
144
- ].filter(Boolean).join(" ");
145
- return /* @__PURE__ */ jsx(
68
+ const goToPrevMonth = () => {
69
+ if (viewMonth === 0) {
70
+ setViewMonth(11);
71
+ setViewYear(viewYear - 1);
72
+ } else {
73
+ setViewMonth(viewMonth - 1);
74
+ }
75
+ };
76
+ const goToNextMonth = () => {
77
+ if (viewMonth === 11) {
78
+ setViewMonth(0);
79
+ setViewYear(viewYear + 1);
80
+ } else {
81
+ setViewMonth(viewMonth + 1);
82
+ }
83
+ };
84
+ const isDateDisabled = (date) => {
85
+ if (disabled) return true;
86
+ if (minDate && date < minDate) return true;
87
+ if (maxDate && date > maxDate) return true;
88
+ return false;
89
+ };
90
+ const handleSelect = (cell) => {
91
+ if (isDateDisabled(cell.date)) return;
92
+ if (!cell.inMonth) {
93
+ setViewMonth(cell.date.getMonth());
94
+ setViewYear(cell.date.getFullYear());
95
+ }
96
+ onDateSelect?.(formatDate(cell.date));
97
+ };
98
+ return /* @__PURE__ */ jsxs("div", { ...rest, ref, className: classes, children: [
99
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__header`, children: [
100
+ /* @__PURE__ */ jsx(
146
101
  "button",
147
102
  {
148
103
  type: "button",
149
- className: cellClasses,
150
- onClick: () => handleSelect(cell),
151
- disabled: isCellDisabled,
152
- "aria-label": cell.date.toLocaleDateString(void 0, {
153
- weekday: "long",
154
- year: "numeric",
155
- month: "long",
156
- day: "numeric"
157
- }),
158
- "aria-selected": isSelected || void 0,
159
- role: "gridcell",
160
- children: cell.day
161
- },
162
- idx
163
- );
164
- })
165
- ] })
166
- ] });
167
- };
104
+ className: `${baseClass}__nav-btn material-symbols-rounded`,
105
+ onClick: goToPrevMonth,
106
+ disabled,
107
+ "aria-label": "Previous month",
108
+ children: "chevron_left"
109
+ }
110
+ ),
111
+ /* @__PURE__ */ jsxs("span", { className: `${baseClass}__month-year`, children: [
112
+ MONTHS[viewMonth],
113
+ " ",
114
+ viewYear
115
+ ] }),
116
+ /* @__PURE__ */ jsx(
117
+ "button",
118
+ {
119
+ type: "button",
120
+ className: `${baseClass}__nav-btn material-symbols-rounded`,
121
+ onClick: goToNextMonth,
122
+ disabled,
123
+ "aria-label": "Next month",
124
+ children: "chevron_right"
125
+ }
126
+ )
127
+ ] }),
128
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__grid`, role: "grid", children: [
129
+ DAYS.map((day) => /* @__PURE__ */ jsx("div", { className: `${baseClass}__day-header`, role: "columnheader", children: day }, day)),
130
+ cells.map((cell, idx) => {
131
+ const isSelected = selectedDate && isSameDay(cell.date, selectedDate);
132
+ const isToday = isSameDay(cell.date, today);
133
+ const isCellDisabled = isDateDisabled(cell.date);
134
+ const cellClasses = [
135
+ `${baseClass}__cell`,
136
+ !cell.inMonth ? `${baseClass}__cell--outside` : "",
137
+ isSelected ? `${baseClass}__cell--selected` : "",
138
+ isToday && !isSelected ? `${baseClass}__cell--today` : "",
139
+ isCellDisabled ? `${baseClass}__cell--disabled` : ""
140
+ ].filter(Boolean).join(" ");
141
+ return /* @__PURE__ */ jsx(
142
+ "button",
143
+ {
144
+ type: "button",
145
+ className: cellClasses,
146
+ onClick: () => handleSelect(cell),
147
+ disabled: isCellDisabled,
148
+ "aria-label": cell.date.toLocaleDateString(void 0, {
149
+ weekday: "long",
150
+ year: "numeric",
151
+ month: "long",
152
+ day: "numeric"
153
+ }),
154
+ "aria-selected": isSelected || void 0,
155
+ role: "gridcell",
156
+ children: cell.day
157
+ },
158
+ idx
159
+ );
160
+ })
161
+ ] })
162
+ ] });
163
+ }
164
+ );
165
+ DatePicker.displayName = "DatePicker";
168
166
  export {
169
167
  DatePicker
170
168
  };
@@ -1,10 +1,14 @@
1
1
  import { default as React } from 'react';
2
- export interface DialogProps {
2
+ /** Props owned by Dialog itself — everything else falls through to the panel. */
3
+ type DialogOwnProps = {
3
4
  /** Whether the dialog is open */
4
5
  open: boolean;
5
6
  /** Callback when dialog requests to close */
6
7
  onOpenChange: (open: boolean) => void;
7
- /** Dialog title */
8
+ /**
9
+ * Dialog title.
10
+ * Note: this shadows the native `title` tooltip attribute, which Dialog does not expose.
11
+ */
8
12
  title: string;
9
13
  /** Optional subtitle under the title */
10
14
  description?: string;
@@ -16,13 +20,20 @@ export interface DialogProps {
16
20
  size?: 'sm' | 'md' | 'lg';
17
21
  /** Whether ESC, backdrop click, and the close button can dismiss */
18
22
  dismissible?: boolean;
19
- /** Additional CSS classes */
23
+ /** Additional CSS classes — applied to the portal container, not the panel */
20
24
  className?: string;
25
+ };
26
+ export interface DialogProps extends DialogOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof DialogOwnProps> {
21
27
  }
22
28
  /**
23
29
  * Dialog component — a general-purpose modal with arbitrary
24
30
  * content and an optional footer. Traps focus, restores it on
25
31
  * close, locks body scroll, and supports ESC / backdrop / close
26
32
  * button dismissal. For confirm/cancel prompts use AlertDialog.
33
+ *
34
+ * Renders through a portal into `document.body`. The forwarded ref and any
35
+ * unrecognised props target the **panel** (the `role="dialog"` element), not the
36
+ * portal container — that is the element worth focusing or measuring.
27
37
  */
28
- export declare const Dialog: ({ open, onOpenChange, title, description, children, footer, size, dismissible, className, }: DialogProps) => React.ReactPortal | null;
38
+ export declare const Dialog: React.ForwardRefExoticComponent<DialogProps & React.RefAttributes<HTMLDivElement>>;
39
+ export {};
@@ -1,132 +1,149 @@
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 "./Dialog.css";
5
5
  import "../../fonts/material-symbols.css";
6
6
  const emptySubscribe = () => () => {
7
7
  };
8
- const Dialog = ({
9
- open,
10
- onOpenChange,
11
- title,
12
- description,
13
- children,
14
- footer,
15
- size = "md",
16
- dismissible = true,
17
- className = ""
18
- }) => {
19
- const panelRef = useRef(null);
20
- const previousFocusRef = useRef(null);
21
- const titleId = useId();
22
- const descId = useId();
23
- const baseClass = "ds-dialog";
24
- const mounted = useSyncExternalStore(emptySubscribe, () => true, () => false);
25
- useEffect(() => {
26
- if (open) {
27
- previousFocusRef.current = document.activeElement;
28
- }
29
- }, [open]);
30
- useEffect(() => {
31
- if (!open) return;
32
- const panel = panelRef.current;
33
- if (panel) {
34
- const first = panel.querySelector(
35
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
36
- );
37
- (first ?? panel).focus();
38
- }
39
- const handleKeyDown = (e) => {
40
- if (e.key === "Escape" && dismissible) {
41
- onOpenChange(false);
42
- return;
8
+ const Dialog = React.forwardRef(
9
+ ({
10
+ open,
11
+ onOpenChange,
12
+ title,
13
+ description,
14
+ children,
15
+ footer,
16
+ size = "md",
17
+ dismissible = true,
18
+ className = "",
19
+ ...rest
20
+ }, ref) => {
21
+ const panelRef = useRef(null);
22
+ const previousFocusRef = useRef(null);
23
+ const titleId = useId();
24
+ const descId = useId();
25
+ const baseClass = "ds-dialog";
26
+ const setPanelRef = (node) => {
27
+ panelRef.current = node;
28
+ if (typeof ref === "function") ref(node);
29
+ else if (ref) ref.current = node;
30
+ };
31
+ const mounted = useSyncExternalStore(
32
+ emptySubscribe,
33
+ () => true,
34
+ () => false
35
+ );
36
+ useEffect(() => {
37
+ if (open) {
38
+ previousFocusRef.current = document.activeElement;
43
39
  }
44
- if (e.key === "Tab" && panel) {
45
- const focusable = panel.querySelectorAll(
40
+ }, [open]);
41
+ useEffect(() => {
42
+ if (!open) return;
43
+ const panel = panelRef.current;
44
+ if (panel) {
45
+ const first = panel.querySelector(
46
46
  'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
47
47
  );
48
- if (focusable.length === 0) return;
49
- const first = focusable[0];
50
- const last = focusable[focusable.length - 1];
51
- if (e.shiftKey && document.activeElement === first) {
52
- e.preventDefault();
53
- last.focus();
54
- } else if (!e.shiftKey && document.activeElement === last) {
55
- e.preventDefault();
56
- first.focus();
48
+ (first ?? panel).focus();
49
+ }
50
+ const handleKeyDown = (e) => {
51
+ if (e.key === "Escape" && dismissible) {
52
+ onOpenChange(false);
53
+ return;
57
54
  }
55
+ if (e.key === "Tab" && panel) {
56
+ const focusable = panel.querySelectorAll(
57
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
58
+ );
59
+ if (focusable.length === 0) return;
60
+ const first = focusable[0];
61
+ const last = focusable[focusable.length - 1];
62
+ if (e.shiftKey && document.activeElement === first) {
63
+ e.preventDefault();
64
+ last.focus();
65
+ } else if (!e.shiftKey && document.activeElement === last) {
66
+ e.preventDefault();
67
+ first.focus();
68
+ }
69
+ }
70
+ };
71
+ document.addEventListener("keydown", handleKeyDown);
72
+ return () => document.removeEventListener("keydown", handleKeyDown);
73
+ }, [open, dismissible, onOpenChange]);
74
+ useEffect(() => {
75
+ if (!open && previousFocusRef.current) {
76
+ previousFocusRef.current.focus();
77
+ previousFocusRef.current = null;
58
78
  }
79
+ }, [open]);
80
+ useEffect(() => {
81
+ if (open) {
82
+ document.body.style.overflow = "hidden";
83
+ } else {
84
+ document.body.style.overflow = "";
85
+ }
86
+ return () => {
87
+ document.body.style.overflow = "";
88
+ };
89
+ }, [open]);
90
+ const handleBackdropClick = () => {
91
+ if (dismissible) onOpenChange(false);
59
92
  };
60
- document.addEventListener("keydown", handleKeyDown);
61
- return () => document.removeEventListener("keydown", handleKeyDown);
62
- }, [open, dismissible, onOpenChange]);
63
- useEffect(() => {
64
- if (!open && previousFocusRef.current) {
65
- previousFocusRef.current.focus();
66
- previousFocusRef.current = null;
67
- }
68
- }, [open]);
69
- useEffect(() => {
70
- if (open) {
71
- document.body.style.overflow = "hidden";
72
- } else {
73
- document.body.style.overflow = "";
74
- }
75
- return () => {
76
- document.body.style.overflow = "";
77
- };
78
- }, [open]);
79
- const handleBackdropClick = () => {
80
- if (dismissible) onOpenChange(false);
81
- };
82
- const openClass = open ? `${baseClass}--open` : "";
83
- const sizeClass = `${baseClass}--${size}`;
84
- const containerClasses = [baseClass, sizeClass, openClass, className].filter(Boolean).join(" ");
85
- const dialog = /* @__PURE__ */ jsxs("div", { className: containerClasses, children: [
86
- /* @__PURE__ */ jsx("div", { className: `${baseClass}__backdrop`, onClick: handleBackdropClick }),
87
- /* @__PURE__ */ jsxs(
88
- "div",
89
- {
90
- className: `${baseClass}__panel`,
91
- ref: panelRef,
92
- role: "dialog",
93
- "aria-modal": "true",
94
- "aria-labelledby": titleId,
95
- "aria-describedby": description ? descId : void 0,
96
- tabIndex: -1,
97
- children: [
98
- /* @__PURE__ */ jsxs("div", { className: `${baseClass}__header`, children: [
99
- /* @__PURE__ */ jsxs("div", { className: `${baseClass}__heading`, children: [
100
- /* @__PURE__ */ jsx("h2", { className: `${baseClass}__title`, id: titleId, children: title }),
101
- description && /* @__PURE__ */ jsx("p", { className: `${baseClass}__description`, id: descId, children: description })
93
+ const containerClasses = [
94
+ baseClass,
95
+ `${baseClass}--${size}`,
96
+ open ? `${baseClass}--open` : "",
97
+ className
98
+ ].filter(Boolean).join(" ");
99
+ const dialog = /* @__PURE__ */ jsxs("div", { className: containerClasses, children: [
100
+ /* @__PURE__ */ jsx("div", { className: `${baseClass}__backdrop`, onClick: handleBackdropClick }),
101
+ /* @__PURE__ */ jsxs(
102
+ "div",
103
+ {
104
+ ...rest,
105
+ className: `${baseClass}__panel`,
106
+ ref: setPanelRef,
107
+ role: "dialog",
108
+ "aria-modal": "true",
109
+ "aria-labelledby": titleId,
110
+ "aria-describedby": description ? descId : rest["aria-describedby"],
111
+ tabIndex: -1,
112
+ children: [
113
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__header`, children: [
114
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__heading`, children: [
115
+ /* @__PURE__ */ jsx("h2", { className: `${baseClass}__title`, id: titleId, children: title }),
116
+ description && /* @__PURE__ */ jsx("p", { className: `${baseClass}__description`, id: descId, children: description })
117
+ ] }),
118
+ dismissible && /* @__PURE__ */ jsx(
119
+ "button",
120
+ {
121
+ type: "button",
122
+ className: `${baseClass}__close`,
123
+ onClick: () => onOpenChange(false),
124
+ "aria-label": "Close dialog",
125
+ children: /* @__PURE__ */ jsx(
126
+ "span",
127
+ {
128
+ className: `${baseClass}__close-icon material-symbols-rounded`,
129
+ "aria-hidden": "true",
130
+ children: "close"
131
+ }
132
+ )
133
+ }
134
+ )
102
135
  ] }),
103
- dismissible && /* @__PURE__ */ jsx(
104
- "button",
105
- {
106
- type: "button",
107
- className: `${baseClass}__close`,
108
- onClick: () => onOpenChange(false),
109
- "aria-label": "Close dialog",
110
- children: /* @__PURE__ */ jsx(
111
- "span",
112
- {
113
- className: `${baseClass}__close-icon material-symbols-rounded`,
114
- "aria-hidden": "true",
115
- children: "close"
116
- }
117
- )
118
- }
119
- )
120
- ] }),
121
- children && /* @__PURE__ */ jsx("div", { className: `${baseClass}__body`, children }),
122
- footer && /* @__PURE__ */ jsx("div", { className: `${baseClass}__footer`, children: footer })
123
- ]
124
- }
125
- )
126
- ] });
127
- if (!mounted) return null;
128
- return ReactDOM.createPortal(dialog, document.body);
129
- };
136
+ children && /* @__PURE__ */ jsx("div", { className: `${baseClass}__body`, children }),
137
+ footer && /* @__PURE__ */ jsx("div", { className: `${baseClass}__footer`, children: footer })
138
+ ]
139
+ }
140
+ )
141
+ ] });
142
+ if (!mounted) return null;
143
+ return ReactDOM.createPortal(dialog, document.body);
144
+ }
145
+ );
146
+ Dialog.displayName = "Dialog";
130
147
  export {
131
148
  Dialog
132
149
  };
@@ -1,10 +1,14 @@
1
1
  import { default as React } from 'react';
2
- export interface DrawerProps {
2
+ /** Props owned by Drawer itself — everything else falls through to the panel. */
3
+ type DrawerOwnProps = {
3
4
  /** Whether the drawer is open */
4
5
  open: boolean;
5
6
  /** Callback when the drawer requests to close */
6
7
  onOpenChange: (open: boolean) => void;
7
- /** Drawer title */
8
+ /**
9
+ * Drawer title.
10
+ * Note: this shadows the native `title` tooltip attribute, which Drawer does not expose.
11
+ */
8
12
  title: string;
9
13
  /** Optional subtitle under the title */
10
14
  description?: string;
@@ -18,13 +22,20 @@ export interface DrawerProps {
18
22
  size?: 'sm' | 'md' | 'lg';
19
23
  /** Whether ESC, scrim click, and the close button can dismiss */
20
24
  dismissible?: boolean;
21
- /** Additional CSS classes */
25
+ /** Additional CSS classes — applied to the portal container, not the panel */
22
26
  className?: string;
27
+ };
28
+ export interface DrawerProps extends DrawerOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof DrawerOwnProps> {
23
29
  }
24
30
  /**
25
31
  * Drawer component — an edge-anchored panel that slides in over the page.
26
32
  * Shares Dialog's modal semantics (focus trap, focus restore, scroll lock,
27
33
  * ESC/scrim dismissal) but is anchored to a viewport edge, which suits
28
34
  * filter panels, detail views, and mobile navigation.
35
+ *
36
+ * Renders through a portal into `document.body`. The forwarded ref and any
37
+ * unrecognised props target the **panel** (the `role="dialog"` element), not the
38
+ * portal container.
29
39
  */
30
- export declare const Drawer: ({ open, onOpenChange, title, description, children, footer, side, size, dismissible, className, }: DrawerProps) => React.ReactPortal | null;
40
+ export declare const Drawer: React.ForwardRefExoticComponent<DrawerProps & React.RefAttributes<HTMLDivElement>>;
41
+ export {};