@dododog/ui 0.14.1 → 0.16.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 (73) hide show
  1. package/dist/chunk-5KOWMIWK.mjs +110 -0
  2. package/dist/{chunk-MCF3EQTV.js → chunk-73BHQJW6.js} +13 -1
  3. package/dist/{chunk-U3XWNFHH.js → chunk-A3M3XQ35.js} +96 -29
  4. package/dist/chunk-CPBDDGRX.mjs +572 -0
  5. package/dist/chunk-DJTFRMRY.js +595 -0
  6. package/dist/{chunk-NHB2TI2B.js → chunk-FHAWYXPX.js} +1 -1
  7. package/dist/{chunk-5UNG2O64.js → chunk-FR46MMNP.js} +7 -1
  8. package/dist/{chunk-I4S3R6C6.js → chunk-JUB3NGJR.js} +51 -6
  9. package/dist/chunk-JWQ2OUEG.js +132 -0
  10. package/dist/{chunk-EEIPCJQ2.mjs → chunk-KWQKC5TG.mjs} +1 -1
  11. package/dist/{chunk-IJIUUBFT.js → chunk-OUN5BU4Z.js} +1 -1
  12. package/dist/{chunk-DMCMWOBJ.mjs → chunk-PBXH4I5N.mjs} +51 -6
  13. package/dist/{chunk-ERL3WXNY.mjs → chunk-PZZWQ6IG.mjs} +5 -5
  14. package/dist/{chunk-L7OYR6U3.js → chunk-R4G5LFPN.js} +1 -1
  15. package/dist/{chunk-F2BUMJYW.mjs → chunk-RBVTV3LA.mjs} +1 -1
  16. package/dist/{chunk-B47HQHX3.mjs → chunk-RGZHSI2G.mjs} +1 -1
  17. package/dist/{chunk-GXKON34B.js → chunk-RTI25IBR.js} +1 -1
  18. package/dist/{chunk-GBWVIY3C.mjs → chunk-UJEIFKSK.mjs} +96 -29
  19. package/dist/{chunk-3R7PT3JG.js → chunk-USJDHMKS.js} +40 -6
  20. package/dist/{chunk-333YD5QI.mjs → chunk-WNBZXXFA.mjs} +40 -6
  21. package/dist/chunk-WY24TZ4R.mjs +94 -0
  22. package/dist/{chunk-JWOV5H6G.mjs → chunk-X53C3IQC.mjs} +7 -1
  23. package/dist/chunk-XBNIHUSG.js +116 -0
  24. package/dist/{chunk-TDGU5Y2P.js → chunk-XOFTU3T7.js} +5 -5
  25. package/dist/{chunk-NZ7GF6RF.mjs → chunk-YOMOULLR.mjs} +13 -1
  26. package/dist/{chunk-ZZ4EWC53.mjs → chunk-Z7CF7EQJ.mjs} +1 -1
  27. package/dist/components/Accordion/index.js +2 -2
  28. package/dist/components/Accordion/index.mjs +1 -1
  29. package/dist/components/Autocomplete/index.js +3 -3
  30. package/dist/components/Autocomplete/index.mjs +1 -1
  31. package/dist/components/Checkbox/index.js +2 -2
  32. package/dist/components/Checkbox/index.mjs +1 -1
  33. package/dist/components/Counter/index.d.mts +12 -0
  34. package/dist/components/Counter/index.d.ts +12 -0
  35. package/dist/components/Counter/index.js +2 -2
  36. package/dist/components/Counter/index.mjs +1 -1
  37. package/dist/components/DateRangePicker/index.d.mts +7 -1
  38. package/dist/components/DateRangePicker/index.d.ts +7 -1
  39. package/dist/components/DateRangePicker/index.js +3 -3
  40. package/dist/components/DateRangePicker/index.mjs +1 -1
  41. package/dist/components/Drawer/index.d.mts +21 -1
  42. package/dist/components/Drawer/index.d.ts +21 -1
  43. package/dist/components/Drawer/index.js +2 -2
  44. package/dist/components/Drawer/index.mjs +1 -1
  45. package/dist/components/DualRangeSlider/index.js +2 -2
  46. package/dist/components/DualRangeSlider/index.mjs +1 -1
  47. package/dist/components/Input/index.js +3 -3
  48. package/dist/components/Input/index.mjs +1 -1
  49. package/dist/components/Lightbox/index.d.mts +2 -0
  50. package/dist/components/Lightbox/index.d.ts +2 -0
  51. package/dist/components/Lightbox/index.js +2 -2
  52. package/dist/components/Lightbox/index.mjs +1 -1
  53. package/dist/components/RadioGroup/index.js +2 -2
  54. package/dist/components/RadioGroup/index.mjs +1 -1
  55. package/dist/components/Select/index.d.mts +8 -0
  56. package/dist/components/Select/index.d.ts +8 -0
  57. package/dist/components/Select/index.js +3 -3
  58. package/dist/components/Select/index.mjs +1 -1
  59. package/dist/components/Switch/index.d.mts +41 -1
  60. package/dist/components/Switch/index.d.ts +41 -1
  61. package/dist/components/Switch/index.js +4 -4
  62. package/dist/components/Switch/index.mjs +1 -1
  63. package/dist/components/Textarea/index.js +3 -3
  64. package/dist/components/Textarea/index.mjs +1 -1
  65. package/dist/index.js +81 -81
  66. package/dist/index.mjs +20 -20
  67. package/package.json +1 -1
  68. package/dist/chunk-AIA3NHCK.js +0 -373
  69. package/dist/chunk-FL3GD5FJ.mjs +0 -350
  70. package/dist/chunk-LRNSVRUN.mjs +0 -55
  71. package/dist/chunk-T4AT3YCT.js +0 -77
  72. package/dist/chunk-VWFY24XF.js +0 -89
  73. package/dist/chunk-ZLF7IL3Y.mjs +0 -67
