@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.
- package/README.md +6 -4
- package/components/Breadcrumb/Breadcrumb.css +9 -1
- package/components/Button/Button.d.ts +32 -16
- package/components/Button/Button.js +66 -58
- package/components/Card/Card.d.ts +17 -5
- package/components/Card/Card.js +101 -80
- package/components/Checkbox/Checkbox.d.ts +24 -12
- package/components/Checkbox/Checkbox.js +99 -85
- package/components/Combobox/Combobox.d.ts +19 -7
- package/components/Combobox/Combobox.js +18 -9
- package/components/DateInput/DateInput.d.ts +18 -21
- package/components/DateInput/DateInput.js +71 -63
- package/components/DatePicker/DatePicker.d.ts +9 -2
- package/components/DatePicker/DatePicker.js +127 -129
- package/components/Dialog/Dialog.d.ts +15 -4
- package/components/Dialog/Dialog.js +133 -116
- package/components/Drawer/Drawer.d.ts +15 -4
- package/components/Drawer/Drawer.js +133 -119
- package/components/Dropdown/Dropdown.d.ts +24 -8
- package/components/Dropdown/Dropdown.js +226 -186
- package/components/FileInput/FileInput.d.ts +13 -17
- package/components/FileInput/FileInput.js +177 -158
- package/components/Input/Input.d.ts +23 -22
- package/components/Input/Input.js +87 -65
- package/components/RadioButton/RadioButton.d.ts +26 -13
- package/components/RadioButton/RadioButton.js +95 -70
- package/components/SelectionCard/SelectionCard.d.ts +10 -4
- package/components/SelectionCard/SelectionCard.js +72 -56
- package/components/Slider/Slider.d.ts +19 -10
- package/components/Slider/Slider.js +50 -40
- package/components/Table/Table.d.ts +10 -2
- package/components/Table/Table.js +58 -58
- package/components/Textarea/Textarea.d.ts +21 -22
- package/components/Textarea/Textarea.js +77 -68
- package/components/ToggleGroup/ToggleGroup.d.ts +18 -8
- package/components/ToggleGroup/ToggleGroup.js +61 -44
- package/components/ToggleSwitch/ToggleSwitch.d.ts +17 -9
- package/components/ToggleSwitch/ToggleSwitch.js +57 -44
- 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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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:
|
|
150
|
-
onClick:
|
|
151
|
-
disabled
|
|
152
|
-
"aria-label":
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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:
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (e.
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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:
|
|
40
|
+
export declare const Drawer: React.ForwardRefExoticComponent<DrawerProps & React.RefAttributes<HTMLDivElement>>;
|
|
41
|
+
export {};
|