@dododog/ui 0.16.0 → 0.17.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/dist/{chunk-R4G5LFPN.js → chunk-2NMA3AXV.js} +37 -8
- package/dist/{chunk-Z7CF7EQJ.mjs → chunk-52M2KUC4.mjs} +37 -8
- package/dist/chunk-DUCRCDLI.mjs +78 -0
- package/dist/{chunk-HH4X2JYO.js → chunk-EKOGDJUT.js} +32 -4
- package/dist/{chunk-QWDKSY6Y.js → chunk-OZ4RJDDZ.js} +46 -2
- package/dist/{chunk-762LQMWP.js → chunk-RAXPJ7VC.js} +66 -8
- package/dist/{chunk-N5ZNXNCO.mjs → chunk-UYOPZZBN.mjs} +32 -4
- package/dist/chunk-YGAWCP65.mjs +109 -0
- package/dist/components/Modal/index.d.mts +42 -1
- package/dist/components/Modal/index.d.ts +42 -1
- package/dist/components/Modal/index.js +2 -2
- package/dist/components/Modal/index.mjs +1 -1
- package/dist/components/SegmentedControl/index.d.mts +16 -0
- package/dist/components/SegmentedControl/index.d.ts +16 -0
- package/dist/components/SegmentedControl/index.js +2 -2
- package/dist/components/SegmentedControl/index.mjs +1 -1
- package/dist/components/Tabs/index.d.mts +21 -0
- package/dist/components/Tabs/index.d.ts +21 -0
- package/dist/components/Tabs/index.js +2 -2
- package/dist/components/Tabs/index.mjs +1 -1
- package/dist/components/Textarea/index.js +3 -3
- package/dist/components/Textarea/index.mjs +1 -1
- package/dist/index.d.mts +16 -3
- package/dist/index.d.ts +16 -3
- package/dist/index.js +112 -110
- package/dist/index.mjs +23 -21
- package/package.json +11 -11
- package/dist/chunk-36Y7UU32.mjs +0 -34
- package/dist/chunk-VOZPGXQD.mjs +0 -51
|
@@ -46,26 +46,55 @@ var textareaVariants = classVarianceAuthority.cva(
|
|
|
46
46
|
}
|
|
47
47
|
);
|
|
48
48
|
var Textarea = React__namespace.forwardRef(
|
|
49
|
-
({
|
|
49
|
+
({
|
|
50
|
+
className,
|
|
51
|
+
size,
|
|
52
|
+
variant,
|
|
53
|
+
label,
|
|
54
|
+
error,
|
|
55
|
+
required,
|
|
56
|
+
rows = 4,
|
|
57
|
+
id,
|
|
58
|
+
"aria-describedby": ariaDescribedBy,
|
|
59
|
+
"aria-invalid": ariaInvalid,
|
|
60
|
+
"aria-required": ariaRequired,
|
|
61
|
+
...props
|
|
62
|
+
}, ref) => {
|
|
63
|
+
const generatedId = React__namespace.useId();
|
|
64
|
+
const textareaId = id ?? generatedId;
|
|
65
|
+
const errorId = `${textareaId}-error`;
|
|
50
66
|
const resolvedVariant = error ? "error" : variant ?? "default";
|
|
67
|
+
const describedBy = [ariaDescribedBy, error ? errorId : null].filter(Boolean).join(" ") || void 0;
|
|
51
68
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full", children: [
|
|
52
|
-
label && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
53
|
-
label,
|
|
54
|
-
|
|
55
|
-
|
|
69
|
+
label && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
70
|
+
"label",
|
|
71
|
+
{
|
|
72
|
+
htmlFor: textareaId,
|
|
73
|
+
className: "block text-sm font-medium text-primary mb-1.5",
|
|
74
|
+
children: [
|
|
75
|
+
label,
|
|
76
|
+
required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-secondary", "aria-hidden": "true", children: " *" })
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
),
|
|
56
80
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
57
81
|
"textarea",
|
|
58
82
|
{
|
|
83
|
+
...props,
|
|
59
84
|
ref,
|
|
85
|
+
id: textareaId,
|
|
60
86
|
rows,
|
|
87
|
+
required,
|
|
88
|
+
"aria-required": ariaRequired ?? (required ? true : void 0),
|
|
89
|
+
"aria-invalid": ariaInvalid ?? (error ? true : void 0),
|
|
90
|
+
"aria-describedby": describedBy,
|
|
61
91
|
className: chunkADIDI7AJ_js.cn(
|
|
62
92
|
textareaVariants({ size, variant: resolvedVariant }),
|
|
63
93
|
className
|
|
64
|
-
)
|
|
65
|
-
...props
|
|
94
|
+
)
|
|
66
95
|
}
|
|
67
96
|
),
|
|
68
|
-
error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-1 text-sm text-red-600", children: error })
|
|
97
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { id: errorId, role: "alert", className: "mt-1 text-sm text-red-600", children: error })
|
|
69
98
|
] });
|
|
70
99
|
}
|
|
71
100
|
);
|
|
@@ -24,26 +24,55 @@ var textareaVariants = cva(
|
|
|
24
24
|
}
|
|
25
25
|
);
|
|
26
26
|
var Textarea = React.forwardRef(
|
|
27
|
-
({
|
|
27
|
+
({
|
|
28
|
+
className,
|
|
29
|
+
size,
|
|
30
|
+
variant,
|
|
31
|
+
label,
|
|
32
|
+
error,
|
|
33
|
+
required,
|
|
34
|
+
rows = 4,
|
|
35
|
+
id,
|
|
36
|
+
"aria-describedby": ariaDescribedBy,
|
|
37
|
+
"aria-invalid": ariaInvalid,
|
|
38
|
+
"aria-required": ariaRequired,
|
|
39
|
+
...props
|
|
40
|
+
}, ref) => {
|
|
41
|
+
const generatedId = React.useId();
|
|
42
|
+
const textareaId = id ?? generatedId;
|
|
43
|
+
const errorId = `${textareaId}-error`;
|
|
28
44
|
const resolvedVariant = error ? "error" : variant ?? "default";
|
|
45
|
+
const describedBy = [ariaDescribedBy, error ? errorId : null].filter(Boolean).join(" ") || void 0;
|
|
29
46
|
return /* @__PURE__ */ jsxs("div", { className: "w-full", children: [
|
|
30
|
-
label && /* @__PURE__ */ jsxs(
|
|
31
|
-
label,
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
label && /* @__PURE__ */ jsxs(
|
|
48
|
+
"label",
|
|
49
|
+
{
|
|
50
|
+
htmlFor: textareaId,
|
|
51
|
+
className: "block text-sm font-medium text-primary mb-1.5",
|
|
52
|
+
children: [
|
|
53
|
+
label,
|
|
54
|
+
required && /* @__PURE__ */ jsx("span", { className: "text-secondary", "aria-hidden": "true", children: " *" })
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
),
|
|
34
58
|
/* @__PURE__ */ jsx(
|
|
35
59
|
"textarea",
|
|
36
60
|
{
|
|
61
|
+
...props,
|
|
37
62
|
ref,
|
|
63
|
+
id: textareaId,
|
|
38
64
|
rows,
|
|
65
|
+
required,
|
|
66
|
+
"aria-required": ariaRequired ?? (required ? true : void 0),
|
|
67
|
+
"aria-invalid": ariaInvalid ?? (error ? true : void 0),
|
|
68
|
+
"aria-describedby": describedBy,
|
|
39
69
|
className: cn(
|
|
40
70
|
textareaVariants({ size, variant: resolvedVariant }),
|
|
41
71
|
className
|
|
42
|
-
)
|
|
43
|
-
...props
|
|
72
|
+
)
|
|
44
73
|
}
|
|
45
74
|
),
|
|
46
|
-
error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 text-sm text-red-600", children: error })
|
|
75
|
+
error && /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "mt-1 text-sm text-red-600", children: error })
|
|
47
76
|
] });
|
|
48
77
|
}
|
|
49
78
|
);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { cn } from './chunk-IMKLN273.mjs';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
var SegmentedControl = React.forwardRef(
|
|
6
|
+
({
|
|
7
|
+
options,
|
|
8
|
+
value,
|
|
9
|
+
onChange,
|
|
10
|
+
className,
|
|
11
|
+
"aria-label": ariaLabel,
|
|
12
|
+
"aria-labelledby": ariaLabelledBy
|
|
13
|
+
}, ref) => {
|
|
14
|
+
const optionRefs = React.useRef([]);
|
|
15
|
+
const selectedIndex = options.findIndex((option) => option.value === value);
|
|
16
|
+
const rovingIndex = selectedIndex === -1 ? 0 : selectedIndex;
|
|
17
|
+
const handleKeyDown = (event, index) => {
|
|
18
|
+
const lastIndex = options.length - 1;
|
|
19
|
+
let nextIndex;
|
|
20
|
+
switch (event.key) {
|
|
21
|
+
case "ArrowRight":
|
|
22
|
+
case "ArrowDown":
|
|
23
|
+
nextIndex = index === lastIndex ? 0 : index + 1;
|
|
24
|
+
break;
|
|
25
|
+
case "ArrowLeft":
|
|
26
|
+
case "ArrowUp":
|
|
27
|
+
nextIndex = index === 0 ? lastIndex : index - 1;
|
|
28
|
+
break;
|
|
29
|
+
case "Home":
|
|
30
|
+
nextIndex = 0;
|
|
31
|
+
break;
|
|
32
|
+
case "End":
|
|
33
|
+
nextIndex = lastIndex;
|
|
34
|
+
break;
|
|
35
|
+
default:
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
event.preventDefault();
|
|
39
|
+
const nextOption = options[nextIndex];
|
|
40
|
+
if (!nextOption) return;
|
|
41
|
+
onChange(nextOption.value);
|
|
42
|
+
optionRefs.current[nextIndex]?.focus();
|
|
43
|
+
};
|
|
44
|
+
return /* @__PURE__ */ jsx(
|
|
45
|
+
"div",
|
|
46
|
+
{
|
|
47
|
+
ref,
|
|
48
|
+
className: cn("flex gap-1 bg-gray-100 rounded-full p-1", className),
|
|
49
|
+
role: "radiogroup",
|
|
50
|
+
"aria-label": ariaLabelledBy ? void 0 : ariaLabel,
|
|
51
|
+
"aria-labelledby": ariaLabelledBy,
|
|
52
|
+
children: options.map((option, index) => /* @__PURE__ */ jsx(
|
|
53
|
+
"button",
|
|
54
|
+
{
|
|
55
|
+
type: "button",
|
|
56
|
+
role: "radio",
|
|
57
|
+
"aria-checked": value === option.value,
|
|
58
|
+
tabIndex: index === rovingIndex ? 0 : -1,
|
|
59
|
+
ref: (node) => {
|
|
60
|
+
optionRefs.current[index] = node;
|
|
61
|
+
},
|
|
62
|
+
onKeyDown: (event) => handleKeyDown(event, index),
|
|
63
|
+
onClick: () => onChange(option.value),
|
|
64
|
+
className: cn(
|
|
65
|
+
"px-3 py-1.5 text-sm rounded-full transition-all",
|
|
66
|
+
value === option.value ? "bg-white text-gray-900 shadow-sm font-medium" : "text-gray-600 hover:text-gray-900"
|
|
67
|
+
),
|
|
68
|
+
children: option.label
|
|
69
|
+
},
|
|
70
|
+
option.value
|
|
71
|
+
))
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
SegmentedControl.displayName = "SegmentedControl";
|
|
77
|
+
|
|
78
|
+
export { SegmentedControl };
|
|
@@ -37,6 +37,7 @@ var FOCUSABLE_SELECTOR = [
|
|
|
37
37
|
"[contenteditable]:not([contenteditable='false'])",
|
|
38
38
|
'[tabindex]:not([tabindex="-1"])'
|
|
39
39
|
].join(", ");
|
|
40
|
+
var DEFAULT_Z_INDEX_CLASS = "z-50";
|
|
40
41
|
function getFocusableElements(panel) {
|
|
41
42
|
return Array.from(panel.querySelectorAll(FOCUSABLE_SELECTOR)).filter(
|
|
42
43
|
(element) => !element.closest("[hidden], [aria-hidden='true'], details:not([open])")
|
|
@@ -54,6 +55,11 @@ var Modal = React__namespace.forwardRef(
|
|
|
54
55
|
children,
|
|
55
56
|
title,
|
|
56
57
|
className,
|
|
58
|
+
variant = "centered",
|
|
59
|
+
overlayClassName,
|
|
60
|
+
zIndexClassName = DEFAULT_Z_INDEX_CLASS,
|
|
61
|
+
initialFocusRef,
|
|
62
|
+
returnFocusTo,
|
|
57
63
|
"aria-label": ariaLabel,
|
|
58
64
|
"aria-labelledby": ariaLabelledBy,
|
|
59
65
|
"aria-describedby": ariaDescribedBy
|
|
@@ -62,10 +68,20 @@ var Modal = React__namespace.forwardRef(
|
|
|
62
68
|
const generatedTitleId = React__namespace.useId();
|
|
63
69
|
const titleId = title ? `${generatedTitleId}-title` : void 0;
|
|
64
70
|
const labelledBy = ariaLabelledBy ?? titleId;
|
|
71
|
+
const isFullBleed = variant === "fullBleed";
|
|
72
|
+
const initialFocusRefLatest = React__namespace.useRef(initialFocusRef);
|
|
73
|
+
initialFocusRefLatest.current = initialFocusRef;
|
|
74
|
+
const returnFocusToLatest = React__namespace.useRef(returnFocusTo);
|
|
75
|
+
returnFocusToLatest.current = returnFocusTo;
|
|
65
76
|
React__namespace.useEffect(() => {
|
|
66
77
|
if (!isOpen) return;
|
|
67
78
|
const trigger = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
68
79
|
return () => {
|
|
80
|
+
const designated = returnFocusToLatest.current?.current ?? null;
|
|
81
|
+
if (designated && designated.isConnected) {
|
|
82
|
+
designated.focus();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
69
85
|
if (trigger && trigger.isConnected) trigger.focus();
|
|
70
86
|
};
|
|
71
87
|
}, [isOpen]);
|
|
@@ -73,6 +89,11 @@ var Modal = React__namespace.forwardRef(
|
|
|
73
89
|
if (!isOpen) return;
|
|
74
90
|
const panel = panelRef.current;
|
|
75
91
|
if (!panel) return;
|
|
92
|
+
const preferred = initialFocusRefLatest.current?.current ?? null;
|
|
93
|
+
if (preferred) {
|
|
94
|
+
preferred.focus();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
76
97
|
const focusable = getFocusableElements(panel);
|
|
77
98
|
(focusable[0] ?? panel).focus();
|
|
78
99
|
}, [isOpen]);
|
|
@@ -124,14 +145,21 @@ var Modal = React__namespace.forwardRef(
|
|
|
124
145
|
"div",
|
|
125
146
|
{
|
|
126
147
|
ref,
|
|
127
|
-
className:
|
|
148
|
+
className: chunkADIDI7AJ_js.cn(
|
|
149
|
+
"fixed inset-0",
|
|
150
|
+
zIndexClassName,
|
|
151
|
+
// La mise en page centree (et sa marge de 16 px) n'existe que dans la
|
|
152
|
+
// variante `centered` : une surface bord a bord n'en veut pas.
|
|
153
|
+
!isFullBleed && "flex items-center justify-center p-4",
|
|
154
|
+
overlayClassName
|
|
155
|
+
),
|
|
128
156
|
role: "dialog",
|
|
129
157
|
"aria-modal": "true",
|
|
130
158
|
"aria-labelledby": labelledBy,
|
|
131
159
|
"aria-label": labelledBy ? void 0 : ariaLabel,
|
|
132
160
|
"aria-describedby": ariaDescribedBy,
|
|
133
161
|
children: [
|
|
134
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
162
|
+
!isFullBleed && /* @__PURE__ */ jsxRuntime.jsx(
|
|
135
163
|
"div",
|
|
136
164
|
{
|
|
137
165
|
className: "absolute inset-0 bg-black/50",
|
|
@@ -145,7 +173,7 @@ var Modal = React__namespace.forwardRef(
|
|
|
145
173
|
ref: panelRef,
|
|
146
174
|
tabIndex: -1,
|
|
147
175
|
className: chunkADIDI7AJ_js.cn(
|
|
148
|
-
"relative bg-white rounded-2xl shadow-xl max-w-md w-full max-h-[90vh] overflow-auto outline-none",
|
|
176
|
+
isFullBleed ? "relative h-full w-full outline-none" : "relative bg-white rounded-2xl shadow-xl max-w-md w-full max-h-[90vh] overflow-auto outline-none",
|
|
149
177
|
className
|
|
150
178
|
),
|
|
151
179
|
children: [
|
|
@@ -186,7 +214,7 @@ var Modal = React__namespace.forwardRef(
|
|
|
186
214
|
}
|
|
187
215
|
)
|
|
188
216
|
] }),
|
|
189
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: chunkADIDI7AJ_js.cn(title ? "px-6 pb-6" : "p-6"), children })
|
|
217
|
+
isFullBleed ? children : /* @__PURE__ */ jsxRuntime.jsx("div", { className: chunkADIDI7AJ_js.cn(title ? "px-6 pb-6" : "p-6"), children })
|
|
190
218
|
]
|
|
191
219
|
}
|
|
192
220
|
)
|
|
@@ -25,19 +25,63 @@ function _interopNamespace(e) {
|
|
|
25
25
|
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
26
26
|
|
|
27
27
|
var SegmentedControl = React__namespace.forwardRef(
|
|
28
|
-
({
|
|
28
|
+
({
|
|
29
|
+
options,
|
|
30
|
+
value,
|
|
31
|
+
onChange,
|
|
32
|
+
className,
|
|
33
|
+
"aria-label": ariaLabel,
|
|
34
|
+
"aria-labelledby": ariaLabelledBy
|
|
35
|
+
}, ref) => {
|
|
36
|
+
const optionRefs = React__namespace.useRef([]);
|
|
37
|
+
const selectedIndex = options.findIndex((option) => option.value === value);
|
|
38
|
+
const rovingIndex = selectedIndex === -1 ? 0 : selectedIndex;
|
|
39
|
+
const handleKeyDown = (event, index) => {
|
|
40
|
+
const lastIndex = options.length - 1;
|
|
41
|
+
let nextIndex;
|
|
42
|
+
switch (event.key) {
|
|
43
|
+
case "ArrowRight":
|
|
44
|
+
case "ArrowDown":
|
|
45
|
+
nextIndex = index === lastIndex ? 0 : index + 1;
|
|
46
|
+
break;
|
|
47
|
+
case "ArrowLeft":
|
|
48
|
+
case "ArrowUp":
|
|
49
|
+
nextIndex = index === 0 ? lastIndex : index - 1;
|
|
50
|
+
break;
|
|
51
|
+
case "Home":
|
|
52
|
+
nextIndex = 0;
|
|
53
|
+
break;
|
|
54
|
+
case "End":
|
|
55
|
+
nextIndex = lastIndex;
|
|
56
|
+
break;
|
|
57
|
+
default:
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
const nextOption = options[nextIndex];
|
|
62
|
+
if (!nextOption) return;
|
|
63
|
+
onChange(nextOption.value);
|
|
64
|
+
optionRefs.current[nextIndex]?.focus();
|
|
65
|
+
};
|
|
29
66
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
30
67
|
"div",
|
|
31
68
|
{
|
|
32
69
|
ref,
|
|
33
70
|
className: chunkADIDI7AJ_js.cn("flex gap-1 bg-gray-100 rounded-full p-1", className),
|
|
34
71
|
role: "radiogroup",
|
|
35
|
-
|
|
72
|
+
"aria-label": ariaLabelledBy ? void 0 : ariaLabel,
|
|
73
|
+
"aria-labelledby": ariaLabelledBy,
|
|
74
|
+
children: options.map((option, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
36
75
|
"button",
|
|
37
76
|
{
|
|
38
77
|
type: "button",
|
|
39
78
|
role: "radio",
|
|
40
79
|
"aria-checked": value === option.value,
|
|
80
|
+
tabIndex: index === rovingIndex ? 0 : -1,
|
|
81
|
+
ref: (node) => {
|
|
82
|
+
optionRefs.current[index] = node;
|
|
83
|
+
},
|
|
84
|
+
onKeyDown: (event) => handleKeyDown(event, index),
|
|
41
85
|
onClick: () => onChange(option.value),
|
|
42
86
|
className: chunkADIDI7AJ_js.cn(
|
|
43
87
|
"px-3 py-1.5 text-sm rounded-full transition-all",
|
|
@@ -25,17 +25,57 @@ function _interopNamespace(e) {
|
|
|
25
25
|
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
26
26
|
|
|
27
27
|
var Tabs = React__namespace.forwardRef(
|
|
28
|
-
({
|
|
28
|
+
({
|
|
29
|
+
tabs,
|
|
30
|
+
defaultTab,
|
|
31
|
+
value,
|
|
32
|
+
onChange,
|
|
33
|
+
className,
|
|
34
|
+
"aria-label": ariaLabel,
|
|
35
|
+
"aria-labelledby": ariaLabelledBy
|
|
36
|
+
}, ref) => {
|
|
29
37
|
const [internalValue, setInternalValue] = React__namespace.useState(
|
|
30
38
|
defaultTab || tabs[0]?.key || ""
|
|
31
39
|
);
|
|
32
40
|
const activeKey = value ?? internalValue;
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
const idPrefix = React__namespace.useId();
|
|
42
|
+
const tabId = (key) => `${idPrefix}-tab-${key}`;
|
|
43
|
+
const panelId = (key) => `${idPrefix}-panel-${key}`;
|
|
44
|
+
const tabRefs = React__namespace.useRef([]);
|
|
45
|
+
const handleChange = React__namespace.useCallback(
|
|
46
|
+
(key) => {
|
|
47
|
+
if (onChange) {
|
|
48
|
+
onChange(key);
|
|
49
|
+
} else {
|
|
50
|
+
setInternalValue(key);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
[onChange]
|
|
54
|
+
);
|
|
55
|
+
const handleKeyDown = (event, index) => {
|
|
56
|
+
const lastIndex = tabs.length - 1;
|
|
57
|
+
let nextIndex;
|
|
58
|
+
switch (event.key) {
|
|
59
|
+
case "ArrowRight":
|
|
60
|
+
nextIndex = index === lastIndex ? 0 : index + 1;
|
|
61
|
+
break;
|
|
62
|
+
case "ArrowLeft":
|
|
63
|
+
nextIndex = index === 0 ? lastIndex : index - 1;
|
|
64
|
+
break;
|
|
65
|
+
case "Home":
|
|
66
|
+
nextIndex = 0;
|
|
67
|
+
break;
|
|
68
|
+
case "End":
|
|
69
|
+
nextIndex = lastIndex;
|
|
70
|
+
break;
|
|
71
|
+
default:
|
|
72
|
+
return;
|
|
38
73
|
}
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
const nextTab = tabs[nextIndex];
|
|
76
|
+
if (!nextTab) return;
|
|
77
|
+
handleChange(nextTab.key);
|
|
78
|
+
tabRefs.current[nextIndex]?.focus();
|
|
39
79
|
};
|
|
40
80
|
const activeTab = tabs.find((t) => t.key === activeKey);
|
|
41
81
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: chunkADIDI7AJ_js.cn("w-full", className), children: [
|
|
@@ -43,13 +83,22 @@ var Tabs = React__namespace.forwardRef(
|
|
|
43
83
|
"div",
|
|
44
84
|
{
|
|
45
85
|
role: "tablist",
|
|
86
|
+
"aria-label": ariaLabelledBy ? void 0 : ariaLabel,
|
|
87
|
+
"aria-labelledby": ariaLabelledBy,
|
|
46
88
|
className: "flex border-b border-gray-200 gap-1 overflow-x-auto",
|
|
47
|
-
children: tabs.map((tab) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
89
|
+
children: tabs.map((tab, index) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
48
90
|
"button",
|
|
49
91
|
{
|
|
92
|
+
id: tabId(tab.key),
|
|
50
93
|
role: "tab",
|
|
51
94
|
type: "button",
|
|
52
95
|
"aria-selected": tab.key === activeKey,
|
|
96
|
+
"aria-controls": panelId(tab.key),
|
|
97
|
+
tabIndex: tab.key === activeKey ? 0 : -1,
|
|
98
|
+
ref: (node) => {
|
|
99
|
+
tabRefs.current[index] = node;
|
|
100
|
+
},
|
|
101
|
+
onKeyDown: (event) => handleKeyDown(event, index),
|
|
53
102
|
onClick: () => handleChange(tab.key),
|
|
54
103
|
className: chunkADIDI7AJ_js.cn(
|
|
55
104
|
"flex items-center gap-2 px-4 py-3 text-sm font-medium whitespace-nowrap border-b-2 transition-colors -mb-px",
|
|
@@ -64,7 +113,16 @@ var Tabs = React__namespace.forwardRef(
|
|
|
64
113
|
))
|
|
65
114
|
}
|
|
66
115
|
),
|
|
67
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
116
|
+
activeTab && /* @__PURE__ */ jsxRuntime.jsx(
|
|
117
|
+
"div",
|
|
118
|
+
{
|
|
119
|
+
role: "tabpanel",
|
|
120
|
+
id: panelId(activeTab.key),
|
|
121
|
+
"aria-labelledby": tabId(activeTab.key),
|
|
122
|
+
className: "py-4",
|
|
123
|
+
children: activeTab.content
|
|
124
|
+
}
|
|
125
|
+
)
|
|
68
126
|
] });
|
|
69
127
|
}
|
|
70
128
|
);
|
|
@@ -15,6 +15,7 @@ var FOCUSABLE_SELECTOR = [
|
|
|
15
15
|
"[contenteditable]:not([contenteditable='false'])",
|
|
16
16
|
'[tabindex]:not([tabindex="-1"])'
|
|
17
17
|
].join(", ");
|
|
18
|
+
var DEFAULT_Z_INDEX_CLASS = "z-50";
|
|
18
19
|
function getFocusableElements(panel) {
|
|
19
20
|
return Array.from(panel.querySelectorAll(FOCUSABLE_SELECTOR)).filter(
|
|
20
21
|
(element) => !element.closest("[hidden], [aria-hidden='true'], details:not([open])")
|
|
@@ -32,6 +33,11 @@ var Modal = React.forwardRef(
|
|
|
32
33
|
children,
|
|
33
34
|
title,
|
|
34
35
|
className,
|
|
36
|
+
variant = "centered",
|
|
37
|
+
overlayClassName,
|
|
38
|
+
zIndexClassName = DEFAULT_Z_INDEX_CLASS,
|
|
39
|
+
initialFocusRef,
|
|
40
|
+
returnFocusTo,
|
|
35
41
|
"aria-label": ariaLabel,
|
|
36
42
|
"aria-labelledby": ariaLabelledBy,
|
|
37
43
|
"aria-describedby": ariaDescribedBy
|
|
@@ -40,10 +46,20 @@ var Modal = React.forwardRef(
|
|
|
40
46
|
const generatedTitleId = React.useId();
|
|
41
47
|
const titleId = title ? `${generatedTitleId}-title` : void 0;
|
|
42
48
|
const labelledBy = ariaLabelledBy ?? titleId;
|
|
49
|
+
const isFullBleed = variant === "fullBleed";
|
|
50
|
+
const initialFocusRefLatest = React.useRef(initialFocusRef);
|
|
51
|
+
initialFocusRefLatest.current = initialFocusRef;
|
|
52
|
+
const returnFocusToLatest = React.useRef(returnFocusTo);
|
|
53
|
+
returnFocusToLatest.current = returnFocusTo;
|
|
43
54
|
React.useEffect(() => {
|
|
44
55
|
if (!isOpen) return;
|
|
45
56
|
const trigger = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
46
57
|
return () => {
|
|
58
|
+
const designated = returnFocusToLatest.current?.current ?? null;
|
|
59
|
+
if (designated && designated.isConnected) {
|
|
60
|
+
designated.focus();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
47
63
|
if (trigger && trigger.isConnected) trigger.focus();
|
|
48
64
|
};
|
|
49
65
|
}, [isOpen]);
|
|
@@ -51,6 +67,11 @@ var Modal = React.forwardRef(
|
|
|
51
67
|
if (!isOpen) return;
|
|
52
68
|
const panel = panelRef.current;
|
|
53
69
|
if (!panel) return;
|
|
70
|
+
const preferred = initialFocusRefLatest.current?.current ?? null;
|
|
71
|
+
if (preferred) {
|
|
72
|
+
preferred.focus();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
54
75
|
const focusable = getFocusableElements(panel);
|
|
55
76
|
(focusable[0] ?? panel).focus();
|
|
56
77
|
}, [isOpen]);
|
|
@@ -102,14 +123,21 @@ var Modal = React.forwardRef(
|
|
|
102
123
|
"div",
|
|
103
124
|
{
|
|
104
125
|
ref,
|
|
105
|
-
className:
|
|
126
|
+
className: cn(
|
|
127
|
+
"fixed inset-0",
|
|
128
|
+
zIndexClassName,
|
|
129
|
+
// La mise en page centree (et sa marge de 16 px) n'existe que dans la
|
|
130
|
+
// variante `centered` : une surface bord a bord n'en veut pas.
|
|
131
|
+
!isFullBleed && "flex items-center justify-center p-4",
|
|
132
|
+
overlayClassName
|
|
133
|
+
),
|
|
106
134
|
role: "dialog",
|
|
107
135
|
"aria-modal": "true",
|
|
108
136
|
"aria-labelledby": labelledBy,
|
|
109
137
|
"aria-label": labelledBy ? void 0 : ariaLabel,
|
|
110
138
|
"aria-describedby": ariaDescribedBy,
|
|
111
139
|
children: [
|
|
112
|
-
/* @__PURE__ */ jsx(
|
|
140
|
+
!isFullBleed && /* @__PURE__ */ jsx(
|
|
113
141
|
"div",
|
|
114
142
|
{
|
|
115
143
|
className: "absolute inset-0 bg-black/50",
|
|
@@ -123,7 +151,7 @@ var Modal = React.forwardRef(
|
|
|
123
151
|
ref: panelRef,
|
|
124
152
|
tabIndex: -1,
|
|
125
153
|
className: cn(
|
|
126
|
-
"relative bg-white rounded-2xl shadow-xl max-w-md w-full max-h-[90vh] overflow-auto outline-none",
|
|
154
|
+
isFullBleed ? "relative h-full w-full outline-none" : "relative bg-white rounded-2xl shadow-xl max-w-md w-full max-h-[90vh] overflow-auto outline-none",
|
|
127
155
|
className
|
|
128
156
|
),
|
|
129
157
|
children: [
|
|
@@ -164,7 +192,7 @@ var Modal = React.forwardRef(
|
|
|
164
192
|
}
|
|
165
193
|
)
|
|
166
194
|
] }),
|
|
167
|
-
/* @__PURE__ */ jsx("div", { className: cn(title ? "px-6 pb-6" : "p-6"), children })
|
|
195
|
+
isFullBleed ? children : /* @__PURE__ */ jsx("div", { className: cn(title ? "px-6 pb-6" : "p-6"), children })
|
|
168
196
|
]
|
|
169
197
|
}
|
|
170
198
|
)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { cn } from './chunk-IMKLN273.mjs';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
var Tabs = React.forwardRef(
|
|
6
|
+
({
|
|
7
|
+
tabs,
|
|
8
|
+
defaultTab,
|
|
9
|
+
value,
|
|
10
|
+
onChange,
|
|
11
|
+
className,
|
|
12
|
+
"aria-label": ariaLabel,
|
|
13
|
+
"aria-labelledby": ariaLabelledBy
|
|
14
|
+
}, ref) => {
|
|
15
|
+
const [internalValue, setInternalValue] = React.useState(
|
|
16
|
+
defaultTab || tabs[0]?.key || ""
|
|
17
|
+
);
|
|
18
|
+
const activeKey = value ?? internalValue;
|
|
19
|
+
const idPrefix = React.useId();
|
|
20
|
+
const tabId = (key) => `${idPrefix}-tab-${key}`;
|
|
21
|
+
const panelId = (key) => `${idPrefix}-panel-${key}`;
|
|
22
|
+
const tabRefs = React.useRef([]);
|
|
23
|
+
const handleChange = React.useCallback(
|
|
24
|
+
(key) => {
|
|
25
|
+
if (onChange) {
|
|
26
|
+
onChange(key);
|
|
27
|
+
} else {
|
|
28
|
+
setInternalValue(key);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
[onChange]
|
|
32
|
+
);
|
|
33
|
+
const handleKeyDown = (event, index) => {
|
|
34
|
+
const lastIndex = tabs.length - 1;
|
|
35
|
+
let nextIndex;
|
|
36
|
+
switch (event.key) {
|
|
37
|
+
case "ArrowRight":
|
|
38
|
+
nextIndex = index === lastIndex ? 0 : index + 1;
|
|
39
|
+
break;
|
|
40
|
+
case "ArrowLeft":
|
|
41
|
+
nextIndex = index === 0 ? lastIndex : index - 1;
|
|
42
|
+
break;
|
|
43
|
+
case "Home":
|
|
44
|
+
nextIndex = 0;
|
|
45
|
+
break;
|
|
46
|
+
case "End":
|
|
47
|
+
nextIndex = lastIndex;
|
|
48
|
+
break;
|
|
49
|
+
default:
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
event.preventDefault();
|
|
53
|
+
const nextTab = tabs[nextIndex];
|
|
54
|
+
if (!nextTab) return;
|
|
55
|
+
handleChange(nextTab.key);
|
|
56
|
+
tabRefs.current[nextIndex]?.focus();
|
|
57
|
+
};
|
|
58
|
+
const activeTab = tabs.find((t) => t.key === activeKey);
|
|
59
|
+
return /* @__PURE__ */ jsxs("div", { ref, className: cn("w-full", className), children: [
|
|
60
|
+
/* @__PURE__ */ jsx(
|
|
61
|
+
"div",
|
|
62
|
+
{
|
|
63
|
+
role: "tablist",
|
|
64
|
+
"aria-label": ariaLabelledBy ? void 0 : ariaLabel,
|
|
65
|
+
"aria-labelledby": ariaLabelledBy,
|
|
66
|
+
className: "flex border-b border-gray-200 gap-1 overflow-x-auto",
|
|
67
|
+
children: tabs.map((tab, index) => /* @__PURE__ */ jsxs(
|
|
68
|
+
"button",
|
|
69
|
+
{
|
|
70
|
+
id: tabId(tab.key),
|
|
71
|
+
role: "tab",
|
|
72
|
+
type: "button",
|
|
73
|
+
"aria-selected": tab.key === activeKey,
|
|
74
|
+
"aria-controls": panelId(tab.key),
|
|
75
|
+
tabIndex: tab.key === activeKey ? 0 : -1,
|
|
76
|
+
ref: (node) => {
|
|
77
|
+
tabRefs.current[index] = node;
|
|
78
|
+
},
|
|
79
|
+
onKeyDown: (event) => handleKeyDown(event, index),
|
|
80
|
+
onClick: () => handleChange(tab.key),
|
|
81
|
+
className: cn(
|
|
82
|
+
"flex items-center gap-2 px-4 py-3 text-sm font-medium whitespace-nowrap border-b-2 transition-colors -mb-px",
|
|
83
|
+
tab.key === activeKey ? "border-secondary text-secondary" : "border-transparent text-text-secondary hover:text-primary hover:border-gray-300"
|
|
84
|
+
),
|
|
85
|
+
children: [
|
|
86
|
+
tab.icon && /* @__PURE__ */ jsx("span", { className: "shrink-0", children: tab.icon }),
|
|
87
|
+
tab.label
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
tab.key
|
|
91
|
+
))
|
|
92
|
+
}
|
|
93
|
+
),
|
|
94
|
+
activeTab && /* @__PURE__ */ jsx(
|
|
95
|
+
"div",
|
|
96
|
+
{
|
|
97
|
+
role: "tabpanel",
|
|
98
|
+
id: panelId(activeTab.key),
|
|
99
|
+
"aria-labelledby": tabId(activeTab.key),
|
|
100
|
+
className: "py-4",
|
|
101
|
+
children: activeTab.content
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
] });
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
Tabs.displayName = "Tabs";
|
|
108
|
+
|
|
109
|
+
export { Tabs };
|