@@ -54,17 +54,47 @@ var switchThumbVariants = classVarianceAuthority.cva(
54
54
  }
55
55
  );
56
56
  var Switch = React__namespace.forwardRef(
57
- ({ checked = false, onCheckedChange, disabled = false, size, label, description, className, name }, ref) => {
58
- const id = React__namespace.useId();
57
+ ({
58
+ checked = false,
59
+ onCheckedChange,
60
+ disabled = false,
61
+ size,
62
+ label,
63
+ description,
64
+ className,
65
+ name,
66
+ id,
67
+ "aria-label": ariaLabel,
68
+ "aria-labelledby": ariaLabelledBy,
69
+ "aria-describedby": ariaDescribedBy
70
+ }, ref) => {
71
+ const generatedId = React__namespace.useId();
72
+ const switchId = id ?? generatedId;
73
+ const labelId = `${switchId}-label`;
74
+ const descriptionId = `${switchId}-description`;
75
+ const labelledBy = [label ? labelId : null, ariaLabelledBy].filter(Boolean).join(" ") || void 0;
76
+ const resolvedAriaLabel = labelledBy ? void 0 : ariaLabel ?? name;
77
+ const describedBy = [description ? descriptionId : null, ariaDescribedBy].filter(Boolean).join(" ") || void 0;
78
+ const hasAccessibleName = Boolean(labelledBy || resolvedAriaLabel);
79
+ React__namespace.useEffect(() => {
80
+ if (hasAccessibleName) return;
81
+ if (typeof process === "undefined") return;
82
+ if (process.env.NODE_ENV === "production") return;
83
+ console.warn(
84
+ "[@dododog/ui] Switch sans nom accessible : passer `label`, `aria-labelledby` ou `aria-label`."
85
+ );
86
+ }, [hasAccessibleName]);
59
87
  const track = /* @__PURE__ */ jsxRuntime.jsxs(
60
88
  "button",
61
89
  {
62
90
  ref,
63
- id,
91
+ id: switchId,
64
92
  type: "button",
65
93
  role: "switch",
66
94
  "aria-checked": checked,
67
- "aria-label": !label ? name : void 0,
95
+ "aria-labelledby": labelledBy,
96
+ "aria-label": resolvedAriaLabel,
97
+ "aria-describedby": describedBy,
68
98
  disabled,
69
99
  onClick: () => onCheckedChange?.(!checked),
70
100
  className: chunkADIDI7AJ_js.cn(
@@ -91,8 +121,23 @@ var Switch = React__namespace.forwardRef(
91
121
  if (!label && !description) return track;
92
122
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: chunkADIDI7AJ_js.cn("flex items-center justify-between gap-3", className), children: [
93
123
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
94
- label && /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: id, className: "text-sm font-medium text-primary cursor-pointer", children: label }),
95
- description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-text-secondary mt-0.5", children: description })
124
+ label && /* @__PURE__ */ jsxRuntime.jsx(
125
+ "label",
126
+ {
127
+ id: labelId,
128
+ htmlFor: switchId,
129
+ className: "text-sm font-medium text-primary cursor-pointer",
130
+ children: label
131
+ }
132
+ ),
133
+ description && /* @__PURE__ */ jsxRuntime.jsx(
134
+ "p",
135
+ {
136
+ id: descriptionId,
137
+ className: "text-xs text-text-secondary mt-0.5",
138
+ children: description
139
+ }
140
+ )
96
141
  ] }),
97
142
  track
98
143
  ] });
