@dododog/ui 0.13.1 → 0.14.1
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 +3 -3
- package/dist/{chunk-SZ3SV4SJ.mjs → chunk-4DHZ6MIL.mjs} +1 -1
- package/dist/{chunk-77WKG2D7.js → chunk-5UNG2O64.js} +38 -8
- package/dist/{chunk-C4RZBOKH.js → chunk-EQPT6VZF.js} +1 -1
- package/dist/{chunk-NBDPT3XQ.js → chunk-HH4X2JYO.js} +94 -15
- package/dist/{chunk-GRG4USTC.mjs → chunk-JWOV5H6G.mjs} +38 -8
- package/dist/chunk-N5ZNXNCO.mjs +178 -0
- package/dist/components/Footer/index.js +2 -2
- package/dist/components/Footer/index.mjs +1 -1
- package/dist/components/Input/index.js +3 -3
- package/dist/components/Input/index.mjs +1 -1
- package/dist/components/Modal/index.d.mts +12 -0
- package/dist/components/Modal/index.d.ts +12 -0
- package/dist/components/Modal/index.js +2 -2
- package/dist/components/Modal/index.mjs +1 -1
- package/dist/index.d.mts +8 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.js +152 -126
- package/dist/index.mjs +54 -28
- package/dist/tailwind-preset.d.mts +2 -2
- package/dist/tailwind-preset.d.ts +2 -2
- package/dist/tailwind-preset.js +2 -2
- package/dist/tailwind-preset.mjs +2 -2
- package/package.json +14 -14
- package/dist/chunk-74B2EGKT.mjs +0 -99
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @dododog/ui
|
|
2
2
|
|
|
3
|
-
React UI component library for [
|
|
3
|
+
React UI component library for [ToutouTrip](https://toutoutrip.com) — the pet-friendly travel platform.
|
|
4
4
|
|
|
5
5
|
Built with React, TypeScript, Tailwind CSS, and [class-variance-authority](https://cva.style).
|
|
6
6
|
|
|
@@ -14,7 +14,7 @@ npm install @dododog/ui
|
|
|
14
14
|
|
|
15
15
|
### Tailwind CSS
|
|
16
16
|
|
|
17
|
-
Add the
|
|
17
|
+
Add the ToutouTrip preset and content path to your `tailwind.config.ts`:
|
|
18
18
|
|
|
19
19
|
```ts
|
|
20
20
|
import type { Config } from "tailwindcss";
|
|
@@ -113,7 +113,7 @@ export default function App() {
|
|
|
113
113
|
| Export | Import path | Description |
|
|
114
114
|
|--------|-------------|-------------|
|
|
115
115
|
| Tokens | `@dododog/ui/tokens` | Design tokens (colors, spacing, etc.) |
|
|
116
|
-
| Tailwind Preset | `@dododog/ui/tailwind-preset` | Tailwind CSS preset with
|
|
116
|
+
| Tailwind Preset | `@dododog/ui/tailwind-preset` | Tailwind CSS preset with ToutouTrip theme |
|
|
117
117
|
| Utils | `@dododog/ui/utils` | Utility functions (`cn`, etc.) |
|
|
118
118
|
|
|
119
119
|
## Requirements
|
|
@@ -106,7 +106,7 @@ var Footer = React.forwardRef(
|
|
|
106
106
|
bottomExtra
|
|
107
107
|
] }),
|
|
108
108
|
tagline && /* @__PURE__ */ jsx("div", { className: "text-sm text-white/60", children: tagline }),
|
|
109
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm text-white/60", children: copyright || `\xA9 ${year}
|
|
109
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-white/60", children: copyright || `\xA9 ${year} ToutouTrip. Tous droits r\xE9serv\xE9s.` })
|
|
110
110
|
] }) }) })
|
|
111
111
|
] });
|
|
112
112
|
}
|
|
@@ -46,31 +46,61 @@ var inputVariants = classVarianceAuthority.cva(
|
|
|
46
46
|
}
|
|
47
47
|
);
|
|
48
48
|
var Input = React__namespace.forwardRef(
|
|
49
|
-
({
|
|
49
|
+
({
|
|
50
|
+
className,
|
|
51
|
+
size,
|
|
52
|
+
variant,
|
|
53
|
+
label,
|
|
54
|
+
error,
|
|
55
|
+
required,
|
|
56
|
+
leftIcon,
|
|
57
|
+
rightIcon,
|
|
58
|
+
id,
|
|
59
|
+
"aria-describedby": ariaDescribedBy,
|
|
60
|
+
"aria-invalid": ariaInvalid,
|
|
61
|
+
"aria-required": ariaRequired,
|
|
62
|
+
...props
|
|
63
|
+
}, ref) => {
|
|
64
|
+
const generatedId = React__namespace.useId();
|
|
65
|
+
const inputId = id ?? generatedId;
|
|
66
|
+
const errorId = `${inputId}-error`;
|
|
50
67
|
const resolvedVariant = error ? "error" : variant ?? "default";
|
|
68
|
+
const describedBy = [ariaDescribedBy, error ? errorId : null].filter(Boolean).join(" ") || void 0;
|
|
51
69
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full", children: [
|
|
52
|
-
label && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
53
|
-
label,
|
|
54
|
-
|
|
55
|
-
|
|
70
|
+
label && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
71
|
+
"label",
|
|
72
|
+
{
|
|
73
|
+
htmlFor: inputId,
|
|
74
|
+
className: "block text-sm font-medium text-primary mb-1.5",
|
|
75
|
+
children: [
|
|
76
|
+
label,
|
|
77
|
+
required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-secondary", "aria-hidden": "true", children: " *" })
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
),
|
|
56
81
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
57
82
|
leftIcon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-text-muted", children: leftIcon }),
|
|
58
83
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
59
84
|
"input",
|
|
60
85
|
{
|
|
86
|
+
...props,
|
|
61
87
|
ref,
|
|
88
|
+
id: inputId,
|
|
89
|
+
required,
|
|
90
|
+
"aria-required": ariaRequired ?? (required ? true : void 0),
|
|
91
|
+
"aria-invalid": ariaInvalid ?? (error ? true : void 0),
|
|
92
|
+
"aria-describedby": describedBy,
|
|
62
93
|
className: chunkADIDI7AJ_js.cn(
|
|
63
94
|
inputVariants({ size, variant: resolvedVariant }),
|
|
64
95
|
leftIcon && "pl-10",
|
|
65
96
|
rightIcon && "pr-10",
|
|
66
97
|
className
|
|
67
|
-
)
|
|
68
|
-
...props
|
|
98
|
+
)
|
|
69
99
|
}
|
|
70
100
|
),
|
|
71
101
|
rightIcon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-3 top-1/2 -translate-y-1/2 text-text-muted", children: rightIcon })
|
|
72
102
|
] }),
|
|
73
|
-
error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-red-500", children: error })
|
|
103
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { id: errorId, className: "mt-1 text-sm text-red-500", children: error })
|
|
74
104
|
] });
|
|
75
105
|
}
|
|
76
106
|
);
|
|
@@ -128,7 +128,7 @@ var Footer = React__namespace.forwardRef(
|
|
|
128
128
|
bottomExtra
|
|
129
129
|
] }),
|
|
130
130
|
tagline && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-white/60", children: tagline }),
|
|
131
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-white/60", children: copyright || `\xA9 ${year}
|
|
131
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-white/60", children: copyright || `\xA9 ${year} ToutouTrip. Tous droits r\xE9serv\xE9s.` })
|
|
132
132
|
] }) }) })
|
|
133
133
|
] });
|
|
134
134
|
}
|
|
@@ -24,13 +24,89 @@ function _interopNamespace(e) {
|
|
|
24
24
|
|
|
25
25
|
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
26
26
|
|
|
27
|
+
var FOCUSABLE_SELECTOR = [
|
|
28
|
+
"a[href]",
|
|
29
|
+
"area[href]",
|
|
30
|
+
"button:not([disabled])",
|
|
31
|
+
"input:not([disabled]):not([type='hidden'])",
|
|
32
|
+
"select:not([disabled])",
|
|
33
|
+
"textarea:not([disabled])",
|
|
34
|
+
"iframe",
|
|
35
|
+
"audio[controls]",
|
|
36
|
+
"video[controls]",
|
|
37
|
+
"[contenteditable]:not([contenteditable='false'])",
|
|
38
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
39
|
+
].join(", ");
|
|
40
|
+
function getFocusableElements(panel) {
|
|
41
|
+
return Array.from(panel.querySelectorAll(FOCUSABLE_SELECTOR)).filter(
|
|
42
|
+
(element) => !element.closest("[hidden], [aria-hidden='true'], details:not([open])")
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
function isTopMostDialog(panel) {
|
|
46
|
+
const dialogs = document.querySelectorAll('[role="dialog"][aria-modal="true"]');
|
|
47
|
+
const topMost = dialogs[dialogs.length - 1];
|
|
48
|
+
return !topMost || topMost.contains(panel);
|
|
49
|
+
}
|
|
27
50
|
var Modal = React__namespace.forwardRef(
|
|
28
|
-
({
|
|
29
|
-
|
|
51
|
+
({
|
|
52
|
+
isOpen,
|
|
53
|
+
onClose,
|
|
54
|
+
children,
|
|
55
|
+
title,
|
|
56
|
+
className,
|
|
57
|
+
"aria-label": ariaLabel,
|
|
58
|
+
"aria-labelledby": ariaLabelledBy,
|
|
59
|
+
"aria-describedby": ariaDescribedBy
|
|
60
|
+
}, ref) => {
|
|
61
|
+
const panelRef = React__namespace.useRef(null);
|
|
62
|
+
const generatedTitleId = React__namespace.useId();
|
|
63
|
+
const titleId = title ? `${generatedTitleId}-title` : void 0;
|
|
64
|
+
const labelledBy = ariaLabelledBy ?? titleId;
|
|
30
65
|
React__namespace.useEffect(() => {
|
|
31
66
|
if (!isOpen) return;
|
|
32
|
-
const
|
|
33
|
-
|
|
67
|
+
const trigger = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
68
|
+
return () => {
|
|
69
|
+
if (trigger && trigger.isConnected) trigger.focus();
|
|
70
|
+
};
|
|
71
|
+
}, [isOpen]);
|
|
72
|
+
React__namespace.useEffect(() => {
|
|
73
|
+
if (!isOpen) return;
|
|
74
|
+
const panel = panelRef.current;
|
|
75
|
+
if (!panel) return;
|
|
76
|
+
const focusable = getFocusableElements(panel);
|
|
77
|
+
(focusable[0] ?? panel).focus();
|
|
78
|
+
}, [isOpen]);
|
|
79
|
+
React__namespace.useEffect(() => {
|
|
80
|
+
if (!isOpen) return;
|
|
81
|
+
const handleKeyDown = (event) => {
|
|
82
|
+
const panel = panelRef.current;
|
|
83
|
+
if (!panel || !isTopMostDialog(panel)) return;
|
|
84
|
+
if (event.key === "Escape") {
|
|
85
|
+
onClose();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (event.key !== "Tab") return;
|
|
89
|
+
const focusable = getFocusableElements(panel);
|
|
90
|
+
const active = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
91
|
+
if (focusable.length === 0) {
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
panel.focus();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const first = focusable[0];
|
|
97
|
+
const last = focusable[focusable.length - 1];
|
|
98
|
+
if (!active || !panel.contains(active)) {
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
(event.shiftKey ? last : first).focus();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (event.shiftKey && active === first) {
|
|
104
|
+
event.preventDefault();
|
|
105
|
+
last.focus();
|
|
106
|
+
} else if (!event.shiftKey && active === last) {
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
first.focus();
|
|
109
|
+
}
|
|
34
110
|
};
|
|
35
111
|
document.addEventListener("keydown", handleKeyDown);
|
|
36
112
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
@@ -43,13 +119,6 @@ var Modal = React__namespace.forwardRef(
|
|
|
43
119
|
document.body.style.overflow = prev;
|
|
44
120
|
};
|
|
45
121
|
}, [isOpen]);
|
|
46
|
-
React__namespace.useEffect(() => {
|
|
47
|
-
if (!isOpen || !contentRef.current) return;
|
|
48
|
-
const focusable = contentRef.current.querySelectorAll(
|
|
49
|
-
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
50
|
-
);
|
|
51
|
-
if (focusable.length > 0) focusable[0].focus();
|
|
52
|
-
}, [isOpen]);
|
|
53
122
|
if (!isOpen) return null;
|
|
54
123
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
55
124
|
"div",
|
|
@@ -58,7 +127,9 @@ var Modal = React__namespace.forwardRef(
|
|
|
58
127
|
className: "fixed inset-0 z-50 flex items-center justify-center p-4",
|
|
59
128
|
role: "dialog",
|
|
60
129
|
"aria-modal": "true",
|
|
61
|
-
"aria-
|
|
130
|
+
"aria-labelledby": labelledBy,
|
|
131
|
+
"aria-label": labelledBy ? void 0 : ariaLabel,
|
|
132
|
+
"aria-describedby": ariaDescribedBy,
|
|
62
133
|
children: [
|
|
63
134
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
64
135
|
"div",
|
|
@@ -71,14 +142,22 @@ var Modal = React__namespace.forwardRef(
|
|
|
71
142
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
72
143
|
"div",
|
|
73
144
|
{
|
|
74
|
-
ref:
|
|
145
|
+
ref: panelRef,
|
|
146
|
+
tabIndex: -1,
|
|
75
147
|
className: chunkADIDI7AJ_js.cn(
|
|
76
|
-
"relative bg-white rounded-2xl shadow-xl max-w-md w-full max-h-[90vh] overflow-auto",
|
|
148
|
+
"relative bg-white rounded-2xl shadow-xl max-w-md w-full max-h-[90vh] overflow-auto outline-none",
|
|
77
149
|
className
|
|
78
150
|
),
|
|
79
151
|
children: [
|
|
80
152
|
title && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 pt-6 pb-2", children: [
|
|
81
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
153
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
154
|
+
"h2",
|
|
155
|
+
{
|
|
156
|
+
id: titleId,
|
|
157
|
+
className: "text-lg font-heading font-semibold text-primary",
|
|
158
|
+
children: title
|
|
159
|
+
}
|
|
160
|
+
),
|
|
82
161
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
83
162
|
"button",
|
|
84
163
|
{
|
|
@@ -24,31 +24,61 @@ var inputVariants = cva(
|
|
|
24
24
|
}
|
|
25
25
|
);
|
|
26
26
|
var Input = React.forwardRef(
|
|
27
|
-
({
|
|
27
|
+
({
|
|
28
|
+
className,
|
|
29
|
+
size,
|
|
30
|
+
variant,
|
|
31
|
+
label,
|
|
32
|
+
error,
|
|
33
|
+
required,
|
|
34
|
+
leftIcon,
|
|
35
|
+
rightIcon,
|
|
36
|
+
id,
|
|
37
|
+
"aria-describedby": ariaDescribedBy,
|
|
38
|
+
"aria-invalid": ariaInvalid,
|
|
39
|
+
"aria-required": ariaRequired,
|
|
40
|
+
...props
|
|
41
|
+
}, ref) => {
|
|
42
|
+
const generatedId = React.useId();
|
|
43
|
+
const inputId = id ?? generatedId;
|
|
44
|
+
const errorId = `${inputId}-error`;
|
|
28
45
|
const resolvedVariant = error ? "error" : variant ?? "default";
|
|
46
|
+
const describedBy = [ariaDescribedBy, error ? errorId : null].filter(Boolean).join(" ") || void 0;
|
|
29
47
|
return /* @__PURE__ */ jsxs("div", { className: "w-full", children: [
|
|
30
|
-
label && /* @__PURE__ */ jsxs(
|
|
31
|
-
label,
|
|
32
|
-
|
|
33
|
-
|
|
48
|
+
label && /* @__PURE__ */ jsxs(
|
|
49
|
+
"label",
|
|
50
|
+
{
|
|
51
|
+
htmlFor: inputId,
|
|
52
|
+
className: "block text-sm font-medium text-primary mb-1.5",
|
|
53
|
+
children: [
|
|
54
|
+
label,
|
|
55
|
+
required && /* @__PURE__ */ jsx("span", { className: "text-secondary", "aria-hidden": "true", children: " *" })
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
),
|
|
34
59
|
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
35
60
|
leftIcon && /* @__PURE__ */ jsx("div", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-text-muted", children: leftIcon }),
|
|
36
61
|
/* @__PURE__ */ jsx(
|
|
37
62
|
"input",
|
|
38
63
|
{
|
|
64
|
+
...props,
|
|
39
65
|
ref,
|
|
66
|
+
id: inputId,
|
|
67
|
+
required,
|
|
68
|
+
"aria-required": ariaRequired ?? (required ? true : void 0),
|
|
69
|
+
"aria-invalid": ariaInvalid ?? (error ? true : void 0),
|
|
70
|
+
"aria-describedby": describedBy,
|
|
40
71
|
className: cn(
|
|
41
72
|
inputVariants({ size, variant: resolvedVariant }),
|
|
42
73
|
leftIcon && "pl-10",
|
|
43
74
|
rightIcon && "pr-10",
|
|
44
75
|
className
|
|
45
|
-
)
|
|
46
|
-
...props
|
|
76
|
+
)
|
|
47
77
|
}
|
|
48
78
|
),
|
|
49
79
|
rightIcon && /* @__PURE__ */ jsx("div", { className: "absolute right-3 top-1/2 -translate-y-1/2 text-text-muted", children: rightIcon })
|
|
50
80
|
] }),
|
|
51
|
-
error && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-red-500", children: error })
|
|
81
|
+
error && /* @__PURE__ */ jsx("p", { id: errorId, className: "mt-1 text-sm text-red-500", children: error })
|
|
52
82
|
] });
|
|
53
83
|
}
|
|
54
84
|
);
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { cn } from './chunk-IMKLN273.mjs';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
var FOCUSABLE_SELECTOR = [
|
|
6
|
+
"a[href]",
|
|
7
|
+
"area[href]",
|
|
8
|
+
"button:not([disabled])",
|
|
9
|
+
"input:not([disabled]):not([type='hidden'])",
|
|
10
|
+
"select:not([disabled])",
|
|
11
|
+
"textarea:not([disabled])",
|
|
12
|
+
"iframe",
|
|
13
|
+
"audio[controls]",
|
|
14
|
+
"video[controls]",
|
|
15
|
+
"[contenteditable]:not([contenteditable='false'])",
|
|
16
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
17
|
+
].join(", ");
|
|
18
|
+
function getFocusableElements(panel) {
|
|
19
|
+
return Array.from(panel.querySelectorAll(FOCUSABLE_SELECTOR)).filter(
|
|
20
|
+
(element) => !element.closest("[hidden], [aria-hidden='true'], details:not([open])")
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
function isTopMostDialog(panel) {
|
|
24
|
+
const dialogs = document.querySelectorAll('[role="dialog"][aria-modal="true"]');
|
|
25
|
+
const topMost = dialogs[dialogs.length - 1];
|
|
26
|
+
return !topMost || topMost.contains(panel);
|
|
27
|
+
}
|
|
28
|
+
var Modal = React.forwardRef(
|
|
29
|
+
({
|
|
30
|
+
isOpen,
|
|
31
|
+
onClose,
|
|
32
|
+
children,
|
|
33
|
+
title,
|
|
34
|
+
className,
|
|
35
|
+
"aria-label": ariaLabel,
|
|
36
|
+
"aria-labelledby": ariaLabelledBy,
|
|
37
|
+
"aria-describedby": ariaDescribedBy
|
|
38
|
+
}, ref) => {
|
|
39
|
+
const panelRef = React.useRef(null);
|
|
40
|
+
const generatedTitleId = React.useId();
|
|
41
|
+
const titleId = title ? `${generatedTitleId}-title` : void 0;
|
|
42
|
+
const labelledBy = ariaLabelledBy ?? titleId;
|
|
43
|
+
React.useEffect(() => {
|
|
44
|
+
if (!isOpen) return;
|
|
45
|
+
const trigger = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
46
|
+
return () => {
|
|
47
|
+
if (trigger && trigger.isConnected) trigger.focus();
|
|
48
|
+
};
|
|
49
|
+
}, [isOpen]);
|
|
50
|
+
React.useEffect(() => {
|
|
51
|
+
if (!isOpen) return;
|
|
52
|
+
const panel = panelRef.current;
|
|
53
|
+
if (!panel) return;
|
|
54
|
+
const focusable = getFocusableElements(panel);
|
|
55
|
+
(focusable[0] ?? panel).focus();
|
|
56
|
+
}, [isOpen]);
|
|
57
|
+
React.useEffect(() => {
|
|
58
|
+
if (!isOpen) return;
|
|
59
|
+
const handleKeyDown = (event) => {
|
|
60
|
+
const panel = panelRef.current;
|
|
61
|
+
if (!panel || !isTopMostDialog(panel)) return;
|
|
62
|
+
if (event.key === "Escape") {
|
|
63
|
+
onClose();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (event.key !== "Tab") return;
|
|
67
|
+
const focusable = getFocusableElements(panel);
|
|
68
|
+
const active = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
69
|
+
if (focusable.length === 0) {
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
panel.focus();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const first = focusable[0];
|
|
75
|
+
const last = focusable[focusable.length - 1];
|
|
76
|
+
if (!active || !panel.contains(active)) {
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
(event.shiftKey ? last : first).focus();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (event.shiftKey && active === first) {
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
last.focus();
|
|
84
|
+
} else if (!event.shiftKey && active === last) {
|
|
85
|
+
event.preventDefault();
|
|
86
|
+
first.focus();
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
90
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
91
|
+
}, [isOpen, onClose]);
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
if (!isOpen) return;
|
|
94
|
+
const prev = document.body.style.overflow;
|
|
95
|
+
document.body.style.overflow = "hidden";
|
|
96
|
+
return () => {
|
|
97
|
+
document.body.style.overflow = prev;
|
|
98
|
+
};
|
|
99
|
+
}, [isOpen]);
|
|
100
|
+
if (!isOpen) return null;
|
|
101
|
+
return /* @__PURE__ */ jsxs(
|
|
102
|
+
"div",
|
|
103
|
+
{
|
|
104
|
+
ref,
|
|
105
|
+
className: "fixed inset-0 z-50 flex items-center justify-center p-4",
|
|
106
|
+
role: "dialog",
|
|
107
|
+
"aria-modal": "true",
|
|
108
|
+
"aria-labelledby": labelledBy,
|
|
109
|
+
"aria-label": labelledBy ? void 0 : ariaLabel,
|
|
110
|
+
"aria-describedby": ariaDescribedBy,
|
|
111
|
+
children: [
|
|
112
|
+
/* @__PURE__ */ jsx(
|
|
113
|
+
"div",
|
|
114
|
+
{
|
|
115
|
+
className: "absolute inset-0 bg-black/50",
|
|
116
|
+
onClick: onClose,
|
|
117
|
+
"aria-hidden": "true"
|
|
118
|
+
}
|
|
119
|
+
),
|
|
120
|
+
/* @__PURE__ */ jsxs(
|
|
121
|
+
"div",
|
|
122
|
+
{
|
|
123
|
+
ref: panelRef,
|
|
124
|
+
tabIndex: -1,
|
|
125
|
+
className: cn(
|
|
126
|
+
"relative bg-white rounded-2xl shadow-xl max-w-md w-full max-h-[90vh] overflow-auto outline-none",
|
|
127
|
+
className
|
|
128
|
+
),
|
|
129
|
+
children: [
|
|
130
|
+
title && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 pt-6 pb-2", children: [
|
|
131
|
+
/* @__PURE__ */ jsx(
|
|
132
|
+
"h2",
|
|
133
|
+
{
|
|
134
|
+
id: titleId,
|
|
135
|
+
className: "text-lg font-heading font-semibold text-primary",
|
|
136
|
+
children: title
|
|
137
|
+
}
|
|
138
|
+
),
|
|
139
|
+
/* @__PURE__ */ jsx(
|
|
140
|
+
"button",
|
|
141
|
+
{
|
|
142
|
+
type: "button",
|
|
143
|
+
onClick: onClose,
|
|
144
|
+
className: "text-text-muted hover:text-primary transition-colors",
|
|
145
|
+
"aria-label": "Fermer",
|
|
146
|
+
children: /* @__PURE__ */ jsxs(
|
|
147
|
+
"svg",
|
|
148
|
+
{
|
|
149
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
150
|
+
width: "20",
|
|
151
|
+
height: "20",
|
|
152
|
+
viewBox: "0 0 24 24",
|
|
153
|
+
fill: "none",
|
|
154
|
+
stroke: "currentColor",
|
|
155
|
+
strokeWidth: "2",
|
|
156
|
+
strokeLinecap: "round",
|
|
157
|
+
strokeLinejoin: "round",
|
|
158
|
+
children: [
|
|
159
|
+
/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
|
|
160
|
+
/* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
] }),
|
|
167
|
+
/* @__PURE__ */ jsx("div", { className: cn(title ? "px-6 pb-6" : "p-6"), children })
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
Modal.displayName = "Modal";
|
|
177
|
+
|
|
178
|
+
export { Modal };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkEQPT6VZF_js = require('../../chunk-EQPT6VZF.js');
|
|
4
4
|
require('../../chunk-ADIDI7AJ.js');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
Object.defineProperty(exports, "Footer", {
|
|
9
9
|
enumerable: true,
|
|
10
|
-
get: function () { return
|
|
10
|
+
get: function () { return chunkEQPT6VZF_js.Footer; }
|
|
11
11
|
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { Footer } from '../../chunk-
|
|
1
|
+
export { Footer } from '../../chunk-4DHZ6MIL.mjs';
|
|
2
2
|
import '../../chunk-IMKLN273.mjs';
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunk5UNG2O64_js = require('../../chunk-5UNG2O64.js');
|
|
4
4
|
require('../../chunk-ADIDI7AJ.js');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
Object.defineProperty(exports, "Input", {
|
|
9
9
|
enumerable: true,
|
|
10
|
-
get: function () { return
|
|
10
|
+
get: function () { return chunk5UNG2O64_js.Input; }
|
|
11
11
|
});
|
|
12
12
|
Object.defineProperty(exports, "inputVariants", {
|
|
13
13
|
enumerable: true,
|
|
14
|
-
get: function () { return
|
|
14
|
+
get: function () { return chunk5UNG2O64_js.inputVariants; }
|
|
15
15
|
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { Input, inputVariants } from '../../chunk-
|
|
1
|
+
export { Input, inputVariants } from '../../chunk-JWOV5H6G.mjs';
|
|
2
2
|
import '../../chunk-IMKLN273.mjs';
|
|
@@ -6,6 +6,18 @@ interface ModalProps {
|
|
|
6
6
|
children: React.ReactNode;
|
|
7
7
|
title?: string;
|
|
8
8
|
className?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Nom accessible quand aucun `title` n'est rendu. Ignore si `title` est
|
|
11
|
+
* fourni : le titre visible sert alors de nom via `aria-labelledby`.
|
|
12
|
+
*/
|
|
13
|
+
"aria-label"?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Id d'un titre rendu par l'appelant (dialogue sans `title`). Prioritaire
|
|
16
|
+
* sur l'id genere en interne.
|
|
17
|
+
*/
|
|
18
|
+
"aria-labelledby"?: string;
|
|
19
|
+
/** Id d'une description du dialogue, relayee telle quelle. */
|
|
20
|
+
"aria-describedby"?: string;
|
|
9
21
|
}
|
|
10
22
|
declare const Modal: React.ForwardRefExoticComponent<ModalProps & React.RefAttributes<HTMLDivElement>>;
|
|
11
23
|
|
|
@@ -6,6 +6,18 @@ interface ModalProps {
|
|
|
6
6
|
children: React.ReactNode;
|
|
7
7
|
title?: string;
|
|
8
8
|
className?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Nom accessible quand aucun `title` n'est rendu. Ignore si `title` est
|
|
11
|
+
* fourni : le titre visible sert alors de nom via `aria-labelledby`.
|
|
12
|
+
*/
|
|
13
|
+
"aria-label"?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Id d'un titre rendu par l'appelant (dialogue sans `title`). Prioritaire
|
|
16
|
+
* sur l'id genere en interne.
|
|
17
|
+
*/
|
|
18
|
+
"aria-labelledby"?: string;
|
|
19
|
+
/** Id d'une description du dialogue, relayee telle quelle. */
|
|
20
|
+
"aria-describedby"?: string;
|
|
9
21
|
}
|
|
10
22
|
declare const Modal: React.ForwardRefExoticComponent<ModalProps & React.RefAttributes<HTMLDivElement>>;
|
|
11
23
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkHH4X2JYO_js = require('../../chunk-HH4X2JYO.js');
|
|
4
4
|
require('../../chunk-ADIDI7AJ.js');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
Object.defineProperty(exports, "Modal", {
|
|
9
9
|
enumerable: true,
|
|
10
|
-
get: function () { return
|
|
10
|
+
get: function () { return chunkHH4X2JYO_js.Modal; }
|
|
11
11
|
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { Modal } from '../../chunk-
|
|
1
|
+
export { Modal } from '../../chunk-N5ZNXNCO.mjs';
|
|
2
2
|
import '../../chunk-IMKLN273.mjs';
|
package/dist/index.d.mts
CHANGED
|
@@ -164,8 +164,14 @@ interface ReviewSummaryProps extends React.HTMLAttributes<HTMLElement> {
|
|
|
164
164
|
sources?: string[];
|
|
165
165
|
/** Sous-notes détaillées (/10) */
|
|
166
166
|
subScores?: ReviewSubScore[];
|
|
167
|
-
/** Avis mis en avant */
|
|
167
|
+
/** Avis mis en avant (liste COMPLÈTE : la pagination « Voir plus » est gérée en interne) */
|
|
168
168
|
reviews?: ReviewItem[];
|
|
169
|
+
/** Nombre d'avis affichés initialement (défaut : 8). Le reste est déplié via « Voir plus d'avis ». */
|
|
170
|
+
initialVisibleReviews?: number;
|
|
171
|
+
/** Pas de déploiement à chaque clic sur « Voir plus d'avis » (défaut : 8). */
|
|
172
|
+
reviewsStep?: number;
|
|
173
|
+
/** Libellé du bouton de déploiement progressif (défaut : « Voir plus d'avis »). */
|
|
174
|
+
moreReviewsLabel?: string;
|
|
169
175
|
/** Callback bouton "Afficher tous les avis" */
|
|
170
176
|
onSeeAll?: () => void;
|
|
171
177
|
/** Libellé du bouton (par défaut dérivé du count) */
|
|
@@ -177,7 +183,7 @@ interface ReviewSummaryProps extends React.HTMLAttributes<HTMLElement> {
|
|
|
177
183
|
* Bloc avis consolidé de la fiche hébergement (échelle /10 unique).
|
|
178
184
|
* Remplace les blocs avis redondants. N'affiche que des sources réelles.
|
|
179
185
|
*/
|
|
180
|
-
declare function ReviewSummary({ score, scoreLabel, count, title, countLabel, sources, subScores, reviews, onSeeAll, seeAllLabel, emptyLabel, className, ...props }: ReviewSummaryProps): react_jsx_runtime.JSX.Element;
|
|
186
|
+
declare function ReviewSummary({ score, scoreLabel, count, title, countLabel, sources, subScores, reviews, initialVisibleReviews, reviewsStep, moreReviewsLabel, onSeeAll, seeAllLabel, emptyLabel, className, ...props }: ReviewSummaryProps): react_jsx_runtime.JSX.Element;
|
|
181
187
|
|
|
182
188
|
interface MobileBookingBarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onSelect"> {
|
|
183
189
|
/** Prix total déjà formaté, ex: "248 €" */
|
package/dist/index.d.ts
CHANGED
|
@@ -164,8 +164,14 @@ interface ReviewSummaryProps extends React.HTMLAttributes<HTMLElement> {
|
|
|
164
164
|
sources?: string[];
|
|
165
165
|
/** Sous-notes détaillées (/10) */
|
|
166
166
|
subScores?: ReviewSubScore[];
|
|
167
|
-
/** Avis mis en avant */
|
|
167
|
+
/** Avis mis en avant (liste COMPLÈTE : la pagination « Voir plus » est gérée en interne) */
|
|
168
168
|
reviews?: ReviewItem[];
|
|
169
|
+
/** Nombre d'avis affichés initialement (défaut : 8). Le reste est déplié via « Voir plus d'avis ». */
|
|
170
|
+
initialVisibleReviews?: number;
|
|
171
|
+
/** Pas de déploiement à chaque clic sur « Voir plus d'avis » (défaut : 8). */
|
|
172
|
+
reviewsStep?: number;
|
|
173
|
+
/** Libellé du bouton de déploiement progressif (défaut : « Voir plus d'avis »). */
|
|
174
|
+
moreReviewsLabel?: string;
|
|
169
175
|
/** Callback bouton "Afficher tous les avis" */
|
|
170
176
|
onSeeAll?: () => void;
|
|
171
177
|
/** Libellé du bouton (par défaut dérivé du count) */
|
|
@@ -177,7 +183,7 @@ interface ReviewSummaryProps extends React.HTMLAttributes<HTMLElement> {
|
|
|
177
183
|
* Bloc avis consolidé de la fiche hébergement (échelle /10 unique).
|
|
178
184
|
* Remplace les blocs avis redondants. N'affiche que des sources réelles.
|
|
179
185
|
*/
|
|
180
|
-
declare function ReviewSummary({ score, scoreLabel, count, title, countLabel, sources, subScores, reviews, onSeeAll, seeAllLabel, emptyLabel, className, ...props }: ReviewSummaryProps): react_jsx_runtime.JSX.Element;
|
|
186
|
+
declare function ReviewSummary({ score, scoreLabel, count, title, countLabel, sources, subScores, reviews, initialVisibleReviews, reviewsStep, moreReviewsLabel, onSeeAll, seeAllLabel, emptyLabel, className, ...props }: ReviewSummaryProps): react_jsx_runtime.JSX.Element;
|
|
181
187
|
|
|
182
188
|
interface MobileBookingBarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onSelect"> {
|
|
183
189
|
/** Prix total déjà formaté, ex: "248 €" */
|