@@ -0,0 +1,132 @@
1
+ 'use strict';
2
+
3
+ var chunkADIDI7AJ_js = require('./chunk-ADIDI7AJ.js');
4
+ var React = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ function _interopNamespace(e) {
8
+ if (e && e.__esModule) return e;
9
+ var n = Object.create(null);
10
+ if (e) {
11
+ Object.keys(e).forEach(function (k) {
12
+ if (k !== 'default') {
13
+ var d = Object.getOwnPropertyDescriptor(e, k);
14
+ Object.defineProperty(n, k, d.get ? d : {
15
+ enumerable: true,
16
+ get: function () { return e[k]; }
17
+ });
18
+ }
19
+ });
20
+ }
21
+ n.default = e;
22
+ return Object.freeze(n);
23
+ }
24
+
25
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
26
+
27
+ var SIDE_CLASSES = {
28
+ right: "top-0 bottom-0 right-0 w-full max-w-md",
29
+ left: "top-0 bottom-0 left-0 w-full max-w-md",
30
+ bottom: "inset-x-0 bottom-0 w-full max-h-[90dvh] rounded-t-3xl"
31
+ };
32
+ var Drawer = React__namespace.forwardRef(
33
+ ({
34
+ isOpen,
35
+ onClose,
36
+ children,
37
+ side = "right",
38
+ title,
39
+ labelledById,
40
+ className,
41
+ overlayClassName,
42
+ contentClassName,
43
+ closeLabel = "Fermer"
44
+ }, ref) => {
45
+ React__namespace.useEffect(() => {
46
+ if (!isOpen) return;
47
+ const handleKeyDown = (e) => {
48
+ if (e.key === "Escape") onClose();
49
+ };
50
+ document.addEventListener("keydown", handleKeyDown);
51
+ return () => document.removeEventListener("keydown", handleKeyDown);
52
+ }, [isOpen, onClose]);
53
+ React__namespace.useEffect(() => {
54
+ if (!isOpen) return;
55
+ const prev = document.body.style.overflow;
56
+ document.body.style.overflow = "hidden";
57
+ return () => {
58
+ document.body.style.overflow = prev;
59
+ };
60
+ }, [isOpen]);
61
+ if (!isOpen) return null;
62
+ return /* @__PURE__ */ jsxRuntime.jsxs(
63
+ "div",
64
+ {
65
+ ref,
66
+ className: chunkADIDI7AJ_js.cn("fixed inset-0 z-50", overlayClassName),
67
+ role: "dialog",
68
+ "aria-modal": "true",
69
+ "aria-labelledby": labelledById,
70
+ "aria-label": labelledById ? void 0 : title,
71
+ children: [
72
+ /* @__PURE__ */ jsxRuntime.jsx(
73
+ "div",
74
+ {
75
+ className: "absolute inset-0 bg-black/50 transition-opacity",
76
+ onClick: onClose,
77
+ "aria-hidden": "true"
78
+ }
79
+ ),
80
+ /* @__PURE__ */ jsxRuntime.jsxs(
81
+ "div",
82
+ {
83
+ className: chunkADIDI7AJ_js.cn(
84
+ "absolute bg-white shadow-xl flex flex-col overflow-hidden",
85
+ SIDE_CLASSES[side],
86
+ className
87
+ ),
88
+ children: [
89
+ /* @__PURE__ */ jsxRuntime.jsxs(
90
+ "div",
91
+ {
92
+ className: chunkADIDI7AJ_js.cn(
93
+ "flex items-center gap-2 px-4 h-16 shrink-0",
94
+ title ? "justify-between border-b border-gray-100" : "justify-end"
95
+ ),
96
+ children: [
97
+ title && /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-heading font-semibold text-primary truncate", children: title }),
98
+ /* @__PURE__ */ jsxRuntime.jsx(
99
+ "button",
100
+ {
101
+ type: "button",
102
+ onClick: onClose,
103
+ className: "p-2 hover:bg-gray-100 rounded-full transition-colors shrink-0",
104
+ "aria-label": closeLabel,
105
+ children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
106
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18" }),
107
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 6 12 12" })
108
+ ] })
109
+ }
110
+ )
111
+ ]
112
+ }
113
+ ),
114
+ /* @__PURE__ */ jsxRuntime.jsx(
115
+ "div",
116
+ {
117
+ "data-drawer-content": "",
118
+ className: chunkADIDI7AJ_js.cn("flex-1 overflow-y-auto overscroll-contain", contentClassName),
119
+ children
120
+ }
121
+ )
122
+ ]
123
+ }
124
+ )
125
+ ]
126
+ }
127
+ );
128
+ }
129
+ );
130
+ Drawer.displayName = "Drawer";
131
+
132
+ exports.Drawer = Drawer;
@@ -63,7 +63,7 @@ var Checkbox = React.forwardRef(
63
63
  ]
64
64
  }
65
65
  ),
66
- error && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-red-500", children: error })
66
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 text-sm text-red-600", children: error })
67
67
  ] });
68
68
  }
69
69
  );
@@ -85,7 +85,7 @@ var Checkbox = React__namespace.forwardRef(
85
85
  ]
86
86
  }
87
87
  ),
88
- error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-red-500", children: error })
88
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-1 text-sm text-red-600", children: error })
89
89
  ] });
90
90
  }
91
91
  );
@@ -32,17 +32,47 @@ var switchThumbVariants = cva(
32
32
  }
33
33
  );
34
34
  var Switch = React.forwardRef(
35
- ({ checked = false, onCheckedChange, disabled = false, size, label, description, className, name }, ref) => {
36
- const id = React.useId();
35
+ ({
36
+ checked = false,
37
+ onCheckedChange,
38
+ disabled = false,
39
+ size,
40
+ label,
41
+ description,
42
+ className,
43
+ name,
44
+ id,
45
+ "aria-label": ariaLabel,
46
+ "aria-labelledby": ariaLabelledBy,
47
+ "aria-describedby": ariaDescribedBy
48
+ }, ref) => {
49
+ const generatedId = React.useId();
50
+ const switchId = id ?? generatedId;
51
+ const labelId = `${switchId}-label`;
52
+ const descriptionId = `${switchId}-description`;
53
+ const labelledBy = [label ? labelId : null, ariaLabelledBy].filter(Boolean).join(" ") || void 0;
54
+ const resolvedAriaLabel = labelledBy ? void 0 : ariaLabel ?? name;
55
+ const describedBy = [description ? descriptionId : null, ariaDescribedBy].filter(Boolean).join(" ") || void 0;
56
+ const hasAccessibleName = Boolean(labelledBy || resolvedAriaLabel);
57
+ React.useEffect(() => {
58
+ if (hasAccessibleName) return;
59
+ if (typeof process === "undefined") return;
60
+ if (process.env.NODE_ENV === "production") return;
61
+ console.warn(
62
+ "[@dododog/ui] Switch sans nom accessible : passer `label`, `aria-labelledby` ou `aria-label`."
63
+ );
64
+ }, [hasAccessibleName]);
37
65
  const track = /* @__PURE__ */ jsxs(
38
66
  "button",
39
67
  {
40
68
  ref,
41
- id,
69
+ id: switchId,
42
70
  type: "button",
43
71
  role: "switch",
44
72
  "aria-checked": checked,
45
- "aria-label": !label ? name : void 0,
73
+ "aria-labelledby": labelledBy,
74
+ "aria-label": resolvedAriaLabel,
75
+ "aria-describedby": describedBy,
46
76
  disabled,
47
77
  onClick: () => onCheckedChange?.(!checked),
48
78
  className: cn(
@@ -69,8 +99,23 @@ var Switch = React.forwardRef(
69
99
  if (!label && !description) return track;
70
100
  return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between gap-3", className), children: [
71
101
  /* @__PURE__ */ jsxs("div", { children: [
72
- label && /* @__PURE__ */ jsx("label", { htmlFor: id, className: "text-sm font-medium text-primary cursor-pointer", children: label }),
73
- description && /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary mt-0.5", children: description })
102
+ label && /* @__PURE__ */ jsx(
103
+ "label",
104
+ {
105
+ id: labelId,
106
+ htmlFor: switchId,
107
+ className: "text-sm font-medium text-primary cursor-pointer",
108
+ children: label
109
+ }
110
+ ),
111
+ description && /* @__PURE__ */ jsx(
112
+ "p",
113
+ {
114
+ id: descriptionId,
115
+ className: "text-xs text-text-secondary mt-0.5",
116
+ children: description
117
+ }
118
+ )
74
119
  ] }),
75
120
  track
76
121
  ] });
@@ -30,12 +30,12 @@ var DualRangeSlider = React.forwardRef(
30
30
  const labelMin = formatLabel ? formatLabel(valueMin, "min") : String(valueMin);
31
31
  const labelMax = formatLabel ? formatLabel(valueMax, "max") : String(valueMax);
32
32
  return /* @__PURE__ */ jsxs("div", { ref, className: cn("w-full", className), children: [
33
- /* @__PURE__ */ jsxs("div", { className: "relative h-2", children: [
34
- /* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-full bg-gray-200" }),
33
+ /* @__PURE__ */ jsxs("div", { className: "relative h-6", children: [
34
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-x-0 top-1/2 h-2 -translate-y-1/2 rounded-full bg-gray-200" }),
35
35
  /* @__PURE__ */ jsx(
36
36
  "div",
37
37
  {
38
- className: "absolute h-full rounded-full bg-secondary",
38
+ className: "absolute top-1/2 h-2 -translate-y-1/2 rounded-full bg-secondary",
39
39
  style: { left: `${leftPercent}%`, right: `${rightPercent}%` }
40
40
  }
41
41
  ),
@@ -50,7 +50,7 @@ var DualRangeSlider = React.forwardRef(
50
50
  onChange: handleMinChange,
51
51
  "aria-label": "Minimum",
52
52
  className: cn(
53
- "absolute w-full h-2 appearance-none bg-transparent pointer-events-none",
53
+ "absolute inset-0 h-6 w-full appearance-none bg-transparent pointer-events-none",
54
54
  // Webkit thumb
55
55
  "[&::-webkit-slider-thumb]:pointer-events-auto",
56
56
  "[&::-webkit-slider-thumb]:appearance-none",
@@ -82,7 +82,7 @@ var DualRangeSlider = React.forwardRef(
82
82
  onChange: handleMaxChange,
83
83
  "aria-label": "Maximum",
84
84
  className: cn(
85
- "absolute w-full h-2 appearance-none bg-transparent pointer-events-none",
85
+ "absolute inset-0 h-6 w-full appearance-none bg-transparent pointer-events-none",
86
86
  "[&::-webkit-slider-thumb]:pointer-events-auto",
87
87
  "[&::-webkit-slider-thumb]:appearance-none",
88
88
  "[&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:h-5",
@@ -65,7 +65,7 @@ var Textarea = React__namespace.forwardRef(
65
65
  ...props
66
66
  }
67
67
  ),
68
- error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-red-500", children: error })
68
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-1 text-sm text-red-600", children: error })
69
69
  ] });
70
70
  }
71
71
  );
@@ -55,7 +55,7 @@ var RadioGroup = React.forwardRef(
55
55
  })
56
56
  }
57
57
  ),
58
- error && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-red-500", children: error })
58
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 text-sm text-red-600", children: error })
59
59
  ] });
60
60
  }
61
61
  );
@@ -178,7 +178,7 @@ var Autocomplete = React.forwardRef(
178
178
  }
179
179
  )
180
180
  ] }),
181
- error && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-red-500", children: error })
181
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 text-sm text-red-600", children: error })
182
182
  ] });
183
183
  }
184
184
  );
@@ -77,7 +77,7 @@ var RadioGroup = React__namespace.forwardRef(
77
77
  })
78
78
  }
79
79
  ),
80
- error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-red-500", children: error })
80
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "mt-1 text-sm text-red-600", children: error })
81
81
  ] });
82
82
  }
83
83
  );
@@ -2,10 +2,30 @@ import { cn } from './chunk-IMKLN273.mjs';
2
2
  import * as React from 'react';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
4
 
5
+ var FOCUSABLE_SELECTOR = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
5
6
  var Lightbox = React.forwardRef(
6
- ({ images, initialIndex = 0, open, onClose, className }, ref) => {
7
+ ({
8
+ images,
9
+ initialIndex = 0,
10
+ open,
11
+ onClose,
12
+ className,
13
+ "aria-label": ariaLabel = "Visionneuse d'images"
14
+ }, ref) => {
7
15
  const [current, setCurrent] = React.useState(initialIndex);
8
16
  const touchStartX = React.useRef(0);
17
+ const containerRef = React.useRef(null);
18
+ const setRefs = React.useCallback(
19
+ (node) => {
20
+ containerRef.current = node;
21
+ if (typeof ref === "function") {
22
+ ref(node);
23
+ } else if (ref) {
24
+ ref.current = node;
25
+ }
26
+ },
27
+ [ref]
28
+ );
9
29
  React.useEffect(() => {
10
30
  if (open) setCurrent(initialIndex);
11
31
  }, [open, initialIndex]);
@@ -15,6 +35,28 @@ var Lightbox = React.forwardRef(
15
35
  if (e.key === "Escape") onClose();
16
36
  if (e.key === "ArrowLeft") setCurrent((c) => (c - 1 + images.length) % images.length);
17
37
  if (e.key === "ArrowRight") setCurrent((c) => (c + 1) % images.length);
38
+ if (e.key === "Tab") {
39
+ const node = containerRef.current;
40
+ if (!node) return;
41
+ const items = Array.from(
42
+ node.querySelectorAll(FOCUSABLE_SELECTOR)
43
+ );
44
+ if (items.length === 0) {
45
+ e.preventDefault();
46
+ return;
47
+ }
48
+ const first = items[0];
49
+ const last = items[items.length - 1];
50
+ const active = document.activeElement;
51
+ const inside = active instanceof Node && node.contains(active);
52
+ if (e.shiftKey && (!inside || active === first)) {
53
+ e.preventDefault();
54
+ last.focus();
55
+ } else if (!e.shiftKey && (!inside || active === last)) {
56
+ e.preventDefault();
57
+ first.focus();
58
+ }
59
+ }
18
60
  }
19
61
  document.addEventListener("keydown", handleKeyDown);
20
62
  document.body.style.overflow = "hidden";
@@ -23,13 +65,24 @@ var Lightbox = React.forwardRef(
23
65
  document.body.style.overflow = "";
24
66
  };
25
67
  }, [open, images.length, onClose]);
68
+ React.useEffect(() => {
69
+ if (!open) return;
70
+ const previouslyFocused = document.activeElement;
71
+ const node = containerRef.current;
72
+ const target = node?.querySelector(FOCUSABLE_SELECTOR) ?? node ?? null;
73
+ target?.focus?.();
74
+ return () => {
75
+ previouslyFocused?.focus?.();
76
+ };
77
+ }, [open]);
26
78
  if (!open || images.length === 0) return null;
27
79
  const prev = () => setCurrent((c) => (c - 1 + images.length) % images.length);
28
80
  const next = () => setCurrent((c) => (c + 1) % images.length);
29
81
  return /* @__PURE__ */ jsxs(
30
82
  "div",
31
83
  {
32
- ref,
84
+ ref: setRefs,
85
+ tabIndex: -1,
33
86
  className: cn(
34
87
  "fixed inset-0 z-50 flex items-center justify-center bg-black/90",
35
88
  className
@@ -37,14 +90,14 @@ var Lightbox = React.forwardRef(
37
90
  onClick: onClose,
38
91
  role: "dialog",
39
92
  "aria-modal": "true",
40
- "aria-label": "Image viewer",
93
+ "aria-label": ariaLabel,
41
94
  children: [
42
95
  /* @__PURE__ */ jsx(
43
96
  "button",
44
97
  {
45
98
  onClick: onClose,
46
99
  className: "absolute top-4 right-4 z-10 w-10 h-10 rounded-full bg-white/10 backdrop-blur-sm flex items-center justify-center text-white hover:bg-white/20 transition-colors",
47
- "aria-label": "Close",
100
+ "aria-label": "Fermer la visionneuse",
48
101
  children: /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
49
102
  /* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
50
103
  /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
@@ -52,9 +105,12 @@ var Lightbox = React.forwardRef(
52
105
  }
53
106
  ),
54
107
  /* @__PURE__ */ jsxs("div", { className: "absolute top-4 left-4 z-10 px-3 py-1.5 rounded-full bg-white/10 backdrop-blur-sm text-white text-sm", children: [
55
- current + 1,
56
- " / ",
57
- images.length
108
+ /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", children: [
109
+ current + 1,
110
+ " / ",
111
+ images.length
112
+ ] }),
113
+ /* @__PURE__ */ jsx("span", { className: "sr-only", "aria-live": "polite", "aria-atomic": "true", children: `Image ${current + 1} sur ${images.length}` })
58
114
  ] }),
59
115
  /* @__PURE__ */ jsx(
60
116
  "div",
@@ -90,7 +146,7 @@ var Lightbox = React.forwardRef(
90
146
  prev();
91
147
  },
92
148
  className: "absolute left-4 top-1/2 -translate-y-1/2 w-12 h-12 rounded-full bg-white/10 backdrop-blur-sm flex items-center justify-center text-white hover:bg-white/20 transition-colors",
93
- "aria-label": "Previous image",
149
+ "aria-label": "Image pr\xE9c\xE9dente",
94
150
  children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "m15 18-6-6 6-6" }) })
95
151
  }
96
152
  ),
@@ -102,33 +158,44 @@ var Lightbox = React.forwardRef(
102
158
  next();
103
159
  },
104
160
  className: "absolute right-4 top-1/2 -translate-y-1/2 w-12 h-12 rounded-full bg-white/10 backdrop-blur-sm flex items-center justify-center text-white hover:bg-white/20 transition-colors",
105
- "aria-label": "Next image",
161
+ "aria-label": "Image suivante",
106
162
  children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" }) })
107
163
  }
108
164
  )
109
165
  ] }),
110
- images.length > 1 && /* @__PURE__ */ jsx("div", { className: "absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2 max-w-[80vw] overflow-x-auto px-4 py-2", children: images.map((img, i) => /* @__PURE__ */ jsx(
111
- "button",
166
+ images.length > 1 && /* @__PURE__ */ jsx(
167
+ "div",
112
168
  {
113
- onClick: (e) => {
114
- e.stopPropagation();
115
- setCurrent(i);
116
- },
117
- className: cn(
118
- "w-16 h-12 rounded-lg overflow-hidden flex-shrink-0 border-2 transition-all",
119
- i === current ? "border-white opacity-100" : "border-transparent opacity-50 hover:opacity-75"
120
- ),
121
- children: /* @__PURE__ */ jsx(
122
- "img",
169
+ role: "group",
170
+ "aria-label": "Vignettes des images",
171
+ className: "absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2 max-w-[80vw] overflow-x-auto px-4 py-2",
172
+ children: images.map((img, i) => /* @__PURE__ */ jsx(
173
+ "button",
123
174
  {
124
- src: img.src,
125
- alt: img.alt,
126
- className: "w-full h-full object-cover"
127
- }
128
- )
129
- },
130
- i
131
- )) })
175
+ onClick: (e) => {
176
+ e.stopPropagation();
177
+ setCurrent(i);
178
+ },
179
+ "aria-label": `Afficher l'image ${i + 1} : ${img.alt}`,
180
+ "aria-current": i === current ? "true" : void 0,
181
+ className: cn(
182
+ "w-16 h-12 rounded-lg overflow-hidden flex-shrink-0 border-2 transition-all",
183
+ i === current ? "border-white opacity-100" : "border-transparent opacity-50 hover:opacity-75"
184
+ ),
185
+ children: /* @__PURE__ */ jsx(
186
+ "img",
187
+ {
188
+ src: img.src,
189
+ alt: "",
190
+ "aria-hidden": "true",
191
+ className: "w-full h-full object-cover"
192
+ }
193
+ )
194
+ },
195
+ i
196
+ ))
197
+ }
198
+ )
132
199
  ]
133
200
  }
134
201
  );