@med-o/ui 0.0.0-stage → 0.1.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 (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -2
  3. package/dist/index.d.ts +2021 -0
  4. package/dist/index.js +4721 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +78 -4
  7. package/src/accordion/accordion.css +184 -0
  8. package/src/alert/alert.css +147 -0
  9. package/src/avatar/avatar.css +221 -0
  10. package/src/badge/badge.css +153 -0
  11. package/src/bottom-sheet/bottom-sheet.css +202 -0
  12. package/src/breadcrumbs/breadcrumbs.css +147 -0
  13. package/src/bulk-action-bar/bulk-action-bar.css +98 -0
  14. package/src/button/button.css +319 -0
  15. package/src/card/card.css +217 -0
  16. package/src/checkbox/choice.css +196 -0
  17. package/src/combobox/combobox.css +125 -0
  18. package/src/date-time/date-time.css +474 -0
  19. package/src/dialog/dialog.css +319 -0
  20. package/src/drawer/drawer.css +156 -0
  21. package/src/empty-state/empty-state.css +161 -0
  22. package/src/field/field.css +115 -0
  23. package/src/link/link.css +103 -0
  24. package/src/list/list.css +172 -0
  25. package/src/list-view/list-view.css +473 -0
  26. package/src/listbox/listbox.css +203 -0
  27. package/src/media/media.css +298 -0
  28. package/src/menu/menu.css +183 -0
  29. package/src/navigation/navigation.css +626 -0
  30. package/src/pagination/pagination.css +132 -0
  31. package/src/popover/popover.css +166 -0
  32. package/src/progress/progress.css +225 -0
  33. package/src/search/search.css +309 -0
  34. package/src/select/select.css +229 -0
  35. package/src/slider/slider.css +212 -0
  36. package/src/styles.css +40 -0
  37. package/src/switch/switch.css +160 -0
  38. package/src/table/table.css +276 -0
  39. package/src/tabs/tabs.css +211 -0
  40. package/src/tag/tag.css +225 -0
  41. package/src/text-field/input.css +206 -0
  42. package/src/toast/toast.css +184 -0
  43. package/src/toggle-button/toggle-button.css +143 -0
  44. package/src/tooltip/tooltip.css +66 -0
  45. package/src/upload/upload.css +380 -0
  46. package/src/utils/utilities.css +14 -0
package/dist/index.js ADDED
@@ -0,0 +1,4721 @@
1
+ "use client";
2
+
3
+ // src/button/Button.tsx
4
+ import { Button as AriaButton } from "react-aria-components";
5
+
6
+ // src/progress/Progress.tsx
7
+ import { useId } from "react";
8
+ import { Label, ProgressBar as AriaProgressBar } from "react-aria-components";
9
+
10
+ // src/utils/cx.ts
11
+ var cx = (...classes) => classes.filter(Boolean).join(" ");
12
+
13
+ // src/progress/Progress.tsx
14
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
15
+ var SpinnerGraphic = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false", children: [
16
+ /* @__PURE__ */ jsx("circle", { className: "medo-spinner__track", cx: "12", cy: "12", r: "10" }),
17
+ /* @__PURE__ */ jsx("circle", { className: "medo-spinner__arc", cx: "12", cy: "12", r: "10", pathLength: 100 })
18
+ ] });
19
+ function Spinner({ size = "md", tone = "current", decorative, className, style, ...props }) {
20
+ const classes = cx("medo-spinner", `medo-spinner--${size}`, tone !== "current" && `medo-spinner--${tone}`, className);
21
+ if (decorative) {
22
+ return /* @__PURE__ */ jsx("span", { className: classes, style, "aria-hidden": "true", children: /* @__PURE__ */ jsx(SpinnerGraphic, {}) });
23
+ }
24
+ return /* @__PURE__ */ jsx(AriaProgressBar, { isIndeterminate: true, "aria-label": props["aria-label"] ?? "Loading", className: classes, style, children: /* @__PURE__ */ jsx(SpinnerGraphic, {}) });
25
+ }
26
+ function ProgressBar({
27
+ label,
28
+ description,
29
+ size = "md",
30
+ status = "default",
31
+ showValue = true,
32
+ className,
33
+ style,
34
+ ...props
35
+ }) {
36
+ const descriptionId = useId();
37
+ return /* @__PURE__ */ jsx(
38
+ AriaProgressBar,
39
+ {
40
+ ...props,
41
+ "aria-describedby": description ? descriptionId : void 0,
42
+ className: cx(
43
+ "medo-progress",
44
+ `medo-progress--${size}`,
45
+ status !== "default" && `medo-progress--${status}`,
46
+ props.isIndeterminate && "medo-progress--indeterminate",
47
+ className
48
+ ),
49
+ style,
50
+ children: ({ percentage, valueText, isIndeterminate }) => /* @__PURE__ */ jsxs(Fragment, { children: [
51
+ (label || showValue && !isIndeterminate) && /* @__PURE__ */ jsxs("div", { className: "medo-progress__header", children: [
52
+ label && /* @__PURE__ */ jsx(Label, { className: "medo-progress__label", children: label }),
53
+ showValue && !isIndeterminate && /* @__PURE__ */ jsx("span", { className: "medo-progress__value", children: valueText })
54
+ ] }),
55
+ /* @__PURE__ */ jsx("div", { className: "medo-progress__track", children: /* @__PURE__ */ jsx("div", { className: "medo-progress__fill", style: isIndeterminate ? void 0 : { inlineSize: `${percentage}%` } }) }),
56
+ description && // Live only for the final state, so regular updates („12 s left“) do not chatter.
57
+ /* @__PURE__ */ jsx("span", { id: descriptionId, className: "medo-progress__description", "aria-live": status === "default" ? void 0 : "polite", children: description })
58
+ ] })
59
+ }
60
+ );
61
+ }
62
+ function Skeleton({ shape = "text", lines = 1, width, height, className }) {
63
+ const style = { inlineSize: width, blockSize: height };
64
+ const one = (key) => /* @__PURE__ */ jsx("span", { className: cx("medo-skeleton", `medo-skeleton--${shape}`, className), style, "aria-hidden": "true" }, key);
65
+ if (shape !== "text" || lines <= 1) return one();
66
+ return /* @__PURE__ */ jsx("span", { className: "medo-skeleton-lines", "aria-hidden": "true", children: Array.from({ length: lines }, (_, i) => one(i)) });
67
+ }
68
+
69
+ // src/button/Button.tsx
70
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
71
+ function Button({ variant = "primary", size = "md", startIcon, endIcon, className, children, ...props }) {
72
+ const icon = props.isPending ? /* @__PURE__ */ jsx2(Spinner, { decorative: true }) : startIcon;
73
+ return /* @__PURE__ */ jsxs2(AriaButton, { ...props, className: cx("medo-button", `medo-button--${variant}`, `medo-button--${size}`, className), children: [
74
+ icon && /* @__PURE__ */ jsx2("span", { className: "medo-button__icon", "aria-hidden": "true", children: icon }),
75
+ /* @__PURE__ */ jsx2("span", { className: "medo-button__label", children }),
76
+ endIcon && /* @__PURE__ */ jsx2("span", { className: "medo-button__icon", "aria-hidden": "true", children: endIcon })
77
+ ] });
78
+ }
79
+ function ButtonGroup({ context = "footer", children, destructive, className, ...props }) {
80
+ return /* @__PURE__ */ jsxs2(
81
+ "div",
82
+ {
83
+ role: props["aria-label"] ? "group" : void 0,
84
+ "aria-label": props["aria-label"],
85
+ className: cx("medo-button-group", `medo-button-group--${context}`, className),
86
+ children: [
87
+ destructive && context === "footer" && /* @__PURE__ */ jsx2("div", { className: "medo-button-group__destructive", children: destructive }),
88
+ children
89
+ ]
90
+ }
91
+ );
92
+ }
93
+
94
+ // src/icon-button/IconButton.tsx
95
+ import { Button as AriaButton2 } from "react-aria-components";
96
+
97
+ // src/tooltip/Tooltip.tsx
98
+ import { useContext } from "react";
99
+ import {
100
+ Link as AriaLink,
101
+ Tooltip as AriaTooltip,
102
+ TooltipTrigger as AriaTooltipTrigger,
103
+ TooltipTriggerStateContext
104
+ } from "react-aria-components";
105
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
106
+ function TooltipTrigger({ delay = 500, closeDelay = 100, ...props }) {
107
+ return /* @__PURE__ */ jsx3(AriaTooltipTrigger, { delay, closeDelay, ...props });
108
+ }
109
+ var KEY_LABELS = { Control: "Ctrl", Meta: "\u2318", Shift: "\u21E7", Alt: "Alt" };
110
+ var isApple = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
111
+ var resolveShortcut = (keys) => keys.replace(/\bMod\b/g, isApple() ? "Meta" : "Control");
112
+ var formatShortcut = (keys) => resolveShortcut(keys).split("+").map((k) => KEY_LABELS[k] ?? k).join(" ");
113
+ function Tooltip({ children, shortcut, placement = "top", offset = 8, className, ...props }) {
114
+ return /* @__PURE__ */ jsxs3(AriaTooltip, { ...props, placement, offset, className: cx("medo-tooltip", className), children: [
115
+ /* @__PURE__ */ jsx3("span", { children }),
116
+ shortcut && /* @__PURE__ */ jsx3("kbd", { className: "medo-tooltip__shortcut", children: shortcut })
117
+ ] });
118
+ }
119
+ function TooltipLink(props) {
120
+ const tooltip = useContext(TooltipTriggerStateContext);
121
+ return /* @__PURE__ */ jsx3(AriaLink, { ...props, onHoverStart: () => tooltip?.open(), onHoverEnd: () => tooltip?.close() });
122
+ }
123
+
124
+ // src/icon-button/IconButton.tsx
125
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
126
+ function IconButton({ icon, variant = "ghost", size = "md", tooltip, shortcut, shortcutLabel, className, ...props }) {
127
+ const button = /* @__PURE__ */ jsx4(
128
+ AriaButton2,
129
+ {
130
+ ...props,
131
+ "aria-keyshortcuts": shortcut && resolveShortcut(shortcut),
132
+ className: cx("medo-button", "medo-button--icon-only", `medo-button--${variant}`, `medo-button--${size}`, className),
133
+ children: /* @__PURE__ */ jsx4("span", { className: "medo-button__icon", "aria-hidden": "true", children: props.isPending ? /* @__PURE__ */ jsx4(Spinner, { decorative: true }) : icon })
134
+ }
135
+ );
136
+ if (tooltip === false) return button;
137
+ return /* @__PURE__ */ jsxs4(TooltipTrigger, { children: [
138
+ button,
139
+ /* @__PURE__ */ jsx4(Tooltip, { shortcut: shortcutLabel ?? (shortcut && formatShortcut(shortcut)), children: tooltip ?? props["aria-label"] })
140
+ ] });
141
+ }
142
+
143
+ // src/text-field/TextField.tsx
144
+ import { useEffect, useState } from "react";
145
+ import {
146
+ Button as AriaButton3,
147
+ Input as AriaInput,
148
+ TextArea as AriaTextArea,
149
+ TextField as AriaTextField
150
+ } from "react-aria-components";
151
+
152
+ // src/field/Field.tsx
153
+ import { FieldError, Label as Label2, Text } from "react-aria-components";
154
+ import { CircleAlert } from "lucide-react";
155
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
156
+ function FieldLabel({ children, isRequired }) {
157
+ return /* @__PURE__ */ jsxs5(Label2, { className: "medo-field__label", children: [
158
+ children,
159
+ isRequired && /* @__PURE__ */ jsx5("span", { className: "medo-field__required", "aria-hidden": "true", children: "*" })
160
+ ] });
161
+ }
162
+ function FieldFooter({
163
+ description,
164
+ errorMessage,
165
+ children
166
+ }) {
167
+ return /* @__PURE__ */ jsxs5("div", { className: "medo-field__footer", children: [
168
+ description && /* @__PURE__ */ jsx5(Text, { slot: "description", className: "medo-field__description", children: description }),
169
+ /* @__PURE__ */ jsx5(FieldError, { className: "medo-field__error", children: (validation) => /* @__PURE__ */ jsxs5(Fragment2, { children: [
170
+ /* @__PURE__ */ jsx5(CircleAlert, { "aria-hidden": "true" }),
171
+ /* @__PURE__ */ jsx5("span", { children: (typeof errorMessage === "function" ? errorMessage(validation) : errorMessage) || validation.validationErrors.join(" ") })
172
+ ] }) }),
173
+ children
174
+ ] });
175
+ }
176
+
177
+ // src/text-field/TextField.tsx
178
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
179
+ function TextField({
180
+ label,
181
+ description,
182
+ errorMessage,
183
+ placeholder,
184
+ size = "md",
185
+ leadingIcon,
186
+ trailing,
187
+ multiline,
188
+ characterLimit,
189
+ className,
190
+ onChange,
191
+ ...props
192
+ }) {
193
+ const [length, setLength] = useState((props.value ?? props.defaultValue ?? "").length);
194
+ const count = props.value !== void 0 ? props.value.length : length;
195
+ return /* @__PURE__ */ jsxs6(
196
+ AriaTextField,
197
+ {
198
+ ...props,
199
+ className: cx("medo-field", className),
200
+ onChange: (value) => {
201
+ setLength(value.length);
202
+ onChange?.(value);
203
+ },
204
+ children: [
205
+ /* @__PURE__ */ jsx6(FieldLabel, { isRequired: props.isRequired, children: label }),
206
+ multiline ? /* @__PURE__ */ jsx6(AriaTextArea, { className: cx("medo-textarea", `medo-textarea--${size === "lg" ? "lg" : "md"}`), placeholder, onInput: clearErrorWhenValid }) : /* @__PURE__ */ jsxs6("div", { className: cx("medo-input", `medo-input--${size}`), children: [
207
+ leadingIcon && /* @__PURE__ */ jsx6("span", { className: "medo-input__icon", "aria-hidden": "true", children: leadingIcon }),
208
+ /* @__PURE__ */ jsx6(AriaInput, { className: "medo-input__control", placeholder, onInput: clearErrorWhenValid }),
209
+ trailing
210
+ ] }),
211
+ /* @__PURE__ */ jsx6(FieldFooter, { description, errorMessage, children: characterLimit !== void 0 && /* @__PURE__ */ jsx6(CharacterCount, { count, limit: characterLimit }) })
212
+ ]
213
+ }
214
+ );
215
+ }
216
+ function clearErrorWhenValid(e) {
217
+ const el = e.currentTarget;
218
+ requestAnimationFrame(() => {
219
+ if (el.validity.valid && el.closest("[data-invalid]")) el.dispatchEvent(new Event("change"));
220
+ });
221
+ }
222
+ function CharacterCount({ count, limit }) {
223
+ const over = count - limit;
224
+ const text = over > 0 ? `${over} too many` : `${count} / ${limit}`;
225
+ const [announcement, setAnnouncement] = useState("");
226
+ useEffect(() => {
227
+ const id = setTimeout(
228
+ () => setAnnouncement(over > 0 ? `${over} characters too many` : `${limit - count} characters left`),
229
+ 1e3
230
+ );
231
+ return () => clearTimeout(id);
232
+ }, [count, limit, over]);
233
+ return /* @__PURE__ */ jsxs6(Fragment3, { children: [
234
+ /* @__PURE__ */ jsx6("span", { className: "medo-field__counter", "data-over": over > 0 || void 0, "aria-hidden": "true", children: text }),
235
+ /* @__PURE__ */ jsx6("span", { className: "medo-visually-hidden", "aria-live": "polite", children: announcement })
236
+ ] });
237
+ }
238
+ function InputAction({ icon, ...props }) {
239
+ return /* @__PURE__ */ jsx6(AriaButton3, { ...props, className: "medo-input__action", children: icon });
240
+ }
241
+
242
+ // src/checkbox/Checkbox.tsx
243
+ import { createContext, useContext as useContext2 } from "react";
244
+ import {
245
+ CheckboxButton,
246
+ CheckboxField,
247
+ CheckboxGroup as AriaCheckboxGroup,
248
+ Text as Text2
249
+ } from "react-aria-components";
250
+ import { Check, Minus } from "lucide-react";
251
+ import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
252
+ var ChoiceSizeContext = createContext("md");
253
+ function Checkbox({ children, description, size, className, ...props }) {
254
+ const groupSize = useContext2(ChoiceSizeContext);
255
+ return /* @__PURE__ */ jsxs7(CheckboxField, { ...props, className: cx("medo-choice", "medo-checkbox", `medo-choice--${size ?? groupSize}`, className), children: [
256
+ /* @__PURE__ */ jsx7(CheckboxButton, { className: "medo-choice__button", children: ({ isSelected, isIndeterminate }) => /* @__PURE__ */ jsxs7(Fragment4, { children: [
257
+ /* @__PURE__ */ jsx7("span", { className: "medo-choice__control", "aria-hidden": "true", children: isIndeterminate ? /* @__PURE__ */ jsx7(Minus, {}) : isSelected ? /* @__PURE__ */ jsx7(Check, {}) : null }),
258
+ children && /* @__PURE__ */ jsx7("span", { className: "medo-choice__label", children })
259
+ ] }) }),
260
+ description && /* @__PURE__ */ jsx7(Text2, { slot: "description", className: "medo-choice__description", children: description })
261
+ ] });
262
+ }
263
+ function CheckboxGroup({ label, description, errorMessage, size = "md", orientation = "vertical", children, className, ...props }) {
264
+ return /* @__PURE__ */ jsxs7(AriaCheckboxGroup, { ...props, className: cx("medo-field", "medo-field--group", className), "data-orientation": orientation, children: [
265
+ label && /* @__PURE__ */ jsx7(FieldLabel, { isRequired: props.isRequired, children: label }),
266
+ /* @__PURE__ */ jsx7(ChoiceSizeContext.Provider, { value: size, children: /* @__PURE__ */ jsx7("div", { className: "medo-field__options", children }) }),
267
+ /* @__PURE__ */ jsx7(FieldFooter, { description, errorMessage })
268
+ ] });
269
+ }
270
+
271
+ // src/radio/Radio.tsx
272
+ import { useContext as useContext3 } from "react";
273
+ import { RadioButton, RadioField, RadioGroup as AriaRadioGroup, Text as Text3 } from "react-aria-components";
274
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
275
+ function Radio({ children, description, size, className, ...props }) {
276
+ const groupSize = useContext3(ChoiceSizeContext);
277
+ return /* @__PURE__ */ jsxs8(RadioField, { ...props, className: cx("medo-choice", "medo-radio", `medo-choice--${size ?? groupSize}`, className), children: [
278
+ /* @__PURE__ */ jsxs8(RadioButton, { className: "medo-choice__button", children: [
279
+ /* @__PURE__ */ jsx8("span", { className: "medo-choice__control", "aria-hidden": "true" }),
280
+ children && /* @__PURE__ */ jsx8("span", { className: "medo-choice__label", children })
281
+ ] }),
282
+ description && /* @__PURE__ */ jsx8(Text3, { slot: "description", className: "medo-choice__description", children: description })
283
+ ] });
284
+ }
285
+ function RadioGroup({ label, description, errorMessage, size = "md", children, className, ...props }) {
286
+ return /* @__PURE__ */ jsxs8(AriaRadioGroup, { ...props, className: cx("medo-field", "medo-field--group", className), children: [
287
+ label && /* @__PURE__ */ jsx8(FieldLabel, { isRequired: props.isRequired, children: label }),
288
+ /* @__PURE__ */ jsx8(ChoiceSizeContext.Provider, { value: size, children: /* @__PURE__ */ jsx8("div", { className: "medo-field__options", children }) }),
289
+ /* @__PURE__ */ jsx8(FieldFooter, { description, errorMessage })
290
+ ] });
291
+ }
292
+
293
+ // src/switch/Switch.tsx
294
+ import { SwitchButton, SwitchField, Text as Text4 } from "react-aria-components";
295
+ import { Check as Check2, LoaderCircle, X } from "lucide-react";
296
+ import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
297
+ function Switch({
298
+ children,
299
+ description,
300
+ size = "md",
301
+ labelPosition = "end",
302
+ isLoading,
303
+ isReadOnly,
304
+ className,
305
+ ...props
306
+ }) {
307
+ return /* @__PURE__ */ jsxs9(
308
+ SwitchField,
309
+ {
310
+ ...props,
311
+ isReadOnly: isReadOnly || isLoading,
312
+ "aria-busy": isLoading || void 0,
313
+ className: cx("medo-switch", `medo-switch--${size}`, `medo-switch--label-${labelPosition}`, className),
314
+ children: [
315
+ /* @__PURE__ */ jsx9(SwitchButton, { className: "medo-switch__button", children: ({ isSelected }) => /* @__PURE__ */ jsxs9(Fragment5, { children: [
316
+ /* @__PURE__ */ jsx9("span", { className: "medo-switch__track", "aria-hidden": "true", children: /* @__PURE__ */ jsx9("span", { className: "medo-switch__thumb", children: isLoading ? /* @__PURE__ */ jsx9(LoaderCircle, { className: "medo-switch__spinner" }) : isSelected ? /* @__PURE__ */ jsx9(Check2, {}) : /* @__PURE__ */ jsx9(X, {}) }) }),
317
+ /* @__PURE__ */ jsx9("span", { className: "medo-switch__label", children })
318
+ ] }) }),
319
+ description && /* @__PURE__ */ jsx9(Text4, { slot: "description", className: "medo-switch__description", children: description })
320
+ ]
321
+ }
322
+ );
323
+ }
324
+
325
+ // src/badge/Badge.tsx
326
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
327
+ function Badge({ tone = "neutral", size = "md", dot, icon, className, children, ...props }) {
328
+ return /* @__PURE__ */ jsxs10("span", { ...props, className: cx("medo-badge", `medo-badge--${tone}`, `medo-badge--${size}`, className), children: [
329
+ dot ? /* @__PURE__ */ jsx10("span", { className: "medo-badge__dot", "aria-hidden": "true" }) : icon && /* @__PURE__ */ jsx10("span", { className: "medo-badge__icon", "aria-hidden": "true", children: icon }),
330
+ children
331
+ ] });
332
+ }
333
+ function BadgeCount({ count, max = 99, tone = "danger", size = "md", label, className, ...props }) {
334
+ if (count <= 0) return null;
335
+ const text = count > max ? `${max}+` : String(count);
336
+ return /* @__PURE__ */ jsxs10(
337
+ "span",
338
+ {
339
+ ...props,
340
+ "aria-hidden": label ? void 0 : true,
341
+ className: cx("medo-badge-count", `medo-badge-count--${tone}`, `medo-badge-count--${size}`, className),
342
+ children: [
343
+ /* @__PURE__ */ jsx10("span", { "aria-hidden": label ? true : void 0, children: text }),
344
+ label && /* @__PURE__ */ jsx10("span", { className: "medo-visually-hidden", children: label })
345
+ ]
346
+ }
347
+ );
348
+ }
349
+ function BadgeDot({ tone = "neutral", label, className, ...props }) {
350
+ return /* @__PURE__ */ jsx10(
351
+ "span",
352
+ {
353
+ ...props,
354
+ role: label ? "img" : void 0,
355
+ "aria-label": label,
356
+ "aria-hidden": label ? void 0 : true,
357
+ className: cx("medo-badge-dot", `medo-badge-dot--${tone}`, className)
358
+ }
359
+ );
360
+ }
361
+
362
+ // src/tag/Tag.tsx
363
+ import { createContext as createContext2, useContext as useContext4, useRef, useState as useState2 } from "react";
364
+ import {
365
+ Button as AriaButton4,
366
+ Tag as AriaTag,
367
+ TagGroup as AriaTagGroup,
368
+ TagList,
369
+ Text as Text5
370
+ } from "react-aria-components";
371
+ import { Check as Check3, X as X2 } from "lucide-react";
372
+ import { Fragment as Fragment6, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
373
+ var TagSizeContext = createContext2("md");
374
+ function TagGroup({
375
+ label,
376
+ description,
377
+ size = "md",
378
+ children,
379
+ items,
380
+ renderEmptyState,
381
+ onRemove,
382
+ removedMessage = (labels) => `${labels.join(", ")} removed`,
383
+ className,
384
+ ...props
385
+ }) {
386
+ const ref = useRef(null);
387
+ const [announcement, setAnnouncement] = useState2("");
388
+ const handleRemove = onRemove && ((keys) => {
389
+ const labels = [...keys].map(
390
+ (key) => ref.current?.querySelector(`[data-key="${CSS.escape(String(key))}"] .medo-tag__label`)?.textContent ?? String(key)
391
+ );
392
+ onRemove(keys);
393
+ setAnnouncement(removedMessage(labels));
394
+ });
395
+ return /* @__PURE__ */ jsxs11(AriaTagGroup, { ...props, ref, onRemove: handleRemove, className: cx("medo-field", "medo-field--group", className), children: [
396
+ label && /* @__PURE__ */ jsx11(FieldLabel, { children: label }),
397
+ /* @__PURE__ */ jsx11(TagSizeContext.Provider, { value: size, children: /* @__PURE__ */ jsx11(TagList, { items, renderEmptyState, className: "medo-tag-list", children }) }),
398
+ description && /* @__PURE__ */ jsx11("div", { className: "medo-field__footer", children: /* @__PURE__ */ jsx11(Text5, { slot: "description", className: "medo-field__description", children: description }) }),
399
+ /* @__PURE__ */ jsx11("span", { className: "medo-visually-hidden", "aria-live": "polite", children: announcement })
400
+ ] });
401
+ }
402
+ function Tag({ children, icon, textValue, className, ...props }) {
403
+ const size = useContext4(TagSizeContext);
404
+ return /* @__PURE__ */ jsx11(
405
+ AriaTag,
406
+ {
407
+ ...props,
408
+ textValue: textValue ?? (typeof children === "string" ? children : void 0),
409
+ className: ({ selectionMode }) => cx("medo-tag", `medo-tag--${size}`, selectionMode !== "none" && "medo-tag--selectable", className),
410
+ children: ({ allowsRemoving, isSelected, selectionMode }) => /* @__PURE__ */ jsxs11(Fragment6, { children: [
411
+ selectionMode !== "none" && isSelected ? /* @__PURE__ */ jsx11("span", { className: "medo-tag__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx11(Check3, {}) }) : icon && /* @__PURE__ */ jsx11("span", { className: "medo-tag__icon", "aria-hidden": "true", children: icon }),
412
+ /* @__PURE__ */ jsx11("span", { className: "medo-tag__label", children }),
413
+ allowsRemoving && /* @__PURE__ */ jsx11(AriaButton4, { slot: "remove", className: "medo-tag__remove", children: /* @__PURE__ */ jsx11(X2, { "aria-hidden": "true" }) })
414
+ ] })
415
+ }
416
+ );
417
+ }
418
+
419
+ // src/avatar/Avatar.tsx
420
+ import {
421
+ Children,
422
+ cloneElement,
423
+ createContext as createContext3,
424
+ isValidElement,
425
+ useContext as useContext5,
426
+ useEffect as useEffect2,
427
+ useState as useState3
428
+ } from "react";
429
+ import { Button as AriaButton5 } from "react-aria-components";
430
+ import { Building2, User } from "lucide-react";
431
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
432
+ var TONES = ["blue", "violet", "pink", "amber", "green", "teal"];
433
+ var AvatarSizeContext = createContext3(void 0);
434
+ function toneFor(key) {
435
+ let hash = 0;
436
+ for (const char of key) hash = hash * 31 + char.codePointAt(0) | 0;
437
+ return TONES[Math.abs(hash) % TONES.length];
438
+ }
439
+ function initialsOf(name) {
440
+ const words = name.trim().split(/\s+/).filter(Boolean);
441
+ if (words.length === 0) return "";
442
+ if (words.length === 1) return Array.from(words[0]).slice(0, 2).join("").toUpperCase();
443
+ return (Array.from(words[0])[0] + Array.from(words[words.length - 1])[0]).toUpperCase();
444
+ }
445
+ var STATUS_TEXT = { online: "online", away: "away", busy: "busy", offline: "offline" };
446
+ function Avatar({
447
+ name,
448
+ src,
449
+ size,
450
+ shape = "circle",
451
+ colorKey,
452
+ status,
453
+ statusLabels,
454
+ decorative,
455
+ unknownLabel,
456
+ className,
457
+ style
458
+ }) {
459
+ const groupSize = useContext5(AvatarSizeContext);
460
+ const finalSize = size ?? groupSize ?? "md";
461
+ const [failed, setFailed] = useState3(false);
462
+ useEffect2(() => setFailed(false), [src]);
463
+ const initials = name ? initialsOf(name) : "";
464
+ const showImage = Boolean(src) && !failed;
465
+ const tone = initials ? toneFor(colorKey ?? name) : "neutral";
466
+ const statusText = status ? statusLabels?.[status] ?? STATUS_TEXT[status] : void 0;
467
+ const fallbackName = unknownLabel ?? (shape === "square" ? "Unknown organization" : "Unknown person");
468
+ const label = [name || fallbackName, statusText].filter(Boolean).join(", ");
469
+ return /* @__PURE__ */ jsxs12(
470
+ "span",
471
+ {
472
+ role: decorative ? void 0 : "img",
473
+ "aria-label": decorative ? void 0 : label,
474
+ "aria-hidden": decorative || void 0,
475
+ className: cx(
476
+ "medo-avatar",
477
+ `medo-avatar--${finalSize}`,
478
+ `medo-avatar--${shape}`,
479
+ `medo-avatar--${tone}`,
480
+ showImage && "medo-avatar--has-image",
481
+ className
482
+ ),
483
+ style,
484
+ children: [
485
+ initials ? /* @__PURE__ */ jsx12("span", { className: "medo-avatar__initials", "aria-hidden": "true", children: initials }) : /* @__PURE__ */ jsx12("span", { className: "medo-avatar__icon", "aria-hidden": "true", children: shape === "square" ? /* @__PURE__ */ jsx12(Building2, {}) : /* @__PURE__ */ jsx12(User, {}) }),
486
+ showImage && /* @__PURE__ */ jsx12("img", { className: "medo-avatar__image", src, alt: "", onError: () => setFailed(true) }),
487
+ status && /* @__PURE__ */ jsx12("span", { className: cx("medo-avatar__status", `medo-avatar__status--${status}`), "aria-hidden": "true" })
488
+ ]
489
+ }
490
+ );
491
+ }
492
+ function AvatarGroup({
493
+ children,
494
+ max = 4,
495
+ size = "md",
496
+ onMorePress,
497
+ moreLabel = (n) => `${n} more`,
498
+ className,
499
+ ...props
500
+ }) {
501
+ const items = Children.toArray(children);
502
+ const visible = items.slice(0, max);
503
+ const hidden = items.length - visible.length;
504
+ const moreClass = cx("medo-avatar", `medo-avatar--${size}`, "medo-avatar--circle", "medo-avatar--neutral", "medo-avatar--more");
505
+ return /* @__PURE__ */ jsxs12("div", { role: "group", "aria-label": props["aria-label"], className: cx("medo-avatar-group", `medo-avatar-group--${size}`, className), children: [
506
+ /* @__PURE__ */ jsx12(AvatarSizeContext.Provider, { value: size, children: visible.map(
507
+ (child, i) => (
508
+ // The first avatar sits on top.
509
+ isValidElement(child) ? cloneElement(child, { style: { ...child.props.style, zIndex: visible.length - i } }) : child
510
+ )
511
+ ) }),
512
+ hidden > 0 && (onMorePress ? /* @__PURE__ */ jsxs12(AriaButton5, { className: moreClass, onPress: onMorePress, "aria-label": moreLabel(hidden), children: [
513
+ "+",
514
+ hidden
515
+ ] }) : /* @__PURE__ */ jsxs12("span", { className: moreClass, role: "img", "aria-label": moreLabel(hidden), children: [
516
+ "+",
517
+ hidden
518
+ ] }))
519
+ ] });
520
+ }
521
+
522
+ // src/toggle-button/ToggleButton.tsx
523
+ import { createContext as createContext4, useContext as useContext6, useEffect as useEffect3, useRef as useRef2 } from "react";
524
+ import {
525
+ SelectionIndicator,
526
+ Separator,
527
+ ToggleButton as AriaToggleButton,
528
+ ToggleButtonGroup as AriaToggleButtonGroup,
529
+ Toolbar as AriaToolbar
530
+ } from "react-aria-components";
531
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
532
+ var ToggleSizeContext = createContext4(null);
533
+ var SegmentedContext = createContext4(false);
534
+ function ToggleButton({ children, icon, size, tooltip, shortcut, className, ...props }) {
535
+ const groupSize = useContext6(ToggleSizeContext);
536
+ const segmented = useContext6(SegmentedContext);
537
+ const s = groupSize ?? size ?? "md";
538
+ const iconOnly = !children;
539
+ const button = /* @__PURE__ */ jsxs13(
540
+ AriaToggleButton,
541
+ {
542
+ ...props,
543
+ "aria-keyshortcuts": shortcut && resolveShortcut(shortcut),
544
+ className: cx("medo-button", `medo-button--${s}`, iconOnly && "medo-button--icon-only", "medo-toggle", className),
545
+ children: [
546
+ segmented && /* @__PURE__ */ jsx13(SelectionIndicator, { className: "medo-toggle__indicator" }),
547
+ icon && /* @__PURE__ */ jsx13("span", { className: "medo-button__icon", "aria-hidden": "true", children: icon }),
548
+ children
549
+ ]
550
+ }
551
+ );
552
+ if (!iconOnly || tooltip === false) return button;
553
+ return /* @__PURE__ */ jsxs13(TooltipTrigger, { children: [
554
+ button,
555
+ /* @__PURE__ */ jsx13(Tooltip, { shortcut: shortcut && formatShortcut(shortcut), children: tooltip ?? props["aria-label"] })
556
+ ] });
557
+ }
558
+ function ToggleButtonGroup({
559
+ variant = "segmented",
560
+ size = "md",
561
+ fullWidth,
562
+ selectionMode,
563
+ disallowEmptySelection,
564
+ children,
565
+ className,
566
+ ...props
567
+ }) {
568
+ const segmented = variant === "segmented";
569
+ const ref = useRef2(null);
570
+ useEffect3(() => {
571
+ const el = ref.current;
572
+ if (!el) return;
573
+ const fix = () => {
574
+ if (el.getAttribute("role") === "group" && el.hasAttribute("aria-orientation")) el.removeAttribute("aria-orientation");
575
+ };
576
+ fix();
577
+ const observer = new MutationObserver(fix);
578
+ observer.observe(el, { attributes: true, attributeFilter: ["role", "aria-orientation"] });
579
+ return () => observer.disconnect();
580
+ }, []);
581
+ const itemSize = segmented ? size === "lg" ? "md" : "sm" : size;
582
+ return /* @__PURE__ */ jsx13(
583
+ AriaToggleButtonGroup,
584
+ {
585
+ ...props,
586
+ ref,
587
+ selectionMode: segmented ? "single" : selectionMode ?? "multiple",
588
+ disallowEmptySelection: segmented || disallowEmptySelection,
589
+ className: cx("medo-toggle-group", `medo-toggle-group--${variant}`, `medo-toggle-group--${size}`, segmented && fullWidth && "medo-toggle-group--full", className),
590
+ children: /* @__PURE__ */ jsx13(SegmentedContext.Provider, { value: segmented, children: /* @__PURE__ */ jsx13(ToggleSizeContext.Provider, { value: itemSize, children }) })
591
+ }
592
+ );
593
+ }
594
+ function Toolbar({ className, ...props }) {
595
+ return /* @__PURE__ */ jsx13(AriaToolbar, { ...props, className: cx("medo-toolbar", className) });
596
+ }
597
+ function ToolbarSeparator() {
598
+ return /* @__PURE__ */ jsx13(Separator, { orientation: "vertical", className: "medo-toolbar__separator" });
599
+ }
600
+
601
+ // src/listbox/ListBox.tsx
602
+ import { createContext as createContext5, useContext as useContext7 } from "react";
603
+ import {
604
+ Collection,
605
+ ComboBoxStateContext,
606
+ Header,
607
+ ListBox as AriaListBox,
608
+ ListBoxItem,
609
+ ListBoxSection as AriaListBoxSection,
610
+ Text as Text6
611
+ } from "react-aria-components";
612
+ import { Check as Check4 } from "lucide-react";
613
+
614
+ // src/utils/highlight.tsx
615
+ import { jsx as jsx14 } from "react/jsx-runtime";
616
+ var plain = (text) => text.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLocaleLowerCase();
617
+ function matchParts(text, query, all = false) {
618
+ const q = plain(query.trim());
619
+ if (!q) return [{ text, match: false }];
620
+ const chars = [...text];
621
+ const map = [];
622
+ let flat = "";
623
+ chars.forEach((char, i) => {
624
+ const p = plain(char);
625
+ flat += p;
626
+ for (let k = 0; k < p.length; k++) map.push(i);
627
+ });
628
+ const parts = [];
629
+ let last = 0;
630
+ let from = 0;
631
+ while (from <= flat.length - q.length) {
632
+ const at = flat.indexOf(q, from);
633
+ if (at < 0) break;
634
+ const start = map[at];
635
+ const end = map[at + q.length - 1] + 1;
636
+ if (start > last) parts.push({ text: chars.slice(last, start).join(""), match: false });
637
+ parts.push({ text: chars.slice(start, end).join(""), match: true });
638
+ last = end;
639
+ from = at + q.length;
640
+ if (!all) break;
641
+ }
642
+ if (last < chars.length) parts.push({ text: chars.slice(last).join(""), match: false });
643
+ return parts;
644
+ }
645
+ function Highlight({
646
+ text,
647
+ query,
648
+ all = false,
649
+ as: Tag2 = "mark",
650
+ className
651
+ }) {
652
+ if (!query?.trim()) return text;
653
+ return matchParts(text, query, all).map(
654
+ (part, i) => part.match ? /* @__PURE__ */ jsx14(Tag2, { className, children: part.text }, i) : part.text
655
+ );
656
+ }
657
+
658
+ // src/listbox/ListBox.tsx
659
+ import { Fragment as Fragment7, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
660
+ var ListBoxSizeContext = createContext5("md");
661
+ function ListBox({ size = "md", className, ...props }) {
662
+ return /* @__PURE__ */ jsx15(ListBoxSizeContext.Provider, { value: size, children: /* @__PURE__ */ jsx15(AriaListBox, { ...props, className: cx("medo-listbox", `medo-listbox--${size}`, className) }) });
663
+ }
664
+ function Option({ children, description, icon, textValue, ...props }) {
665
+ const size = useContext7(ListBoxSizeContext);
666
+ return /* @__PURE__ */ jsx15(ListBoxItem, { ...props, textValue: textValue ?? (typeof children === "string" ? children : void 0), className: "medo-option", children: ({ isSelected, selectionMode }) => /* @__PURE__ */ jsxs14(Fragment7, { children: [
667
+ selectionMode === "multiple" && /* @__PURE__ */ jsx15(
668
+ "span",
669
+ {
670
+ className: cx("medo-choice", "medo-checkbox", `medo-choice--${size}`, "medo-option__checkbox"),
671
+ "data-selected": isSelected || void 0,
672
+ "aria-hidden": "true",
673
+ children: /* @__PURE__ */ jsx15("span", { className: "medo-choice__control", children: isSelected && /* @__PURE__ */ jsx15(Check4, {}) })
674
+ }
675
+ ),
676
+ icon && /* @__PURE__ */ jsx15("span", { className: "medo-option__icon", "aria-hidden": "true", children: icon }),
677
+ /* @__PURE__ */ jsxs14("span", { className: "medo-option__content", children: [
678
+ /* @__PURE__ */ jsx15(Text6, { slot: "label", className: "medo-option__label", children: /* @__PURE__ */ jsx15(Highlight2, { children }) }),
679
+ description && /* @__PURE__ */ jsx15(Text6, { slot: "description", className: "medo-option__description", children: description })
680
+ ] }),
681
+ selectionMode !== "multiple" && isSelected && /* @__PURE__ */ jsx15("span", { className: "medo-option__check", "aria-hidden": "true", children: /* @__PURE__ */ jsx15(Check4, {}) })
682
+ ] }) });
683
+ }
684
+ function ListBoxSection({ title, children, items, id, ...props }) {
685
+ return /* @__PURE__ */ jsxs14(AriaListBoxSection, { id, "aria-label": props["aria-label"], className: "medo-listbox__section", children: [
686
+ title && /* @__PURE__ */ jsx15(Header, { className: "medo-listbox__header", children: title }),
687
+ typeof children === "function" ? /* @__PURE__ */ jsx15(Collection, { items, children }) : children
688
+ ] });
689
+ }
690
+ function ListBoxMessage({ children }) {
691
+ return /* @__PURE__ */ jsx15("div", { className: "medo-listbox__message", role: "status", children });
692
+ }
693
+ var OptionHighlightContext = createContext5(null);
694
+ function Highlight2({ children }) {
695
+ const combo = useContext7(ComboBoxStateContext);
696
+ const external = useContext7(OptionHighlightContext);
697
+ let query = (external ?? combo?.inputValue ?? "").trim();
698
+ if (combo?.selectedItems.some((item) => item?.textValue === query)) query = "";
699
+ if (!query || typeof children !== "string") return /* @__PURE__ */ jsx15(Fragment7, { children });
700
+ return /* @__PURE__ */ jsx15(Highlight, { text: children, query, as: "span", className: "medo-option__match" });
701
+ }
702
+
703
+ // src/select/Select.tsx
704
+ import { useContext as useContext9, useEffect as useEffect4, useLayoutEffect as useLayoutEffect2, useRef as useRef4, useState as useState5 } from "react";
705
+ import {
706
+ Button as AriaButton6,
707
+ Popover as Popover2,
708
+ Select as AriaSelect,
709
+ SelectStateContext,
710
+ SelectValue
711
+ } from "react-aria-components";
712
+ import { ChevronDown, X as X4 } from "lucide-react";
713
+
714
+ // src/bottom-sheet/BottomSheet.tsx
715
+ import { useContext as useContext8, useId as useId2, useRef as useRef3 } from "react";
716
+ import { Dialog, Heading, Modal, ModalOverlay, OverlayTriggerStateContext, Popover } from "react-aria-components";
717
+ import { ChevronLeft, X as X3 } from "lucide-react";
718
+ import { Fragment as Fragment8, jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
719
+ var CLOSE_DISTANCE = 0.25;
720
+ var CLOSE_VELOCITY = 0.5;
721
+ function BottomSheet({
722
+ title,
723
+ children,
724
+ footer,
725
+ height = "content",
726
+ closeLabel = "Close",
727
+ onBack,
728
+ backLabel = "Back",
729
+ isDismissable = true,
730
+ className,
731
+ ...props
732
+ }) {
733
+ return /* @__PURE__ */ jsx16(ModalOverlay, { ...props, isDismissable, className: "medo-sheet-overlay", children: /* @__PURE__ */ jsx16(Modal, { className: cx("medo-sheet", height === "full" && "medo-sheet--full", className), children: /* @__PURE__ */ jsx16(Dialog, { className: "medo-sheet__dialog", children: ({ close }) => /* @__PURE__ */ jsxs15(Fragment8, { children: [
734
+ /* @__PURE__ */ jsxs15(SwipeArea, { onClose: close, children: [
735
+ /* @__PURE__ */ jsx16("span", { className: "medo-sheet__handle", "aria-hidden": "true" }),
736
+ /* @__PURE__ */ jsxs15("div", { className: "medo-sheet__header", children: [
737
+ onBack && /* @__PURE__ */ jsx16(IconButton, { variant: "ghost", size: "lg", icon: /* @__PURE__ */ jsx16(ChevronLeft, {}), "aria-label": backLabel, tooltip: false, onPress: onBack }),
738
+ /* @__PURE__ */ jsx16(Heading, { slot: "title", className: "medo-sheet__title", children: title }),
739
+ /* @__PURE__ */ jsx16(IconButton, { variant: "ghost", size: "lg", icon: /* @__PURE__ */ jsx16(X3, {}), "aria-label": closeLabel, tooltip: false, onPress: close })
740
+ ] })
741
+ ] }),
742
+ /* @__PURE__ */ jsx16("div", { className: "medo-sheet__content", children }),
743
+ footer && /* @__PURE__ */ jsx16("div", { className: "medo-sheet__footer", children: typeof footer === "function" ? footer(close) : footer })
744
+ ] }) }) }) });
745
+ }
746
+ function SwipeArea({ onClose, children }) {
747
+ const drag = useRef3(null);
748
+ const onPointerDown = (e) => {
749
+ if (e.target.closest("button")) return;
750
+ const sheet = e.currentTarget.closest(".medo-sheet");
751
+ if (!sheet) return;
752
+ drag.current = { y: e.clientY, t: e.timeStamp, sheet };
753
+ e.currentTarget.setPointerCapture(e.pointerId);
754
+ sheet.setAttribute("data-dragging", "");
755
+ };
756
+ const onPointerMove = (e) => {
757
+ if (!drag.current) return;
758
+ const dy = Math.max(0, e.clientY - drag.current.y);
759
+ drag.current.sheet.style.transform = `translateY(${dy}px)`;
760
+ };
761
+ const end = (e) => {
762
+ const d = drag.current;
763
+ if (!d) return;
764
+ drag.current = null;
765
+ const dy = Math.max(0, e.clientY - d.y);
766
+ const velocity = dy / Math.max(1, e.timeStamp - d.t);
767
+ d.sheet.removeAttribute("data-dragging");
768
+ d.sheet.style.transform = "";
769
+ if (dy > d.sheet.offsetHeight * CLOSE_DISTANCE || velocity > CLOSE_VELOCITY && dy > 24) onClose();
770
+ };
771
+ return /* @__PURE__ */ jsx16("div", { className: "medo-sheet__grab", onPointerDown, onPointerMove, onPointerUp: end, onPointerCancel: end, children });
772
+ }
773
+ function BottomSheetPopover({
774
+ title,
775
+ children,
776
+ footer,
777
+ height = "content",
778
+ closeLabel = "Close"
779
+ }) {
780
+ return /* @__PURE__ */ jsx16(Popover, { className: "medo-sheet-overlay medo-sheet-overlay--popover", offset: 0, containerPadding: 0, children: /* @__PURE__ */ jsx16(SheetPopoverBody, { title, footer, height, closeLabel, children }) });
781
+ }
782
+ function SheetPopoverBody({ title, children, footer, height, closeLabel }) {
783
+ const state = useContext8(OverlayTriggerStateContext);
784
+ const titleId = useId2();
785
+ const close = () => state?.close();
786
+ return (
787
+ // A tap on the scrim (outside the sheet) closes it.
788
+ /* @__PURE__ */ jsx16("div", { className: "medo-sheet-scrim", onClick: (e) => e.target === e.currentTarget && close(), children: /* @__PURE__ */ jsxs15("section", { className: cx("medo-sheet", height === "full" && "medo-sheet--full"), "aria-labelledby": titleId, children: [
789
+ /* @__PURE__ */ jsxs15(SwipeArea, { onClose: close, children: [
790
+ /* @__PURE__ */ jsx16("span", { className: "medo-sheet__handle", "aria-hidden": "true" }),
791
+ /* @__PURE__ */ jsxs15("div", { className: "medo-sheet__header", children: [
792
+ /* @__PURE__ */ jsx16("h2", { id: titleId, className: "medo-sheet__title", children: title }),
793
+ /* @__PURE__ */ jsx16(IconButton, { slot: null, variant: "ghost", size: "lg", icon: /* @__PURE__ */ jsx16(X3, {}), "aria-label": closeLabel ?? "Close", tooltip: false, onPress: close })
794
+ ] })
795
+ ] }),
796
+ /* @__PURE__ */ jsx16("div", { className: "medo-sheet__content", children }),
797
+ footer && /* @__PURE__ */ jsx16("div", { className: "medo-sheet__footer", children: typeof footer === "function" ? footer(close) : footer })
798
+ ] }) })
799
+ );
800
+ }
801
+
802
+ // src/utils/useFitCount.ts
803
+ import { useLayoutEffect, useState as useState4 } from "react";
804
+ function useFitCount(rowRef, measureRef, key, total, reserve = 0) {
805
+ const [visible, setVisible] = useState4(total);
806
+ useLayoutEffect(() => {
807
+ const row = rowRef.current;
808
+ const measure = measureRef.current;
809
+ if (!row || !measure) return;
810
+ const fit = () => {
811
+ const items = [...measure.children];
812
+ const plus = items.pop();
813
+ const gap = parseFloat(getComputedStyle(measure).columnGap) || 0;
814
+ const avail = row.clientWidth - reserve;
815
+ let used = 0;
816
+ let count = 0;
817
+ for (let i = 0; i < items.length; i++) {
818
+ const width = items[i].offsetWidth + (i ? gap : 0);
819
+ const plusRoom = i < items.length - 1 ? gap + (plus?.offsetWidth ?? 0) : 0;
820
+ if (used + width + plusRoom > avail) break;
821
+ used += width;
822
+ count = i + 1;
823
+ }
824
+ setVisible(Math.min(total, Math.max(1, count)));
825
+ };
826
+ fit();
827
+ const observer = new ResizeObserver(fit);
828
+ observer.observe(row);
829
+ return () => observer.disconnect();
830
+ }, [rowRef, measureRef, key, total, reserve]);
831
+ return Math.min(visible, total);
832
+ }
833
+
834
+ // src/utils/useMediaQuery.ts
835
+ import { useSyncExternalStore } from "react";
836
+ var MOBILE_QUERY = "(max-width: 639.98px)";
837
+ function useMediaQuery(query) {
838
+ return useSyncExternalStore(
839
+ (onChange) => {
840
+ const list = window.matchMedia(query);
841
+ list.addEventListener("change", onChange);
842
+ return () => list.removeEventListener("change", onChange);
843
+ },
844
+ () => window.matchMedia(query).matches,
845
+ () => false
846
+ );
847
+ }
848
+
849
+ // src/select/Select.tsx
850
+ import { Fragment as Fragment9, jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
851
+ function Select({
852
+ label,
853
+ description,
854
+ errorMessage,
855
+ placeholder = "Select\u2026",
856
+ size = "md",
857
+ leadingIcon,
858
+ clearable,
859
+ isReadOnly,
860
+ items,
861
+ children,
862
+ overlay = "auto",
863
+ commit = "live",
864
+ onApply,
865
+ resultLabel = () => "Show results",
866
+ className,
867
+ selectionMode,
868
+ ...props
869
+ }) {
870
+ const multiple = selectionMode === "multiple";
871
+ const mobile = useMediaQuery(MOBILE_QUERY);
872
+ const mode = overlay === "auto" ? mobile ? multiple ? "sheet" : "native" : "popover" : overlay === "native" && multiple ? "sheet" : overlay;
873
+ const [hasValue, setHasValue] = useState5(false);
874
+ const fieldRef = useRef4(null);
875
+ return /* @__PURE__ */ jsxs16(
876
+ AriaSelect,
877
+ {
878
+ ...props,
879
+ selectionMode,
880
+ placeholder,
881
+ isOpen: isReadOnly ? false : props.isOpen,
882
+ "data-readonly": isReadOnly || void 0,
883
+ className: cx(
884
+ "medo-field",
885
+ "medo-select",
886
+ multiple && hasValue && "medo-select--tags",
887
+ (multiple || clearable) && hasValue && !isReadOnly && !props.isDisabled && "medo-select--clearable",
888
+ className
889
+ ),
890
+ children: [
891
+ label && /* @__PURE__ */ jsx17(FieldLabel, { isRequired: props.isRequired, children: label }),
892
+ /* @__PURE__ */ jsxs16("div", { ref: fieldRef, className: cx("medo-input", `medo-input--${size}`, "medo-select__field"), children: [
893
+ /* @__PURE__ */ jsx17(ReadOnlyKeys, { isReadOnly, children: /* @__PURE__ */ jsxs16(AriaButton6, { className: "medo-select__trigger", excludeFromTabOrder: mode === "native", "aria-hidden": mode === "native" || void 0, children: [
894
+ leadingIcon && /* @__PURE__ */ jsx17("span", { className: "medo-input__icon", "aria-hidden": "true", children: leadingIcon }),
895
+ /* @__PURE__ */ jsx17(SelectValue, { className: "medo-select__value", children: ({ defaultChildren, isPlaceholder, selectedText }) => /* @__PURE__ */ jsxs16(Fragment9, { children: [
896
+ isPlaceholder ? defaultChildren : multiple ? /* @__PURE__ */ jsx17(SelectedTags, { size }) : selectedText,
897
+ isReadOnly && /* @__PURE__ */ jsx17("span", { className: "medo-visually-hidden", children: ", read only" })
898
+ ] }) }),
899
+ !isReadOnly && /* @__PURE__ */ jsx17("span", { className: "medo-select__chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx17(ChevronDown, {}) })
900
+ ] }) }),
901
+ mode === "native" && !isReadOnly && /* @__PURE__ */ jsx17(NativePicker, { label: label ?? props["aria-label"], placeholder, isRequired: props.isRequired }),
902
+ /* @__PURE__ */ jsx17(ClearButton, { enabled: (multiple || !!clearable) && !isReadOnly && !props.isDisabled, onHasValue: setHasValue })
903
+ ] }),
904
+ /* @__PURE__ */ jsx17(FieldFooter, { description, errorMessage }),
905
+ commit === "onClose" && onApply && /* @__PURE__ */ jsx17(ApplyOnClose, { onApply }),
906
+ mode === "sheet" ? /* @__PURE__ */ jsx17(
907
+ BottomSheetPopover,
908
+ {
909
+ title: label ?? props["aria-label"],
910
+ footer: multiple ? /* @__PURE__ */ jsx17(SheetFooter, { commit, resultLabel }) : void 0,
911
+ children: /* @__PURE__ */ jsx17(ListBox, { size: "lg", items, children })
912
+ }
913
+ ) : /* @__PURE__ */ jsx17(Popover2, { triggerRef: fieldRef, offset: 4, className: "medo-listbox-popover", children: /* @__PURE__ */ jsx17(ListBox, { size: size === "lg" ? "lg" : "md", items, children }) })
914
+ ]
915
+ }
916
+ );
917
+ }
918
+ function ReadOnlyKeys({ isReadOnly, children }) {
919
+ if (!isReadOnly) return /* @__PURE__ */ jsx17(Fragment9, { children });
920
+ const block = (e) => {
921
+ if (e.key !== "Tab") e.stopPropagation();
922
+ };
923
+ return /* @__PURE__ */ jsx17("div", { style: { display: "contents" }, onKeyDownCapture: block, children });
924
+ }
925
+ function ClearButton({ enabled, onHasValue }) {
926
+ const state = useContext9(SelectStateContext);
927
+ const hasValue = !!state && (Array.isArray(state.value) ? state.value.length > 0 : state.value != null);
928
+ useLayoutEffect2(() => onHasValue(hasValue), [hasValue, onHasValue]);
929
+ const ref = useRef4(null);
930
+ if (!enabled || !hasValue || !state) return null;
931
+ return /* @__PURE__ */ jsx17("span", { className: "medo-select__clear", ref, children: /* @__PURE__ */ jsx17(
932
+ InputAction,
933
+ {
934
+ slot: null,
935
+ "aria-label": Array.isArray(state.value) ? "Clear all" : "Clear selection",
936
+ icon: /* @__PURE__ */ jsx17(X4, {}),
937
+ onPress: () => {
938
+ state.setValue(Array.isArray(state.value) ? [] : null);
939
+ ref.current?.parentElement?.querySelector(".medo-select__trigger")?.focus();
940
+ }
941
+ }
942
+ ) });
943
+ }
944
+ function SelectedTags({ size }) {
945
+ const state = useContext9(SelectStateContext);
946
+ const labels = state?.selectedItems.map((item) => item.textValue) ?? [];
947
+ const key = labels.join("\0");
948
+ const rowRef = useRef4(null);
949
+ const measureRef = useRef4(null);
950
+ const visible = useFitCount(rowRef, measureRef, key, labels.length);
951
+ const tagClass = cx("medo-tag", size === "lg" ? "medo-tag--md" : "medo-tag--sm");
952
+ const hidden = labels.length - visible;
953
+ return /* @__PURE__ */ jsxs16("span", { className: "medo-select__tags", ref: rowRef, children: [
954
+ /* @__PURE__ */ jsx17("span", { className: "medo-visually-hidden", children: labels.join(", ") }),
955
+ /* @__PURE__ */ jsxs16("span", { className: "medo-select__measure", ref: measureRef, "aria-hidden": "true", children: [
956
+ labels.map((label, i) => /* @__PURE__ */ jsx17("span", { className: tagClass, children: label }, i)),
957
+ /* @__PURE__ */ jsxs16("span", { className: tagClass, children: [
958
+ "+",
959
+ labels.length
960
+ ] })
961
+ ] }),
962
+ labels.slice(0, visible).map((label, i) => /* @__PURE__ */ jsx17("span", { className: tagClass, "aria-hidden": "true", children: /* @__PURE__ */ jsx17("span", { className: "medo-select__tag-label", children: label }) }, i)),
963
+ hidden > 0 && /* @__PURE__ */ jsxs16("span", { className: tagClass, "aria-hidden": "true", children: [
964
+ "+",
965
+ hidden
966
+ ] })
967
+ ] });
968
+ }
969
+ function NativePicker({ label, placeholder, isRequired }) {
970
+ const state = useContext9(SelectStateContext);
971
+ if (!state) return null;
972
+ const keys = /* @__PURE__ */ new Map();
973
+ const option = (key) => {
974
+ const node = state.collection.getItem(key);
975
+ if (!node || node.type !== "item") return null;
976
+ keys.set(String(key), key);
977
+ return /* @__PURE__ */ jsx17("option", { value: String(key), disabled: state.selectionManager.isDisabled(key), children: node.textValue }, String(key));
978
+ };
979
+ const value = state.value == null || Array.isArray(state.value) ? "" : String(state.value);
980
+ return /* @__PURE__ */ jsxs16(
981
+ "select",
982
+ {
983
+ className: "medo-select__native",
984
+ "aria-label": typeof label === "string" ? label : void 0,
985
+ required: isRequired,
986
+ value,
987
+ onChange: (e) => state.setValue(e.target.value === "" ? null : keys.get(e.target.value) ?? e.target.value),
988
+ children: [
989
+ /* @__PURE__ */ jsx17("option", { value: "", disabled: isRequired, children: placeholder }),
990
+ [...state.collection].map(
991
+ (node) => node.type === "section" ? /* @__PURE__ */ jsx17("optgroup", { label: node.textValue || node["aria-label"] || "", children: [...state.collection.getChildren(node.key)].map((child) => option(child.key)) }, String(node.key)) : option(node.key)
992
+ )
993
+ ]
994
+ }
995
+ );
996
+ }
997
+ function SheetFooter({ commit, resultLabel }) {
998
+ const state = useContext9(SelectStateContext);
999
+ if (!state) return null;
1000
+ const count = Array.isArray(state.value) ? state.value.length : state.value == null ? 0 : 1;
1001
+ return commit === "onClose" ? /* @__PURE__ */ jsxs16(Fragment9, { children: [
1002
+ /* @__PURE__ */ jsx17(Button, { size: "lg", onPress: () => state.close(), children: resultLabel(state.value) }),
1003
+ /* @__PURE__ */ jsx17(Button, { size: "lg", variant: "secondary", onPress: () => state.setValue([]), isDisabled: count === 0, children: "Reset" })
1004
+ ] }) : /* @__PURE__ */ jsxs16(Fragment9, { children: [
1005
+ /* @__PURE__ */ jsxs16("p", { className: "medo-sheet__count", "aria-live": "polite", children: [
1006
+ count,
1007
+ " selected"
1008
+ ] }),
1009
+ /* @__PURE__ */ jsx17(Button, { size: "lg", onPress: () => state.close(), children: "Done" })
1010
+ ] });
1011
+ }
1012
+ function ApplyOnClose({ onApply }) {
1013
+ const state = useContext9(SelectStateContext);
1014
+ const opened = useRef4(null);
1015
+ const isOpen = !!state?.isOpen;
1016
+ useEffect4(() => {
1017
+ if (!state) return;
1018
+ const snapshot = JSON.stringify(state.value);
1019
+ if (isOpen) opened.current = snapshot;
1020
+ else if (opened.current !== null) {
1021
+ if (opened.current !== snapshot) onApply(state.value);
1022
+ opened.current = null;
1023
+ }
1024
+ }, [isOpen]);
1025
+ return null;
1026
+ }
1027
+
1028
+ // src/menu/Menu.tsx
1029
+ import { createContext as createContext6, useContext as useContext10, useState as useState6 } from "react";
1030
+ import {
1031
+ Header as Header2,
1032
+ Keyboard,
1033
+ Menu as AriaMenu,
1034
+ MenuItem as AriaMenuItem,
1035
+ MenuSection as AriaMenuSection,
1036
+ MenuTrigger as AriaMenuTrigger,
1037
+ Popover as Popover3,
1038
+ SubmenuTrigger,
1039
+ Text as Text7
1040
+ } from "react-aria-components";
1041
+ import { ChevronRight, Ellipsis } from "lucide-react";
1042
+ import { Fragment as Fragment10, jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
1043
+ import { createElement } from "react";
1044
+ var MenuModeContext = createContext6({ size: "md" });
1045
+ function MenuTrigger(props) {
1046
+ return /* @__PURE__ */ jsx18(AriaMenuTrigger, { ...props });
1047
+ }
1048
+ function MenuButton({ "aria-label": label, size = "md" }) {
1049
+ return /* @__PURE__ */ jsx18(IconButton, { variant: "ghost", size, icon: /* @__PURE__ */ jsx18(Ellipsis, {}), "aria-label": label });
1050
+ }
1051
+ function Menu({ title, overlay = "auto", className, ...props }) {
1052
+ const mobile = useMediaQuery(MOBILE_QUERY);
1053
+ const sheet = overlay === "sheet" || overlay === "auto" && mobile;
1054
+ if (sheet) return /* @__PURE__ */ jsx18(SheetMenu, { title, className, ...props });
1055
+ return /* @__PURE__ */ jsx18(Popover3, { placement: "bottom end", offset: 4, className: "medo-menu-popover", children: /* @__PURE__ */ jsx18(MenuModeContext.Provider, { value: { size: "md" }, children: /* @__PURE__ */ jsx18(AriaMenu, { ...props, className: cx("medo-menu", "medo-menu--md", className) }) }) });
1056
+ }
1057
+ function SheetMenu({ title, className, ...props }) {
1058
+ const [view, setView] = useState6(null);
1059
+ return /* @__PURE__ */ jsx18(
1060
+ BottomSheet,
1061
+ {
1062
+ title: view ? view.title : title ?? props["aria-label"],
1063
+ onBack: view ? () => setView(null) : void 0,
1064
+ onOpenChange: (open) => !open && setView(null),
1065
+ children: /* @__PURE__ */ jsx18(MenuModeContext.Provider, { value: { size: "lg", push: (t, c) => setView({ title: t, children: c }) }, children: view ? /* @__PURE__ */ jsx18(AriaMenu, { "aria-label": typeof view.title === "string" ? view.title : void 0, autoFocus: "first", className: cx("medo-menu", "medo-menu--lg", className), children: view.children }, "sub") : /* @__PURE__ */ createElement(AriaMenu, { ...props, key: "main", className: cx("medo-menu", "medo-menu--lg", className) }) })
1066
+ }
1067
+ );
1068
+ }
1069
+ function MenuItem({ children, icon, shortcut, variant = "default", textValue, ...props }) {
1070
+ const { size } = useContext10(MenuModeContext);
1071
+ return /* @__PURE__ */ jsx18(
1072
+ AriaMenuItem,
1073
+ {
1074
+ ...props,
1075
+ textValue: textValue ?? children,
1076
+ className: cx("medo-menu-item", variant === "danger" && "medo-menu-item--danger"),
1077
+ children: ({ hasSubmenu }) => /* @__PURE__ */ jsxs17(Fragment10, { children: [
1078
+ /* @__PURE__ */ jsx18("span", { className: "medo-menu-item__icon", "aria-hidden": "true", children: icon }),
1079
+ /* @__PURE__ */ jsx18(Text7, { slot: "label", className: "medo-menu-item__label", children }),
1080
+ shortcut && size === "md" && /* @__PURE__ */ jsx18(Keyboard, { className: "medo-menu-item__shortcut", children: formatShortcut(shortcut) }),
1081
+ hasSubmenu && /* @__PURE__ */ jsx18("span", { className: "medo-menu-item__chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx18(ChevronRight, {}) })
1082
+ ] })
1083
+ }
1084
+ );
1085
+ }
1086
+ function Submenu({ label, icon, children, isDisabled }) {
1087
+ const { size, push } = useContext10(MenuModeContext);
1088
+ if (push) {
1089
+ return /* @__PURE__ */ jsxs17(
1090
+ AriaMenuItem,
1091
+ {
1092
+ textValue: label,
1093
+ isDisabled,
1094
+ shouldCloseOnSelect: false,
1095
+ onAction: () => push(label, children),
1096
+ className: "medo-menu-item",
1097
+ children: [
1098
+ /* @__PURE__ */ jsx18("span", { className: "medo-menu-item__icon", "aria-hidden": "true", children: icon }),
1099
+ /* @__PURE__ */ jsx18(Text7, { slot: "label", className: "medo-menu-item__label", children: label }),
1100
+ /* @__PURE__ */ jsx18("span", { className: "medo-menu-item__chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx18(ChevronRight, {}) })
1101
+ ]
1102
+ }
1103
+ );
1104
+ }
1105
+ return /* @__PURE__ */ jsxs17(SubmenuTrigger, { children: [
1106
+ /* @__PURE__ */ jsx18(MenuItem, { icon, isDisabled, children: label }),
1107
+ /* @__PURE__ */ jsx18(Popover3, { placement: "end top", offset: 4, crossOffset: -5, className: "medo-menu-popover", children: /* @__PURE__ */ jsx18(AriaMenu, { "aria-label": label, className: cx("medo-menu", `medo-menu--${size}`), children }) })
1108
+ ] });
1109
+ }
1110
+ function MenuSection({ title, children, ...props }) {
1111
+ return /* @__PURE__ */ jsxs17(AriaMenuSection, { "aria-label": props["aria-label"], className: "medo-menu__section", children: [
1112
+ title && /* @__PURE__ */ jsx18(Header2, { className: "medo-listbox__header", children: title }),
1113
+ children
1114
+ ] });
1115
+ }
1116
+
1117
+ // src/dialog/Dialog.tsx
1118
+ import { useContext as useContext11, useEffect as useEffect5, useId as useId3, useLayoutEffect as useLayoutEffect3, useRef as useRef5, useState as useState7 } from "react";
1119
+ import { Dialog as AriaDialog, DialogContext, Heading as Heading2, Modal as Modal2, ModalOverlay as ModalOverlay2, OverlayTriggerStateContext as OverlayTriggerStateContext2, Provider } from "react-aria-components";
1120
+ import { CircleAlert as CircleAlert2, Info, TriangleAlert, X as X5 } from "lucide-react";
1121
+ import { DialogTrigger } from "react-aria-components";
1122
+ import { Fragment as Fragment11, jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
1123
+ function useOpenState({ isOpen, onOpenChange }) {
1124
+ const trigger = useContext11(OverlayTriggerStateContext2);
1125
+ const open = isOpen ?? trigger?.isOpen ?? false;
1126
+ const setOpen = (next) => {
1127
+ onOpenChange?.(next);
1128
+ if (isOpen === void 0) trigger?.setOpen(next);
1129
+ };
1130
+ return [open, setOpen];
1131
+ }
1132
+ function ActionButton({ action, close, size, variant }) {
1133
+ const [pending, setPending] = useState7(false);
1134
+ const run = async () => {
1135
+ const result = action.onAction?.();
1136
+ if (!(result instanceof Promise)) return close();
1137
+ setPending(true);
1138
+ try {
1139
+ await result;
1140
+ close();
1141
+ } finally {
1142
+ setPending(false);
1143
+ }
1144
+ };
1145
+ return /* @__PURE__ */ jsx19(
1146
+ Button,
1147
+ {
1148
+ variant: action.variant ?? variant,
1149
+ size,
1150
+ type: action.form ? "submit" : "button",
1151
+ form: action.form,
1152
+ isDisabled: action.isDisabled,
1153
+ isPending: action.isPending || pending,
1154
+ onPress: action.form ? void 0 : run,
1155
+ children: action.label
1156
+ }
1157
+ );
1158
+ }
1159
+ function Nested({ children }) {
1160
+ return /* @__PURE__ */ jsx19(
1161
+ Provider,
1162
+ {
1163
+ values: [
1164
+ [DialogContext, null],
1165
+ [OverlayTriggerStateContext2, null]
1166
+ ],
1167
+ children
1168
+ }
1169
+ );
1170
+ }
1171
+ function useDirtyGuard({ isDirty = false, discardText, ...openProps }) {
1172
+ const [open, setOpen] = useOpenState(openProps);
1173
+ const [confirm, setConfirm] = useState7(false);
1174
+ const onOpenChange = (next) => {
1175
+ if (next || !isDirty) setOpen(next);
1176
+ else setConfirm(true);
1177
+ };
1178
+ const discard = { ...DISCARD, ...discardText };
1179
+ const confirmDiscard = /* @__PURE__ */ jsx19(Nested, { children: /* @__PURE__ */ jsx19(
1180
+ AlertDialog,
1181
+ {
1182
+ tone: "warning",
1183
+ isOpen: confirm,
1184
+ onOpenChange: setConfirm,
1185
+ title: discard.title,
1186
+ actionLabel: discard.actionLabel,
1187
+ cancelLabel: discard.cancelLabel,
1188
+ onAction: () => setOpen(false),
1189
+ children: discard.description
1190
+ }
1191
+ ) });
1192
+ const forceClose = () => setOpen(false);
1193
+ return { open, onOpenChange, confirmDiscard, forceClose };
1194
+ }
1195
+ var DISCARD = {
1196
+ title: "Discard changes?",
1197
+ description: "Your changes will be lost.",
1198
+ actionLabel: "Discard",
1199
+ cancelLabel: "Keep editing"
1200
+ };
1201
+ function Dialog2({
1202
+ title,
1203
+ description,
1204
+ children,
1205
+ size = "md",
1206
+ primaryAction,
1207
+ cancelLabel = "Cancel",
1208
+ closeLabel = "Close",
1209
+ showClose = true,
1210
+ isDirty = false,
1211
+ discardText,
1212
+ overlay = "auto",
1213
+ mobile = "fullscreen",
1214
+ className,
1215
+ ...openProps
1216
+ }) {
1217
+ const { open, onOpenChange, confirmDiscard, forceClose } = useDirtyGuard({ isDirty, discardText, ...openProps });
1218
+ const isMobile = useMediaQuery(MOBILE_QUERY);
1219
+ const layout = overlay === "auto" ? isMobile ? mobile : "dialog" : overlay;
1220
+ const content = (close) => typeof children === "function" ? children(close) : children;
1221
+ if (layout === "sheet") {
1222
+ return /* @__PURE__ */ jsxs18(
1223
+ BottomSheet,
1224
+ {
1225
+ title,
1226
+ closeLabel,
1227
+ isOpen: open,
1228
+ onOpenChange,
1229
+ isDismissable: !isDirty,
1230
+ className,
1231
+ footer: primaryAction && ((close) => /* @__PURE__ */ jsxs18(ButtonGroup, { children: [
1232
+ /* @__PURE__ */ jsx19(Button, { variant: "secondary", size: "lg", onPress: close, children: cancelLabel }),
1233
+ /* @__PURE__ */ jsx19(ActionButton, { action: primaryAction, close: forceClose, size: "lg", variant: "primary" })
1234
+ ] })),
1235
+ children: [
1236
+ /* @__PURE__ */ jsx19(SheetContent, { description, children: content(forceClose) }),
1237
+ confirmDiscard
1238
+ ]
1239
+ }
1240
+ );
1241
+ }
1242
+ const fullscreen = layout === "fullscreen";
1243
+ return /* @__PURE__ */ jsx19(
1244
+ ModalOverlay2,
1245
+ {
1246
+ isOpen: open,
1247
+ onOpenChange,
1248
+ isDismissable: !isDirty,
1249
+ className: cx("medo-dialog-overlay", fullscreen && "medo-dialog-overlay--fullscreen"),
1250
+ children: /* @__PURE__ */ jsx19(Modal2, { className: cx("medo-dialog", fullscreen ? "medo-dialog--fullscreen" : `medo-dialog--${size}`, className), children: /* @__PURE__ */ jsx19(
1251
+ DialogFrame,
1252
+ {
1253
+ title,
1254
+ description,
1255
+ fullscreen,
1256
+ primaryAction,
1257
+ cancelLabel,
1258
+ closeLabel,
1259
+ showClose,
1260
+ nested: confirmDiscard,
1261
+ forceClose,
1262
+ children: content
1263
+ }
1264
+ ) })
1265
+ }
1266
+ );
1267
+ }
1268
+ function DialogFrame({ title, description, fullscreen, primaryAction, cancelLabel, closeLabel, showClose, nested, forceClose, children }) {
1269
+ const descriptionId = useId3();
1270
+ const ref = useRef5(null);
1271
+ const [scrolls, setScrolls] = useState7(false);
1272
+ useInitialFocus(ref);
1273
+ return /* @__PURE__ */ jsx19(AriaDialog, { ref, className: "medo-dialog__dialog", "aria-describedby": description ? descriptionId : void 0, "data-scrolled": scrolls || void 0, children: ({ close }) => fullscreen ? /* @__PURE__ */ jsxs18(Fragment11, { children: [
1274
+ /* @__PURE__ */ jsxs18("div", { className: "medo-dialog__bar", children: [
1275
+ /* @__PURE__ */ jsx19(Button, { variant: "ghost", size: "md", onPress: close, children: primaryAction ? cancelLabel : closeLabel }),
1276
+ /* @__PURE__ */ jsx19(Heading2, { slot: "title", className: "medo-dialog__bar-title", children: title }),
1277
+ primaryAction ? /* @__PURE__ */ jsx19(ActionButton, { action: primaryAction, close: forceClose, size: "md", variant: "primary" }) : /* @__PURE__ */ jsx19("span", {})
1278
+ ] }),
1279
+ /* @__PURE__ */ jsxs18(DialogBody, { onScrollsChange: setScrolls, children: [
1280
+ description && /* @__PURE__ */ jsx19("p", { id: descriptionId, className: "medo-dialog__description", children: description }),
1281
+ children(forceClose)
1282
+ ] }),
1283
+ nested
1284
+ ] }) : /* @__PURE__ */ jsxs18(Fragment11, { children: [
1285
+ /* @__PURE__ */ jsxs18("div", { className: "medo-dialog__header", children: [
1286
+ /* @__PURE__ */ jsxs18("div", { className: "medo-dialog__heading", children: [
1287
+ /* @__PURE__ */ jsx19(Heading2, { slot: "title", className: "medo-dialog__title", children: title }),
1288
+ description && /* @__PURE__ */ jsx19("p", { id: descriptionId, className: "medo-dialog__description", children: description })
1289
+ ] }),
1290
+ showClose && /* @__PURE__ */ jsx19(IconButton, { variant: "ghost", size: "sm", icon: /* @__PURE__ */ jsx19(X5, {}), "aria-label": closeLabel, tooltip: false, onPress: close })
1291
+ ] }),
1292
+ /* @__PURE__ */ jsx19(DialogBody, { onScrollsChange: setScrolls, children: children(forceClose) }),
1293
+ primaryAction && /* @__PURE__ */ jsx19("div", { className: "medo-dialog__footer", children: /* @__PURE__ */ jsxs18(ButtonGroup, { children: [
1294
+ /* @__PURE__ */ jsx19(Button, { variant: "secondary", onPress: close, children: cancelLabel }),
1295
+ /* @__PURE__ */ jsx19(ActionButton, { action: primaryAction, close: forceClose, size: "md", variant: "primary" })
1296
+ ] }) }),
1297
+ nested
1298
+ ] }) });
1299
+ }
1300
+ function DialogBody({ children, onScrollsChange }) {
1301
+ const ref = useRef5(null);
1302
+ const [scrolls, setScrolls] = useState7(false);
1303
+ useLayoutEffect3(() => {
1304
+ const body = ref.current;
1305
+ if (!body) return;
1306
+ const check = () => setScrolls(body.scrollHeight > body.clientHeight + 1);
1307
+ const observer = new ResizeObserver(check);
1308
+ observer.observe(body);
1309
+ if (body.firstElementChild) observer.observe(body.firstElementChild);
1310
+ check();
1311
+ return () => observer.disconnect();
1312
+ }, []);
1313
+ useEffect5(() => onScrollsChange(scrolls), [scrolls, onScrollsChange]);
1314
+ return /* @__PURE__ */ jsx19("div", { ref, className: "medo-dialog__body", tabIndex: scrolls ? 0 : void 0, children: /* @__PURE__ */ jsx19("div", { className: "medo-dialog__body-inner", children }) });
1315
+ }
1316
+ function SheetContent({ description, children }) {
1317
+ return /* @__PURE__ */ jsxs18("div", { className: "medo-dialog__sheet-body", children: [
1318
+ description && /* @__PURE__ */ jsx19("p", { className: "medo-dialog__description", children: description }),
1319
+ children
1320
+ ] });
1321
+ }
1322
+ var FIELDS = 'input, textarea, select, [role="combobox"], button[aria-haspopup="listbox"]';
1323
+ function useInitialFocus(ref) {
1324
+ useEffect5(() => {
1325
+ const root = ref.current;
1326
+ if (!root) return;
1327
+ const usable = (el) => el.tabIndex >= 0 && !el.closest('[aria-hidden="true"], [inert]') && !el.matches(":disabled");
1328
+ const field = [...root.querySelectorAll(FIELDS)].find(usable);
1329
+ (field ?? [...root.querySelectorAll("button")].find(usable))?.focus();
1330
+ }, [ref]);
1331
+ }
1332
+ var TONE_ICONS = {
1333
+ danger: CircleAlert2,
1334
+ warning: TriangleAlert,
1335
+ info: Info
1336
+ };
1337
+ function AlertDialog({ tone = "danger", title, children, actionLabel, onAction, cancelLabel = "Cancel", className, ...openProps }) {
1338
+ const [open, setOpen] = useOpenState(openProps);
1339
+ const isMobile = useMediaQuery(MOBILE_QUERY);
1340
+ const descriptionId = useId3();
1341
+ const Icon = TONE_ICONS[tone];
1342
+ const size = isMobile ? "lg" : "md";
1343
+ return /* @__PURE__ */ jsx19(ModalOverlay2, { isOpen: open, onOpenChange: setOpen, isDismissable: false, className: "medo-dialog-overlay", children: /* @__PURE__ */ jsx19(Modal2, { className: cx("medo-dialog", "medo-alert-dialog", `medo-alert-dialog--${tone}`, className), children: /* @__PURE__ */ jsx19(AriaDialog, { role: "alertdialog", "aria-describedby": children ? descriptionId : void 0, className: "medo-dialog__dialog", children: ({ close }) => /* @__PURE__ */ jsxs18(Fragment11, { children: [
1344
+ /* @__PURE__ */ jsxs18("div", { className: "medo-alert-dialog__content", children: [
1345
+ /* @__PURE__ */ jsx19("span", { className: "medo-alert-dialog__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx19(Icon, {}) }),
1346
+ /* @__PURE__ */ jsxs18("div", { className: "medo-alert-dialog__text", children: [
1347
+ /* @__PURE__ */ jsx19(Heading2, { slot: "title", className: "medo-alert-dialog__title", children: title }),
1348
+ children && /* @__PURE__ */ jsx19("div", { id: descriptionId, className: "medo-alert-dialog__description", children })
1349
+ ] })
1350
+ ] }),
1351
+ /* @__PURE__ */ jsxs18(ButtonGroup, { children: [
1352
+ /* @__PURE__ */ jsx19(Button, { variant: "secondary", size, autoFocus: true, onPress: close, children: cancelLabel }),
1353
+ /* @__PURE__ */ jsx19(ActionButton, { action: { label: actionLabel, onAction }, close, size, variant: tone === "danger" ? "danger" : "primary" })
1354
+ ] })
1355
+ ] }) }) }) });
1356
+ }
1357
+
1358
+ // src/combobox/Combobox.tsx
1359
+ import { useContext as useContext12, useEffect as useEffect6, useId as useId4, useRef as useRef6, useState as useState8 } from "react";
1360
+ import {
1361
+ Autocomplete,
1362
+ Button as AriaButton7,
1363
+ Collection as Collection2,
1364
+ CollectionBuilder,
1365
+ ComboBox as AriaComboBox,
1366
+ ComboBoxStateContext as ComboBoxStateContext2,
1367
+ Group,
1368
+ Input,
1369
+ Popover as Popover4,
1370
+ TextField as AriaTextField2,
1371
+ useFilter
1372
+ } from "react-aria-components";
1373
+ import { ChevronDown as ChevronDown2, CircleAlert as CircleAlert3, Plus, Search, X as X6 } from "lucide-react";
1374
+ import { Fragment as Fragment12, jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
1375
+ var CREATE_KEY = "__medo_create__";
1376
+ function Combobox({
1377
+ label,
1378
+ description,
1379
+ errorMessage,
1380
+ placeholder = "Search\u2026",
1381
+ size = "md",
1382
+ leadingIcon,
1383
+ isLoading,
1384
+ noResultsMessage = (text) => text ? `No results for \u201E${text}\u201C` : "No results",
1385
+ onCreate,
1386
+ children,
1387
+ overlay = "auto",
1388
+ commit = "live",
1389
+ onApply,
1390
+ resultLabel = () => "Show results",
1391
+ className,
1392
+ selectionMode,
1393
+ value: valueProp,
1394
+ defaultValue,
1395
+ onChange,
1396
+ onInputChange,
1397
+ ...props
1398
+ }) {
1399
+ const multiple = selectionMode === "multiple";
1400
+ const groupRef = useRef6(null);
1401
+ const inputRef = useRef6(null);
1402
+ const entryRef = useRef6(null);
1403
+ const textRef = useRef6("");
1404
+ const [text, setText] = useState8("");
1405
+ const [exact, setExact] = useState8(false);
1406
+ const [announcement, setAnnouncement] = useState8("");
1407
+ const [inner, setInner] = useState8(defaultValue ?? (multiple ? [] : null));
1408
+ const value = valueProp !== void 0 ? valueProp : inner;
1409
+ const setValue = (next) => {
1410
+ if (valueProp === void 0) setInner(next);
1411
+ onChange?.(next);
1412
+ };
1413
+ const handleChange = (next) => {
1414
+ if (Array.isArray(next) && next.includes(CREATE_KEY)) {
1415
+ const rest = next.filter((k) => k !== CREATE_KEY);
1416
+ const created = onCreate?.(textRef.current.trim());
1417
+ setValue(created != null ? [...rest, created] : rest);
1418
+ return;
1419
+ }
1420
+ setValue(next);
1421
+ };
1422
+ const mobile = useMediaQuery(MOBILE_QUERY);
1423
+ if (overlay === "sheet" || overlay === "auto" && mobile) {
1424
+ return /* @__PURE__ */ jsx20(
1425
+ ComboboxSheet,
1426
+ {
1427
+ ...{
1428
+ label,
1429
+ description,
1430
+ errorMessage,
1431
+ placeholder,
1432
+ size,
1433
+ leadingIcon,
1434
+ isLoading,
1435
+ noResultsMessage,
1436
+ onCreate,
1437
+ children,
1438
+ className
1439
+ },
1440
+ multiple,
1441
+ value,
1442
+ onValueChange: handleChange,
1443
+ textRef,
1444
+ items: props.items,
1445
+ defaultItems: props.defaultItems,
1446
+ inputValue: props.inputValue,
1447
+ onInputChange,
1448
+ isDisabled: props.isDisabled,
1449
+ isReadOnly: props.isReadOnly,
1450
+ isInvalid: props.isInvalid,
1451
+ isRequired: props.isRequired,
1452
+ name: props.name,
1453
+ commit,
1454
+ onApply,
1455
+ resultLabel
1456
+ }
1457
+ );
1458
+ }
1459
+ const focusInput = (e) => {
1460
+ if (e.target === e.currentTarget) {
1461
+ e.preventDefault();
1462
+ inputRef.current?.focus();
1463
+ }
1464
+ };
1465
+ return /* @__PURE__ */ jsxs19(
1466
+ AriaComboBox,
1467
+ {
1468
+ ...props,
1469
+ selectionMode,
1470
+ value,
1471
+ onChange: handleChange,
1472
+ onInputChange: (text2) => {
1473
+ textRef.current = text2;
1474
+ setText(text2.trim());
1475
+ onInputChange?.(text2);
1476
+ },
1477
+ menuTrigger: "input",
1478
+ allowsEmptyCollection: true,
1479
+ className: cx("medo-field", "medo-combobox", className),
1480
+ children: [
1481
+ /* @__PURE__ */ jsx20(FieldLabel, { isRequired: props.isRequired, children: label }),
1482
+ /* @__PURE__ */ jsxs19(Group, { ref: groupRef, className: cx("medo-input", `medo-input--${size}`, "medo-combobox__field"), onMouseDown: focusInput, children: [
1483
+ leadingIcon && /* @__PURE__ */ jsx20("span", { className: "medo-input__icon", "aria-hidden": "true", children: leadingIcon }),
1484
+ multiple ? (
1485
+ /* Tags and input share one line; the tags get what is left after 4 rem for typing. */
1486
+ /* @__PURE__ */ jsxs19("span", { className: "medo-combobox__entry", ref: entryRef, children: [
1487
+ /* @__PURE__ */ jsx20(SelectedTags2, { entryRef, size, label, isDisabled: props.isDisabled, isReadOnly: props.isReadOnly }),
1488
+ /* @__PURE__ */ jsx20(ComboInput, { ref: inputRef, placeholder, multiple: true, onRemoved: setAnnouncement })
1489
+ ] })
1490
+ ) : /* @__PURE__ */ jsx20(ComboInput, { ref: inputRef, placeholder, multiple: false, onRemoved: setAnnouncement }),
1491
+ /* @__PURE__ */ jsx20(ClearButton2, { inputRef, isDisabled: props.isDisabled || props.isReadOnly }),
1492
+ !props.isReadOnly && /* @__PURE__ */ jsx20(AriaButton7, { className: "medo-input__action medo-combobox__toggle", children: /* @__PURE__ */ jsx20(ChevronDown2, { "aria-hidden": "true" }) })
1493
+ ] }),
1494
+ /* @__PURE__ */ jsx20(FieldFooter, { description, errorMessage }),
1495
+ /* @__PURE__ */ jsx20("span", { className: "medo-visually-hidden", "aria-live": "polite", children: announcement }),
1496
+ /* @__PURE__ */ jsx20(Popover4, { offset: 4, className: "medo-listbox-popover", children: /* @__PURE__ */ jsx20(
1497
+ ComboList,
1498
+ {
1499
+ size,
1500
+ items: props.items ?? props.defaultItems,
1501
+ isLoading,
1502
+ noResultsMessage,
1503
+ creatable: multiple && !!onCreate,
1504
+ text,
1505
+ exact,
1506
+ onExactChange: setExact,
1507
+ children
1508
+ }
1509
+ ) })
1510
+ ]
1511
+ }
1512
+ );
1513
+ }
1514
+ function ComboInput({
1515
+ ref,
1516
+ placeholder,
1517
+ multiple,
1518
+ onRemoved
1519
+ }) {
1520
+ const state = useContext12(ComboBoxStateContext2);
1521
+ const values = Array.isArray(state?.value) ? state.value : [];
1522
+ const onKeyDown = (e) => {
1523
+ if (!multiple || !state || e.key !== "Backspace" || state.inputValue !== "" || values.length === 0) return;
1524
+ const last = state.selectedItems.at(-1);
1525
+ state.setValue(values.slice(0, -1));
1526
+ if (last) onRemoved(`${last.textValue} removed`);
1527
+ };
1528
+ return /* @__PURE__ */ jsx20(
1529
+ Input,
1530
+ {
1531
+ ref,
1532
+ className: "medo-input__control medo-combobox__input",
1533
+ placeholder: multiple && values.length > 0 ? void 0 : placeholder,
1534
+ onKeyDown
1535
+ }
1536
+ );
1537
+ }
1538
+ function ClearButton2({ inputRef, isDisabled }) {
1539
+ const state = useContext12(ComboBoxStateContext2);
1540
+ if (!state || isDisabled) return null;
1541
+ const hasValue = Array.isArray(state.value) ? state.value.length > 0 : state.value != null;
1542
+ if (!hasValue && !state.inputValue) return null;
1543
+ return /* @__PURE__ */ jsx20(
1544
+ InputAction,
1545
+ {
1546
+ slot: null,
1547
+ excludeFromTabOrder: true,
1548
+ "aria-label": Array.isArray(state.value) ? "Clear all" : "Clear",
1549
+ icon: /* @__PURE__ */ jsx20(X6, {}),
1550
+ onPress: () => {
1551
+ state.setValue(Array.isArray(state.value) ? [] : null);
1552
+ state.setInputValue("");
1553
+ inputRef.current?.focus();
1554
+ }
1555
+ }
1556
+ );
1557
+ }
1558
+ function SelectedTags2({
1559
+ entryRef,
1560
+ size,
1561
+ label,
1562
+ isDisabled,
1563
+ isReadOnly
1564
+ }) {
1565
+ const state = useContext12(ComboBoxStateContext2);
1566
+ const items = (state?.selectedItems ?? []).filter(Boolean);
1567
+ const values = Array.isArray(state?.value) ? state.value : [];
1568
+ const key = items.map((item) => item.key).join("\0");
1569
+ const measureRef = useRef6(null);
1570
+ const visible = useFitCount(entryRef, measureRef, key, items.length, 68);
1571
+ if (!state || items.length === 0) return null;
1572
+ const tagSize = size === "lg" ? "md" : "sm";
1573
+ const hidden = items.length - visible;
1574
+ const name = typeof label === "string" ? label : "Selected";
1575
+ return /* @__PURE__ */ jsxs19("span", { className: "medo-combobox__tags-row", children: [
1576
+ /* @__PURE__ */ jsxs19("span", { className: cx("medo-combobox__measure"), ref: measureRef, "aria-hidden": "true", children: [
1577
+ items.map((item) => /* @__PURE__ */ jsxs19("span", { className: cx("medo-tag", `medo-tag--${tagSize}`), "data-allows-removing": "", children: [
1578
+ /* @__PURE__ */ jsx20("span", { className: "medo-tag__label", children: item.textValue }),
1579
+ /* @__PURE__ */ jsx20("span", { className: "medo-tag__remove" })
1580
+ ] }, item.key)),
1581
+ /* @__PURE__ */ jsxs19("span", { className: cx("medo-tag", `medo-tag--${tagSize}`), children: [
1582
+ "+",
1583
+ items.length
1584
+ ] })
1585
+ ] }),
1586
+ /* @__PURE__ */ jsx20(
1587
+ TagGroup,
1588
+ {
1589
+ "aria-label": `${name}: selected`,
1590
+ size: tagSize,
1591
+ className: "medo-combobox__tags",
1592
+ items: items.slice(0, visible),
1593
+ disabledKeys: isDisabled ? items.map((item) => item.key) : void 0,
1594
+ onRemove: isReadOnly ? void 0 : (keys) => state.setValue(values.filter((k) => !keys.has(k))),
1595
+ children: (item) => /* @__PURE__ */ jsx20(Tag, { id: item.key, children: item.textValue })
1596
+ }
1597
+ ),
1598
+ hidden > 0 && /* @__PURE__ */ jsxs19("span", { className: cx("medo-tag", `medo-tag--${tagSize}`, "medo-combobox__more"), children: [
1599
+ "+",
1600
+ hidden,
1601
+ /* @__PURE__ */ jsx20("span", { className: "medo-visually-hidden", children: " more selected" })
1602
+ ] })
1603
+ ] });
1604
+ }
1605
+ function ComboList({
1606
+ size,
1607
+ items,
1608
+ isLoading,
1609
+ noResultsMessage,
1610
+ creatable,
1611
+ text,
1612
+ exact,
1613
+ onExactChange,
1614
+ children
1615
+ }) {
1616
+ const state = useContext12(ComboBoxStateContext2);
1617
+ const nodes = state ? [...state.collection].filter((node) => node.key !== CREATE_KEY) : [];
1618
+ const hasItems = nodes.length > 0;
1619
+ const exactNow = nodes.some((node) => node.textValue.toLocaleLowerCase() === text.toLocaleLowerCase());
1620
+ useEffect6(() => {
1621
+ if (state && exactNow !== exact) onExactChange(exactNow);
1622
+ });
1623
+ const typing = !!state?.isOpen && text !== "";
1624
+ useEffect6(() => {
1625
+ if (!state || !typing) return;
1626
+ const frame = requestAnimationFrame(() => {
1627
+ const manager = state.selectionManager;
1628
+ let key = state.collection.getFirstKey();
1629
+ while (key != null) {
1630
+ const node = state.collection.getItem(key);
1631
+ if (node?.type === "item" && !manager.isDisabled(key)) break;
1632
+ key = state.collection.getKeyAfter(key);
1633
+ }
1634
+ if (key == null) return;
1635
+ manager.setFocused(true);
1636
+ if (manager.focusedKey !== key) manager.setFocusedKey(key);
1637
+ });
1638
+ return () => cancelAnimationFrame(frame);
1639
+ }, [typing, text, state?.collection]);
1640
+ return /* @__PURE__ */ jsxs19(Fragment12, { children: [
1641
+ /* @__PURE__ */ jsxs19(
1642
+ ListBox,
1643
+ {
1644
+ size: size === "lg" ? "lg" : "md",
1645
+ renderEmptyState: () => /* @__PURE__ */ jsx20(ListBoxMessage, { children: isLoading ? "Loading\u2026" : noResultsMessage(text) }),
1646
+ children: [
1647
+ typeof children === "function" ? /* @__PURE__ */ jsx20(Collection2, { items, children }) : children,
1648
+ creatable && text && !exact && /* @__PURE__ */ jsx20(ListBoxSection, { "aria-label": "Create", children: /* @__PURE__ */ jsx20(Option, { id: CREATE_KEY, icon: /* @__PURE__ */ jsx20(Plus, {}), textValue: text, children: `Add \u201E${text}\u201C` }) })
1649
+ ]
1650
+ }
1651
+ ),
1652
+ isLoading && hasItems && /* @__PURE__ */ jsx20(ListBoxMessage, { children: "Loading\u2026" })
1653
+ ] });
1654
+ }
1655
+ function ComboboxSheet({
1656
+ label,
1657
+ description,
1658
+ errorMessage,
1659
+ placeholder,
1660
+ size,
1661
+ leadingIcon,
1662
+ isLoading,
1663
+ noResultsMessage,
1664
+ onCreate,
1665
+ children,
1666
+ className,
1667
+ multiple,
1668
+ value,
1669
+ onValueChange,
1670
+ textRef,
1671
+ items,
1672
+ defaultItems,
1673
+ inputValue,
1674
+ onInputChange,
1675
+ isDisabled,
1676
+ isReadOnly,
1677
+ isInvalid,
1678
+ isRequired,
1679
+ name,
1680
+ commit,
1681
+ onApply,
1682
+ resultLabel
1683
+ }) {
1684
+ const id = useId4();
1685
+ const [isOpen, setOpen] = useState8(false);
1686
+ const [ownSearch, setOwnSearch] = useState8("");
1687
+ const search = inputValue ?? ownSearch;
1688
+ const setSearch = (text) => {
1689
+ textRef.current = text;
1690
+ if (inputValue === void 0) setOwnSearch(text);
1691
+ onInputChange?.(text);
1692
+ };
1693
+ const { contains } = useFilter({ sensitivity: "base" });
1694
+ const list = items ?? defaultItems;
1695
+ const keys = Array.isArray(value) ? value : value == null ? [] : [value];
1696
+ const opened = useRef6(null);
1697
+ const query = search.trim();
1698
+ const content = typeof children === "function" ? /* @__PURE__ */ jsx20(Collection2, { items: list, children }) : children;
1699
+ const open = (next) => {
1700
+ if (next) {
1701
+ setSearch("");
1702
+ opened.current = JSON.stringify(keys);
1703
+ } else if (commit === "onClose" && multiple && opened.current !== null && opened.current !== JSON.stringify(keys)) {
1704
+ onApply?.(keys);
1705
+ }
1706
+ if (!next) opened.current = null;
1707
+ setOpen(next);
1708
+ };
1709
+ const onSelectionChange = (selection) => {
1710
+ const next = selection === "all" ? [] : [...selection];
1711
+ if (multiple) {
1712
+ onValueChange(next);
1713
+ } else {
1714
+ onValueChange(next[0] ?? value);
1715
+ open(false);
1716
+ }
1717
+ };
1718
+ const descId = description ? `${id}-desc` : void 0;
1719
+ const errId = isInvalid && errorMessage ? `${id}-err` : void 0;
1720
+ return /* @__PURE__ */ jsx20(CollectionBuilder, { content, children: (collection) => {
1721
+ const nodes = [...collection.getKeys()].map((key) => collection.getItem(key)).filter((node) => node?.type === "item");
1722
+ const text = keys.map((key) => collection.getItem(key)?.textValue ?? String(key));
1723
+ const exact = nodes.some((node) => node.textValue.toLocaleLowerCase() === query.toLocaleLowerCase());
1724
+ return /* @__PURE__ */ jsxs19(
1725
+ "div",
1726
+ {
1727
+ className: cx("medo-field", "medo-combobox", "medo-select", multiple && keys.length > 0 && "medo-select--tags", className),
1728
+ "data-invalid": isInvalid || void 0,
1729
+ "data-disabled": isDisabled || void 0,
1730
+ "data-readonly": isReadOnly || void 0,
1731
+ "data-open": isOpen || void 0,
1732
+ children: [
1733
+ /* @__PURE__ */ jsxs19("span", { className: "medo-field__label", id: `${id}-label`, children: [
1734
+ label,
1735
+ isRequired && /* @__PURE__ */ jsx20("span", { className: "medo-field__required", "aria-hidden": "true", children: "*" })
1736
+ ] }),
1737
+ /* @__PURE__ */ jsx20("div", { className: cx("medo-input", `medo-input--${size}`, "medo-select__field"), children: /* @__PURE__ */ jsxs19(
1738
+ AriaButton7,
1739
+ {
1740
+ className: "medo-select__trigger",
1741
+ isDisabled,
1742
+ "aria-haspopup": "dialog",
1743
+ "aria-labelledby": `${id}-label ${id}-value`,
1744
+ "aria-describedby": [errId ?? descId].filter(Boolean).join(" ") || void 0,
1745
+ onPress: () => !isReadOnly && open(true),
1746
+ children: [
1747
+ leadingIcon && /* @__PURE__ */ jsx20("span", { className: "medo-input__icon", "aria-hidden": "true", children: leadingIcon }),
1748
+ /* @__PURE__ */ jsxs19("span", { className: "medo-select__value", id: `${id}-value`, "data-placeholder": keys.length === 0 || void 0, children: [
1749
+ keys.length === 0 ? placeholder : multiple ? /* @__PURE__ */ jsx20(DisplayTags, { labels: text, size }) : text[0],
1750
+ isReadOnly && /* @__PURE__ */ jsx20("span", { className: "medo-visually-hidden", children: ", read only" })
1751
+ ] }),
1752
+ !isReadOnly && /* @__PURE__ */ jsx20("span", { className: "medo-select__chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx20(ChevronDown2, {}) })
1753
+ ]
1754
+ }
1755
+ ) }),
1756
+ /* @__PURE__ */ jsx20("div", { className: "medo-field__footer", children: isInvalid && errorMessage ? /* @__PURE__ */ jsxs19("span", { className: "medo-field__error", id: errId, children: [
1757
+ /* @__PURE__ */ jsx20(CircleAlert3, { "aria-hidden": "true" }),
1758
+ /* @__PURE__ */ jsx20("span", { children: errorMessage })
1759
+ ] }) : description && /* @__PURE__ */ jsx20("span", { className: "medo-field__description", id: descId, children: description }) }),
1760
+ name && keys.map((key) => /* @__PURE__ */ jsx20("input", { type: "hidden", name, value: String(key) }, String(key))),
1761
+ /* @__PURE__ */ jsx20(
1762
+ BottomSheet,
1763
+ {
1764
+ title: label,
1765
+ height: "full",
1766
+ isOpen,
1767
+ onOpenChange: open,
1768
+ footer: multiple ? commit === "onClose" ? (close) => /* @__PURE__ */ jsxs19(Fragment12, { children: [
1769
+ /* @__PURE__ */ jsx20(Button, { size: "lg", onPress: close, children: resultLabel(keys) }),
1770
+ /* @__PURE__ */ jsx20(Button, { size: "lg", variant: "secondary", onPress: () => onValueChange([]), isDisabled: keys.length === 0, children: "Reset" })
1771
+ ] }) : (close) => /* @__PURE__ */ jsxs19(Fragment12, { children: [
1772
+ /* @__PURE__ */ jsxs19("p", { className: "medo-sheet__count", "aria-live": "polite", children: [
1773
+ keys.length,
1774
+ " selected"
1775
+ ] }),
1776
+ /* @__PURE__ */ jsx20(Button, { size: "lg", onPress: close, children: "Done" })
1777
+ ] }) : void 0,
1778
+ children: /* @__PURE__ */ jsxs19(Autocomplete, { inputValue: search, onInputChange: setSearch, filter: items ? void 0 : contains, children: [
1779
+ /* @__PURE__ */ jsx20("div", { className: "medo-sheet__search", children: /* @__PURE__ */ jsxs19(AriaTextField2, { "aria-label": "Search", autoFocus: true, className: "medo-input medo-input--lg", children: [
1780
+ /* @__PURE__ */ jsx20("span", { className: "medo-input__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx20(Search, {}) }),
1781
+ /* @__PURE__ */ jsx20(Input, { className: "medo-input__control", placeholder })
1782
+ ] }) }),
1783
+ /* @__PURE__ */ jsx20(OptionHighlightContext.Provider, { value: query, children: /* @__PURE__ */ jsxs19(
1784
+ ListBox,
1785
+ {
1786
+ size: "lg",
1787
+ "aria-labelledby": `${id}-label`,
1788
+ selectionMode: multiple ? "multiple" : "single",
1789
+ selectedKeys: keys,
1790
+ onSelectionChange,
1791
+ renderEmptyState: () => /* @__PURE__ */ jsx20(ListBoxMessage, { children: isLoading ? "Loading\u2026" : noResultsMessage(query) }),
1792
+ children: [
1793
+ content,
1794
+ multiple && onCreate && query && !exact && /* @__PURE__ */ jsx20(ListBoxSection, { "aria-label": "Create", children: /* @__PURE__ */ jsx20(Option, { id: CREATE_KEY, icon: /* @__PURE__ */ jsx20(Plus, {}), textValue: query, children: `Add \u201E${query}\u201C` }) })
1795
+ ]
1796
+ }
1797
+ ) }),
1798
+ isLoading && list && [...list].length > 0 && /* @__PURE__ */ jsx20(ListBoxMessage, { children: "Loading\u2026" })
1799
+ ] })
1800
+ }
1801
+ )
1802
+ ]
1803
+ }
1804
+ );
1805
+ } });
1806
+ }
1807
+ function DisplayTags({ labels, size }) {
1808
+ const rowRef = useRef6(null);
1809
+ const measureRef = useRef6(null);
1810
+ const visible = useFitCount(rowRef, measureRef, labels.join("\0"), labels.length);
1811
+ const tagClass = cx("medo-tag", size === "lg" ? "medo-tag--md" : "medo-tag--sm");
1812
+ return /* @__PURE__ */ jsxs19("span", { className: "medo-select__tags", ref: rowRef, children: [
1813
+ /* @__PURE__ */ jsx20("span", { className: "medo-visually-hidden", children: labels.join(", ") }),
1814
+ /* @__PURE__ */ jsxs19("span", { className: "medo-select__measure", ref: measureRef, "aria-hidden": "true", children: [
1815
+ labels.map((l, i) => /* @__PURE__ */ jsx20("span", { className: tagClass, children: l }, i)),
1816
+ /* @__PURE__ */ jsxs19("span", { className: tagClass, children: [
1817
+ "+",
1818
+ labels.length
1819
+ ] })
1820
+ ] }),
1821
+ labels.slice(0, visible).map((l, i) => /* @__PURE__ */ jsx20("span", { className: tagClass, "aria-hidden": "true", children: /* @__PURE__ */ jsx20("span", { className: "medo-select__tag-label", children: l }) }, i)),
1822
+ labels.length > visible && /* @__PURE__ */ jsxs19("span", { className: tagClass, "aria-hidden": "true", children: [
1823
+ "+",
1824
+ labels.length - visible
1825
+ ] })
1826
+ ] });
1827
+ }
1828
+
1829
+ // src/toast/Toast.tsx
1830
+ import { useEffect as useEffect7, useState as useState9 } from "react";
1831
+ import { flushSync } from "react-dom";
1832
+ import {
1833
+ Button as Button2,
1834
+ Text as Text8,
1835
+ UNSTABLE_Toast as AriaToast,
1836
+ UNSTABLE_ToastContent as AriaToastContent,
1837
+ UNSTABLE_ToastQueue as ToastQueue,
1838
+ UNSTABLE_ToastRegion as AriaToastRegion
1839
+ } from "react-aria-components";
1840
+ import { X as X7 } from "lucide-react";
1841
+ import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
1842
+ var TIMEOUT = 5e3;
1843
+ var TIMEOUT_ACTION = 8e3;
1844
+ var MAX_VISIBLE = 3;
1845
+ var queue = new ToastQueue({
1846
+ maxVisibleToasts: MAX_VISIBLE,
1847
+ wrapUpdate(fn) {
1848
+ if (typeof document !== "undefined" && "startViewTransition" in document) {
1849
+ document.startViewTransition(() => flushSync(fn));
1850
+ } else {
1851
+ fn();
1852
+ }
1853
+ }
1854
+ });
1855
+ function toast(message, { action, persistent = false, onClose } = {}) {
1856
+ const same = queue.visibleToasts.find((t) => t.content.message === message);
1857
+ if (same) return same.key;
1858
+ const visible = queue.visibleToasts;
1859
+ if (visible.length >= MAX_VISIBLE) queue.close(visible[visible.length - 1].key);
1860
+ return queue.add({ message, action }, { timeout: persistent ? void 0 : action ? TIMEOUT_ACTION : TIMEOUT, onClose });
1861
+ }
1862
+ toast.close = (key) => queue.close(key);
1863
+ toast.clear = () => queue.clear();
1864
+ var regionMounted = false;
1865
+ function closeOnEscape(el, key) {
1866
+ if (!el) return;
1867
+ const onKeyDown = (e) => {
1868
+ if (e.key !== "Escape") return;
1869
+ e.stopPropagation();
1870
+ queue.close(key);
1871
+ };
1872
+ el.addEventListener("keydown", onKeyDown);
1873
+ return () => el.removeEventListener("keydown", onKeyDown);
1874
+ }
1875
+ function ToastRegion({ closeLabel, ...props }) {
1876
+ const [active, setActive] = useState9(false);
1877
+ useEffect7(() => {
1878
+ if (regionMounted) return;
1879
+ regionMounted = true;
1880
+ setActive(true);
1881
+ const onVisibility = () => document.hidden ? queue.pauseAll() : queue.resumeAll();
1882
+ document.addEventListener("visibilitychange", onVisibility);
1883
+ return () => {
1884
+ regionMounted = false;
1885
+ document.removeEventListener("visibilitychange", onVisibility);
1886
+ };
1887
+ }, []);
1888
+ if (!active) return null;
1889
+ return /* @__PURE__ */ jsx21(AriaToastRegion, { ...props, queue, className: "medo-toast-region", children: ({ toast: t }) => /* @__PURE__ */ jsxs20(
1890
+ AriaToast,
1891
+ {
1892
+ toast: t,
1893
+ className: "medo-toast",
1894
+ style: { viewTransitionName: t.key },
1895
+ ref: (el) => closeOnEscape(el, t.key),
1896
+ children: [
1897
+ /* @__PURE__ */ jsx21(AriaToastContent, { className: "medo-toast__content", children: /* @__PURE__ */ jsx21(Text8, { slot: "title", className: "medo-toast__message", children: t.content.message }) }),
1898
+ t.content.action && /* @__PURE__ */ jsx21(
1899
+ Button2,
1900
+ {
1901
+ className: "medo-toast__button medo-toast__action",
1902
+ onPress: () => {
1903
+ t.content.action?.onAction();
1904
+ queue.close(t.key);
1905
+ },
1906
+ children: t.content.action.label
1907
+ }
1908
+ ),
1909
+ /* @__PURE__ */ jsx21(Button2, { slot: "close", "aria-label": closeLabel, className: "medo-toast__button medo-toast__close", children: /* @__PURE__ */ jsx21(X7, { "aria-hidden": "true" }) })
1910
+ ]
1911
+ }
1912
+ ) });
1913
+ }
1914
+
1915
+ // src/tabs/Tabs.tsx
1916
+ import { useLayoutEffect as useLayoutEffect4, useRef as useRef7 } from "react";
1917
+ import {
1918
+ SelectionIndicator as SelectionIndicator2,
1919
+ Tab as AriaTab,
1920
+ TabList as AriaTabList,
1921
+ TabPanel as AriaTabPanel,
1922
+ Tabs as AriaTabs
1923
+ } from "react-aria-components";
1924
+ import { jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
1925
+ function Tabs({ size = "md", fullWidth = false, className, ...props }) {
1926
+ return /* @__PURE__ */ jsx22(AriaTabs, { ...props, className: cx("medo-tabs", `medo-tabs--${size}`, fullWidth && "medo-tabs--full", className) });
1927
+ }
1928
+ function TabList({ className, ...props }) {
1929
+ const ref = useRef7(null);
1930
+ useScrollEdges(ref);
1931
+ return /* @__PURE__ */ jsx22(AriaTabList, { ...props, ref, className: cx("medo-tabs__list", className) });
1932
+ }
1933
+ function Tab({ children, icon, count, countLabel, ...props }) {
1934
+ return /* @__PURE__ */ jsxs21(AriaTab, { ...props, className: "medo-tab", children: [
1935
+ /* @__PURE__ */ jsx22(SelectionIndicator2, { className: "medo-tab__thumb", ref: revealInList }),
1936
+ icon && /* @__PURE__ */ jsx22("span", { className: "medo-tab__icon", "aria-hidden": "true", children: icon }),
1937
+ /* @__PURE__ */ jsx22("span", { className: "medo-tab__label", children }),
1938
+ count !== void 0 && /* @__PURE__ */ jsx22(BadgeCount, { count, tone: "neutral", size: "sm", label: countLabel ?? String(count) })
1939
+ ] });
1940
+ }
1941
+ function TabPanel({ keepMounted = true, className, ...props }) {
1942
+ return /* @__PURE__ */ jsx22(AriaTabPanel, { ...props, shouldForceMount: keepMounted, className: cx("medo-tabs__panel", className) });
1943
+ }
1944
+ function revealInList(thumb) {
1945
+ const tab = thumb?.parentElement;
1946
+ const list = tab?.parentElement;
1947
+ if (!tab || !list || list.scrollWidth <= list.clientWidth) return;
1948
+ const start = tab.offsetLeft - list.offsetLeft;
1949
+ const end = start + tab.offsetWidth;
1950
+ if (start < list.scrollLeft) list.scrollTo({ left: start - 32 });
1951
+ else if (end > list.scrollLeft + list.clientWidth) list.scrollTo({ left: end - list.clientWidth + 32 });
1952
+ }
1953
+ function useScrollEdges(ref) {
1954
+ useLayoutEffect4(() => {
1955
+ const list = ref.current;
1956
+ if (!list) return;
1957
+ const update = () => {
1958
+ const max = list.scrollWidth - list.clientWidth;
1959
+ const start = Math.abs(list.scrollLeft);
1960
+ list.toggleAttribute("data-scroll-start", max > 1 && start > 1);
1961
+ list.toggleAttribute("data-scroll-end", max > 1 && start < max - 1);
1962
+ };
1963
+ update();
1964
+ list.addEventListener("scroll", update, { passive: true });
1965
+ const observer = new ResizeObserver(update);
1966
+ observer.observe(list);
1967
+ return () => {
1968
+ list.removeEventListener("scroll", update);
1969
+ observer.disconnect();
1970
+ };
1971
+ }, [ref]);
1972
+ }
1973
+
1974
+ // src/popover/Popover.tsx
1975
+ import { useContext as useContext13, useRef as useRef8 } from "react";
1976
+ import {
1977
+ Dialog as AriaDialog2,
1978
+ DialogTrigger as DialogTrigger2,
1979
+ Heading as Heading3,
1980
+ OverlayTriggerStateContext as OverlayTriggerStateContext3,
1981
+ Popover as AriaPopover
1982
+ } from "react-aria-components";
1983
+ import { Info as Info2, X as X8 } from "lucide-react";
1984
+
1985
+ // src/link/Link.tsx
1986
+ import { Link as AriaLink2 } from "react-aria-components";
1987
+ import { ArrowRight, ExternalLink } from "lucide-react";
1988
+ import { jsx as jsx23, jsxs as jsxs22 } from "react/jsx-runtime";
1989
+ function Link({
1990
+ children,
1991
+ variant = "inline",
1992
+ size = "md",
1993
+ external,
1994
+ arrow,
1995
+ newTabLabel = "(opens in a new tab)",
1996
+ className,
1997
+ ...props
1998
+ }) {
1999
+ const newTab = props.target === "_blank";
2000
+ return /* @__PURE__ */ jsxs22(
2001
+ AriaLink2,
2002
+ {
2003
+ ...props,
2004
+ rel: newTab ? props.rel ?? "noopener noreferrer" : props.rel,
2005
+ className: cx("medo-link", variant === "standalone" && "medo-link--standalone", variant === "standalone" && `medo-link--${size}`, className),
2006
+ children: [
2007
+ children,
2008
+ external && /* @__PURE__ */ jsx23(ExternalLink, { className: "medo-link__icon", "aria-hidden": "true" }),
2009
+ !external && arrow && variant === "standalone" && /* @__PURE__ */ jsx23(ArrowRight, { className: "medo-link__icon", "aria-hidden": "true" }),
2010
+ newTab && /* @__PURE__ */ jsxs22("span", { className: "medo-visually-hidden", children: [
2011
+ " ",
2012
+ newTabLabel
2013
+ ] })
2014
+ ]
2015
+ }
2016
+ );
2017
+ }
2018
+
2019
+ // src/popover/Popover.tsx
2020
+ import { Fragment as Fragment13, jsx as jsx24, jsxs as jsxs23 } from "react/jsx-runtime";
2021
+ var PopoverTrigger = DialogTrigger2;
2022
+ function Popover5({
2023
+ title,
2024
+ "aria-label": ariaLabel,
2025
+ children,
2026
+ size = "md",
2027
+ primaryAction,
2028
+ cancelLabel = "Cancel",
2029
+ closeLabel = "Close",
2030
+ overlay = "auto",
2031
+ placement = "bottom start",
2032
+ className
2033
+ }) {
2034
+ const mobile = useMediaQuery(MOBILE_QUERY);
2035
+ const content = (close) => typeof children === "function" ? children(close) : children;
2036
+ if (overlay === "sheet" || overlay === "auto" && mobile) {
2037
+ return /* @__PURE__ */ jsx24(
2038
+ BottomSheet,
2039
+ {
2040
+ title: title ?? ariaLabel,
2041
+ closeLabel,
2042
+ className,
2043
+ footer: primaryAction && ((close) => /* @__PURE__ */ jsxs23(ButtonGroup, { children: [
2044
+ /* @__PURE__ */ jsx24(Button, { variant: "secondary", size: "lg", onPress: close, children: cancelLabel }),
2045
+ /* @__PURE__ */ jsx24(ActionButton, { action: primaryAction, close, size: "lg", variant: "primary" })
2046
+ ] })),
2047
+ children: /* @__PURE__ */ jsx24(SheetBody, { children: content })
2048
+ }
2049
+ );
2050
+ }
2051
+ return /* @__PURE__ */ jsx24(AriaPopover, { placement, offset: 8, containerPadding: 16, className: cx("medo-popover", `medo-popover--${size}`, className), children: /* @__PURE__ */ jsx24(PopoverDialog, { "aria-label": title ? void 0 : ariaLabel, children: ({ close }) => /* @__PURE__ */ jsxs23(Fragment13, { children: [
2052
+ title && /* @__PURE__ */ jsxs23("div", { className: "medo-popover__header", children: [
2053
+ /* @__PURE__ */ jsx24(Heading3, { slot: "title", className: "medo-popover__title", children: title }),
2054
+ /* @__PURE__ */ jsx24(IconButton, { variant: "ghost", size: "sm", icon: /* @__PURE__ */ jsx24(X8, {}), "aria-label": closeLabel, tooltip: false, onPress: close })
2055
+ ] }),
2056
+ /* @__PURE__ */ jsx24("div", { className: "medo-popover__body", children: content(close) }),
2057
+ primaryAction && /* @__PURE__ */ jsx24("div", { className: "medo-popover__footer", children: /* @__PURE__ */ jsxs23(ButtonGroup, { children: [
2058
+ /* @__PURE__ */ jsx24(Button, { variant: "secondary", size: "sm", onPress: close, children: cancelLabel }),
2059
+ /* @__PURE__ */ jsx24(ActionButton, { action: primaryAction, close, size: "sm", variant: "primary" })
2060
+ ] }) })
2061
+ ] }) }) });
2062
+ }
2063
+ function PopoverDialog({ children, ...props }) {
2064
+ const ref = useRef8(null);
2065
+ useInitialFocus(ref);
2066
+ return /* @__PURE__ */ jsx24(AriaDialog2, { ...props, ref, className: "medo-popover__dialog", children });
2067
+ }
2068
+ function SheetBody({ children }) {
2069
+ const state = useContext13(OverlayTriggerStateContext3);
2070
+ return /* @__PURE__ */ jsx24("div", { className: "medo-popover__sheet-body", children: children(() => state?.close()) });
2071
+ }
2072
+ function Toggletip({ "aria-label": label, children, link, className }) {
2073
+ return /* @__PURE__ */ jsxs23(DialogTrigger2, { children: [
2074
+ /* @__PURE__ */ jsx24(IconButton, { variant: "ghost", size: "sm", icon: /* @__PURE__ */ jsx24(Info2, {}), "aria-label": label, tooltip: false }),
2075
+ /* @__PURE__ */ jsx24(AriaPopover, { placement: "bottom start", offset: 8, containerPadding: 16, className: cx("medo-popover", "medo-toggletip", className), children: /* @__PURE__ */ jsxs23(AriaDialog2, { "aria-label": label, className: "medo-popover__dialog medo-toggletip__dialog", children: [
2076
+ /* @__PURE__ */ jsx24("div", { className: "medo-toggletip__text", children }),
2077
+ link && /* @__PURE__ */ jsx24(Link, { variant: "standalone", size: "md", href: link.href, external: link.external, target: link.external ? "_blank" : void 0, children: link.label })
2078
+ ] }) })
2079
+ ] });
2080
+ }
2081
+
2082
+ // src/navigation/Navigation.tsx
2083
+ import { Children as Children2, createContext as createContext7, isValidElement as isValidElement2, useContext as useContext14, useEffect as useEffect9, useId as useId5, useRef as useRef10, useState as useState10 } from "react";
2084
+ import {
2085
+ Button as AriaButton8,
2086
+ Dialog as AriaDialog4,
2087
+ DialogTrigger as DialogTrigger3,
2088
+ Heading as Heading5,
2089
+ Link as AriaLink3,
2090
+ Modal as Modal4,
2091
+ ModalOverlay as ModalOverlay4
2092
+ } from "react-aria-components";
2093
+ import { ChevronDown as ChevronDown3, Ellipsis as Ellipsis2, Menu as MenuIcon, PanelLeft, X as X10 } from "lucide-react";
2094
+
2095
+ // src/drawer/Drawer.tsx
2096
+ import { useEffect as useEffect8, useRef as useRef9 } from "react";
2097
+ import { Dialog as AriaDialog3, Heading as Heading4, Modal as Modal3, ModalOverlay as ModalOverlay3 } from "react-aria-components";
2098
+ import { X as X9 } from "lucide-react";
2099
+ import { Fragment as Fragment14, jsx as jsx25, jsxs as jsxs24 } from "react/jsx-runtime";
2100
+ function Drawer({
2101
+ title,
2102
+ "aria-label": ariaLabel,
2103
+ children,
2104
+ footer,
2105
+ side = "right",
2106
+ size = "sm",
2107
+ closeLabel = "Close",
2108
+ isDirty = false,
2109
+ discardText,
2110
+ className,
2111
+ ...openProps
2112
+ }) {
2113
+ const { open, onOpenChange, confirmDiscard, forceClose } = useDirtyGuard({ isDirty, discardText, ...openProps });
2114
+ return /* @__PURE__ */ jsx25(ModalOverlay3, { isOpen: open, onOpenChange, isDismissable: !isDirty, className: cx("medo-drawer-overlay", `medo-drawer-overlay--${side}`), children: /* @__PURE__ */ jsx25(Modal3, { className: cx("medo-drawer", `medo-drawer--${side}`, `medo-drawer--${size}`, className), children: /* @__PURE__ */ jsx25(DrawerDialog, { "aria-label": ariaLabel, side, children: (close) => /* @__PURE__ */ jsxs24(Fragment14, { children: [
2115
+ /* @__PURE__ */ jsxs24("div", { className: "medo-drawer__header", children: [
2116
+ ariaLabel ? /* @__PURE__ */ jsx25("div", { className: "medo-drawer__title", children: title }) : /* @__PURE__ */ jsx25(Heading4, { slot: "title", className: "medo-drawer__title", children: title }),
2117
+ /* @__PURE__ */ jsx25(IconButton, { variant: "ghost", size: "md", icon: /* @__PURE__ */ jsx25(X9, {}), "aria-label": closeLabel, tooltip: false, onPress: close })
2118
+ ] }),
2119
+ /* @__PURE__ */ jsx25("div", { className: "medo-drawer__body", children: typeof children === "function" ? children(forceClose) : children }),
2120
+ footer && /* @__PURE__ */ jsx25("div", { className: "medo-drawer__footer", children: typeof footer === "function" ? footer(forceClose, close) : footer }),
2121
+ confirmDiscard
2122
+ ] }) }) }) });
2123
+ }
2124
+ function DrawerDialog({ children, side, ...props }) {
2125
+ const ref = useRef9(null);
2126
+ return /* @__PURE__ */ jsx25(AriaDialog3, { ...props, ref, className: "medo-drawer__dialog", children: ({ close }) => /* @__PURE__ */ jsxs24(Fragment14, { children: [
2127
+ /* @__PURE__ */ jsx25(SwipeToClose, { target: ref, side, onClose: close }),
2128
+ children(close)
2129
+ ] }) });
2130
+ }
2131
+ var CLOSE_DISTANCE2 = 0.25;
2132
+ var CLOSE_VELOCITY2 = 0.5;
2133
+ var START = 10;
2134
+ function SwipeToClose({ target, side, onClose }) {
2135
+ useEffect8(() => {
2136
+ const el = target.current;
2137
+ const drawer = el?.parentElement;
2138
+ if (!el || !drawer) return;
2139
+ const dir = side === "right" ? 1 : -1;
2140
+ let start = null;
2141
+ let dragging = false;
2142
+ const down = (e) => {
2143
+ if (e.pointerType === "mouse") return;
2144
+ start = { x: e.clientX, y: e.clientY, t: e.timeStamp, id: e.pointerId };
2145
+ dragging = false;
2146
+ };
2147
+ const move = (e) => {
2148
+ if (!start || e.pointerId !== start.id) return;
2149
+ const dx = (e.clientX - start.x) * dir;
2150
+ const dy = e.clientY - start.y;
2151
+ if (!dragging) {
2152
+ if (Math.abs(dx) < START || Math.abs(dx) < Math.abs(dy)) return;
2153
+ if (dx < 0) return void (start = null);
2154
+ dragging = true;
2155
+ el.setPointerCapture(e.pointerId);
2156
+ drawer.setAttribute("data-dragging", "");
2157
+ }
2158
+ drawer.style.transform = `translateX(${Math.max(0, dx) * dir}px)`;
2159
+ };
2160
+ const up = (e) => {
2161
+ if (!start || e.pointerId !== start.id) return;
2162
+ const dx = Math.max(0, (e.clientX - start.x) * dir);
2163
+ const velocity = dx / Math.max(1, e.timeStamp - start.t);
2164
+ const wasDragging = dragging;
2165
+ start = null;
2166
+ dragging = false;
2167
+ if (!wasDragging) return;
2168
+ drawer.removeAttribute("data-dragging");
2169
+ drawer.style.transform = "";
2170
+ if (dx > drawer.offsetWidth * CLOSE_DISTANCE2 || velocity > CLOSE_VELOCITY2 && dx > 24) onClose();
2171
+ };
2172
+ el.addEventListener("pointerdown", down);
2173
+ el.addEventListener("pointermove", move);
2174
+ el.addEventListener("pointerup", up);
2175
+ el.addEventListener("pointercancel", up);
2176
+ return () => {
2177
+ el.removeEventListener("pointerdown", down);
2178
+ el.removeEventListener("pointermove", move);
2179
+ el.removeEventListener("pointerup", up);
2180
+ el.removeEventListener("pointercancel", up);
2181
+ };
2182
+ }, [target, side, onClose]);
2183
+ return null;
2184
+ }
2185
+
2186
+ // src/navigation/Navigation.tsx
2187
+ import { Fragment as Fragment15, jsx as jsx26, jsxs as jsxs25 } from "react/jsx-runtime";
2188
+ var NavVariantContext = createContext7("sidebar");
2189
+ var SideNavContext = createContext7(null);
2190
+ var TABLET_QUERY = "(min-width: 640px) and (max-width: 1023.98px)";
2191
+ var SITE_MENU_QUERY = "(max-width: 767.98px)";
2192
+ function NavItem({ children, icon, isCurrent, count, countLabel, unread, ...props }) {
2193
+ const variant = useContext14(NavVariantContext);
2194
+ const sideNav = useContext14(SideNavContext);
2195
+ const rail = variant === "rail";
2196
+ const content = /* @__PURE__ */ jsxs25(Fragment15, { children: [
2197
+ icon && variant !== "header" && /* @__PURE__ */ jsx26("span", { className: "medo-nav-item__icon", "aria-hidden": "true", children: icon }),
2198
+ /* @__PURE__ */ jsx26("span", { className: rail ? "medo-visually-hidden" : "medo-nav-item__label", children }),
2199
+ count !== void 0 && (rail ? unread && count > 0 && /* @__PURE__ */ jsx26(BadgeDot, { tone: "danger", label: countLabel ?? String(count), className: "medo-nav-item__dot" }) : /* @__PURE__ */ jsx26(BadgeCount, { count, tone: "neutral", size: "sm", label: countLabel ?? String(count) }))
2200
+ ] });
2201
+ const linkProps = {
2202
+ ...props,
2203
+ onPress: (e) => {
2204
+ props.onPress?.(e);
2205
+ sideNav?.onNavigate?.();
2206
+ },
2207
+ "aria-current": isCurrent ? "page" : void 0,
2208
+ className: cx("medo-nav-item", `medo-nav-item--${variant}`)
2209
+ };
2210
+ return /* @__PURE__ */ jsx26("li", { className: "medo-nav__entry", children: rail ? (
2211
+ // Collapsed: the label appears as tooltip on hover and focus.
2212
+ /* @__PURE__ */ jsxs25(TooltipTrigger, { children: [
2213
+ /* @__PURE__ */ jsx26(TooltipLink, { ...linkProps, children: content }),
2214
+ /* @__PURE__ */ jsx26(Tooltip, { placement: "end", children })
2215
+ ] })
2216
+ ) : /* @__PURE__ */ jsx26(AriaLink3, { ...linkProps, children: content }) });
2217
+ }
2218
+ function containsCurrent(children) {
2219
+ return Children2.toArray(children).some((c) => isValidElement2(c) && c.props.isCurrent);
2220
+ }
2221
+ function NavGroup({ label, icon, children, defaultOpen }) {
2222
+ const variant = useContext14(NavVariantContext);
2223
+ const sideNav = useContext14(SideNavContext);
2224
+ const hasCurrent = containsCurrent(children);
2225
+ const [open, setOpen] = useState10(defaultOpen ?? (variant !== "header" && (hasCurrent || sideNav?.openGroup === label)));
2226
+ const panelId = useId5();
2227
+ const itemRef = useRef10(null);
2228
+ const buttonRef = useRef10(null);
2229
+ const floating = variant === "header";
2230
+ useEffect9(() => {
2231
+ if (!floating || !open) return;
2232
+ const item = itemRef.current;
2233
+ const onKey = (e) => {
2234
+ if (e.key !== "Escape") return;
2235
+ setOpen(false);
2236
+ buttonRef.current?.focus();
2237
+ };
2238
+ const onPointer = (e) => !item?.contains(e.target) && setOpen(false);
2239
+ const onFocusOut = (e) => e.relatedTarget && !item?.contains(e.relatedTarget) && setOpen(false);
2240
+ document.addEventListener("keydown", onKey);
2241
+ document.addEventListener("pointerdown", onPointer);
2242
+ item?.addEventListener("focusout", onFocusOut);
2243
+ return () => {
2244
+ document.removeEventListener("keydown", onKey);
2245
+ document.removeEventListener("pointerdown", onPointer);
2246
+ item?.removeEventListener("focusout", onFocusOut);
2247
+ };
2248
+ }, [floating, open]);
2249
+ if (variant === "rail") {
2250
+ return /* @__PURE__ */ jsx26("li", { className: "medo-nav__entry", children: /* @__PURE__ */ jsxs25(TooltipTrigger, { children: [
2251
+ /* @__PURE__ */ jsxs25(
2252
+ AriaButton8,
2253
+ {
2254
+ className: "medo-nav-item medo-nav-item--rail",
2255
+ "data-current": hasCurrent || void 0,
2256
+ onPress: () => {
2257
+ setOpen(true);
2258
+ sideNav?.expand(label);
2259
+ },
2260
+ children: [
2261
+ /* @__PURE__ */ jsx26("span", { className: "medo-nav-item__icon", "aria-hidden": "true", children: icon }),
2262
+ /* @__PURE__ */ jsx26("span", { className: "medo-visually-hidden", children: label })
2263
+ ]
2264
+ }
2265
+ ),
2266
+ /* @__PURE__ */ jsx26(Tooltip, { placement: "end", children: label })
2267
+ ] }) });
2268
+ }
2269
+ return /* @__PURE__ */ jsxs25("li", { ref: itemRef, className: cx("medo-nav__entry", "medo-nav-group", floating && "medo-nav-group--floating"), children: [
2270
+ /* @__PURE__ */ jsxs25(
2271
+ AriaButton8,
2272
+ {
2273
+ ref: buttonRef,
2274
+ "aria-expanded": open,
2275
+ "aria-controls": panelId,
2276
+ "data-current": hasCurrent && !open || void 0,
2277
+ onPress: () => setOpen((o) => !o),
2278
+ className: cx("medo-nav-item", `medo-nav-item--${variant}`, "medo-nav-group__button"),
2279
+ children: [
2280
+ icon && !floating && /* @__PURE__ */ jsx26("span", { className: "medo-nav-item__icon", "aria-hidden": "true", children: icon }),
2281
+ /* @__PURE__ */ jsx26("span", { className: "medo-nav-item__label", children: label }),
2282
+ /* @__PURE__ */ jsx26(ChevronDown3, { className: "medo-nav-group__chevron", "aria-hidden": "true" })
2283
+ ]
2284
+ }
2285
+ ),
2286
+ /* @__PURE__ */ jsx26(NavVariantContext.Provider, { value: floating ? "sidebar" : variant, children: /* @__PURE__ */ jsx26("ul", { id: panelId, hidden: !open, className: cx("medo-nav__list", floating ? "medo-nav-group__panel" : "medo-nav-group__list"), children }) })
2287
+ ] });
2288
+ }
2289
+ function NavList({ variant = "sidebar", "aria-label": label, children, className }) {
2290
+ return /* @__PURE__ */ jsx26("nav", { "aria-label": label, className, children: /* @__PURE__ */ jsx26(NavVariantContext.Provider, { value: variant, children: /* @__PURE__ */ jsx26("ul", { className: cx("medo-nav__list", variant === "header" && "medo-nav__list--row", variant === "rail" && "medo-nav__list--rail"), children }) }) });
2291
+ }
2292
+ function NavSection({ title, children }) {
2293
+ const variant = useContext14(NavVariantContext);
2294
+ const id = useId5();
2295
+ return /* @__PURE__ */ jsxs25("li", { className: "medo-nav__entry medo-nav-section", children: [
2296
+ variant === "rail" ? /* @__PURE__ */ jsx26("span", { className: "medo-nav-section__divider", role: "presentation" }) : /* @__PURE__ */ jsx26("span", { id, className: "medo-nav-section__title", children: title }),
2297
+ /* @__PURE__ */ jsx26("ul", { className: "medo-nav__list", "aria-labelledby": variant === "rail" ? void 0 : id, "aria-label": variant === "rail" ? title : void 0, children })
2298
+ ] });
2299
+ }
2300
+ function SkipLink({ href = "#main", children = "Skip to content" }) {
2301
+ return /* @__PURE__ */ jsx26("a", { href, className: "medo-skip-link", children });
2302
+ }
2303
+ function ActionLink({ action, size }) {
2304
+ return /* @__PURE__ */ jsx26(AriaLink3, { href: action.href, onPress: action.onPress, className: cx("medo-button", `medo-button--${action.variant ?? "ghost"}`, `medo-button--${size}`), children: /* @__PURE__ */ jsx26("span", { className: "medo-button__label", children: action.label }) });
2305
+ }
2306
+ function SiteHeader({ logo, children, actions = [], "aria-label": label = "Main", menuLabel = "Menu", closeLabel = "Close", layout = "auto" }) {
2307
+ const narrow = useMediaQuery(SITE_MENU_QUERY);
2308
+ const compact = layout === "mobile" || layout === "auto" && narrow;
2309
+ const scrolled = useScrolled();
2310
+ const primary = actions.find((a) => a.variant === "primary");
2311
+ return /* @__PURE__ */ jsxs25("header", { className: cx("medo-site-header", compact && "medo-site-header--compact"), "data-scrolled": scrolled || void 0, children: [
2312
+ /* @__PURE__ */ jsx26("div", { className: "medo-site-header__logo", children: logo }),
2313
+ compact ? /* @__PURE__ */ jsxs25("div", { className: "medo-site-header__actions", children: [
2314
+ primary && /* @__PURE__ */ jsx26(ActionLink, { action: primary, size: "sm" }),
2315
+ /* @__PURE__ */ jsx26(NavFullscreen, { logo, actions, "aria-label": label, menuLabel, closeLabel, children })
2316
+ ] }) : /* @__PURE__ */ jsxs25(Fragment15, { children: [
2317
+ /* @__PURE__ */ jsx26("nav", { "aria-label": label, className: "medo-site-header__nav", children: /* @__PURE__ */ jsx26(NavVariantContext.Provider, { value: "header", children: /* @__PURE__ */ jsx26("ul", { className: "medo-nav__list medo-nav__list--row", children }) }) }),
2318
+ actions.length > 0 && /* @__PURE__ */ jsx26("div", { className: "medo-site-header__actions", children: actions.map((a) => /* @__PURE__ */ jsx26(ActionLink, { action: a, size: "md" }, a.label)) })
2319
+ ] })
2320
+ ] });
2321
+ }
2322
+ function NavFullscreen({
2323
+ logo,
2324
+ actions,
2325
+ children,
2326
+ "aria-label": label,
2327
+ menuLabel,
2328
+ closeLabel
2329
+ }) {
2330
+ const ordered = [...actions].sort((a, b) => Number(b.variant === "primary") - Number(a.variant === "primary"));
2331
+ return /* @__PURE__ */ jsxs25(DialogTrigger3, { children: [
2332
+ /* @__PURE__ */ jsx26(IconButton, { variant: "ghost", size: "md", icon: /* @__PURE__ */ jsx26(MenuIcon, {}), "aria-label": menuLabel, tooltip: false }),
2333
+ /* @__PURE__ */ jsx26(ModalOverlay4, { className: "medo-nav-fullscreen-overlay", children: /* @__PURE__ */ jsx26(Modal4, { className: "medo-nav-fullscreen", children: /* @__PURE__ */ jsx26(AriaDialog4, { "aria-label": menuLabel, className: "medo-nav-fullscreen__dialog", children: ({ close }) => /* @__PURE__ */ jsxs25(Fragment15, { children: [
2334
+ /* @__PURE__ */ jsxs25("div", { className: "medo-nav-fullscreen__header", children: [
2335
+ /* @__PURE__ */ jsx26("div", { className: "medo-site-header__logo", children: logo }),
2336
+ /* @__PURE__ */ jsx26(IconButton, { variant: "ghost", size: "md", icon: /* @__PURE__ */ jsx26(X10, {}), "aria-label": closeLabel, tooltip: false, onPress: close })
2337
+ ] }),
2338
+ /* @__PURE__ */ jsx26("nav", { "aria-label": label, className: "medo-nav-fullscreen__nav", children: /* @__PURE__ */ jsx26(NavVariantContext.Provider, { value: "sheet", children: /* @__PURE__ */ jsx26("ul", { className: "medo-nav__list", children }) }) }),
2339
+ ordered.length > 0 && /* @__PURE__ */ jsx26("div", { className: "medo-nav-fullscreen__actions", children: ordered.map((a) => /* @__PURE__ */ jsx26(ActionLink, { action: a.variant === "ghost" || !a.variant ? { ...a, variant: "secondary" } : a, size: "lg" }, a.label)) })
2340
+ ] }) }) }) })
2341
+ ] });
2342
+ }
2343
+ function AppHeader({ title, headingLevel = 1, logo, search, actions }) {
2344
+ const scrolled = useScrolled();
2345
+ return /* @__PURE__ */ jsxs25("header", { className: "medo-app-header", "data-scrolled": scrolled || void 0, children: [
2346
+ logo && /* @__PURE__ */ jsx26("div", { className: "medo-app-header__logo", children: logo }),
2347
+ /* @__PURE__ */ jsx26(Heading5, { level: headingLevel, className: "medo-app-header__title", children: title }),
2348
+ search && /* @__PURE__ */ jsx26("div", { className: "medo-app-header__search", children: search }),
2349
+ actions && /* @__PURE__ */ jsx26("div", { className: "medo-app-header__actions", children: actions })
2350
+ ] });
2351
+ }
2352
+ function SideNav({
2353
+ logo,
2354
+ logoMark,
2355
+ children,
2356
+ user,
2357
+ isCollapsed,
2358
+ defaultCollapsed = false,
2359
+ onCollapsedChange,
2360
+ "aria-label": label = "App",
2361
+ collapseLabel = "Collapse navigation",
2362
+ expandLabel = "Expand navigation",
2363
+ closeLabel = "Close"
2364
+ }) {
2365
+ const [ownCollapsed, setOwnCollapsed] = useState10(defaultCollapsed);
2366
+ const tablet = useMediaQuery(TABLET_QUERY);
2367
+ const [drawer, setDrawer] = useState10(false);
2368
+ const [openGroup, setOpenGroup] = useState10();
2369
+ const collapsed = tablet || (isCollapsed ?? ownCollapsed);
2370
+ const expand = (group) => {
2371
+ setOpenGroup(group);
2372
+ if (tablet) return setDrawer(true);
2373
+ if (isCollapsed === void 0) setOwnCollapsed(false);
2374
+ onCollapsedChange?.(false);
2375
+ };
2376
+ const collapse = () => {
2377
+ if (isCollapsed === void 0) setOwnCollapsed(true);
2378
+ onCollapsedChange?.(true);
2379
+ };
2380
+ useEffect9(() => {
2381
+ if (!tablet) setDrawer(false);
2382
+ }, [tablet]);
2383
+ const toggle = /* @__PURE__ */ jsx26(
2384
+ IconButton,
2385
+ {
2386
+ variant: "ghost",
2387
+ size: "sm",
2388
+ icon: /* @__PURE__ */ jsx26(PanelLeft, {}),
2389
+ "aria-label": collapsed ? expandLabel : collapseLabel,
2390
+ "aria-expanded": tablet ? drawer : !collapsed,
2391
+ onPress: () => collapsed ? expand() : collapse()
2392
+ }
2393
+ );
2394
+ return /* @__PURE__ */ jsxs25("aside", { className: cx("medo-sidenav", collapsed && "medo-sidenav--collapsed"), children: [
2395
+ /* @__PURE__ */ jsxs25("div", { className: "medo-sidenav__panel", children: [
2396
+ /* @__PURE__ */ jsxs25("div", { className: "medo-sidenav__top", children: [
2397
+ /* @__PURE__ */ jsx26("div", { className: "medo-sidenav__logo", children: collapsed ? logoMark : logo }),
2398
+ !collapsed && toggle
2399
+ ] }),
2400
+ /* @__PURE__ */ jsx26("nav", { "aria-label": label, className: "medo-sidenav__nav", children: /* @__PURE__ */ jsx26(SideNavContext.Provider, { value: { expand, openGroup }, children: /* @__PURE__ */ jsx26(NavVariantContext.Provider, { value: collapsed ? "rail" : "sidebar", children: /* @__PURE__ */ jsx26("ul", { className: "medo-nav__list", children }) }) }) }),
2401
+ /* @__PURE__ */ jsxs25("div", { className: "medo-sidenav__bottom", children: [
2402
+ collapsed && toggle,
2403
+ user && /* @__PURE__ */ jsxs25("div", { className: "medo-sidenav__user", children: [
2404
+ /* @__PURE__ */ jsx26(Avatar, { name: user.name, src: user.src, size: "md", decorative: !collapsed }),
2405
+ !collapsed && /* @__PURE__ */ jsxs25("span", { className: "medo-sidenav__user-text", children: [
2406
+ /* @__PURE__ */ jsx26("span", { className: "medo-sidenav__user-name", children: user.name }),
2407
+ user.email && /* @__PURE__ */ jsx26("span", { className: "medo-sidenav__user-email", children: user.email })
2408
+ ] })
2409
+ ] })
2410
+ ] })
2411
+ ] }),
2412
+ tablet && // Tablet: the full navigation as a Drawer on the left. Choosing a page closes it.
2413
+ /* @__PURE__ */ jsx26(Drawer, { side: "left", size: "sm", title: logo, "aria-label": label, closeLabel, isOpen: drawer, onOpenChange: setDrawer, children: /* @__PURE__ */ jsx26("nav", { "aria-label": label, children: /* @__PURE__ */ jsx26(SideNavContext.Provider, { value: { expand, openGroup, onNavigate: () => setDrawer(false) }, children: /* @__PURE__ */ jsx26(NavVariantContext.Provider, { value: "sidebar", children: /* @__PURE__ */ jsx26("ul", { className: "medo-nav__list", children }) }) }) }) })
2414
+ ] });
2415
+ }
2416
+ function BottomTabBar({ children, "aria-label": label = "App", display = "mobile" }) {
2417
+ return /* @__PURE__ */ jsx26("nav", { "aria-label": label, className: cx("medo-tab-bar", display === "always" && "medo-tab-bar--always"), children: /* @__PURE__ */ jsx26("ul", { className: "medo-tab-bar__list", children }) });
2418
+ }
2419
+ function TabBarItem({ children, icon, isCurrent, dot, dotLabel = "new", ...props }) {
2420
+ return /* @__PURE__ */ jsx26("li", { className: "medo-tab-bar__entry", children: /* @__PURE__ */ jsxs25(AriaLink3, { ...props, "aria-current": isCurrent ? "page" : void 0, className: "medo-tab-bar-item", children: [
2421
+ /* @__PURE__ */ jsx26(TabBarIcon, { icon, dot, dotLabel }),
2422
+ /* @__PURE__ */ jsx26("span", { className: "medo-tab-bar-item__label", children })
2423
+ ] }) });
2424
+ }
2425
+ function TabBarIcon({ icon, dot, dotLabel }) {
2426
+ return /* @__PURE__ */ jsxs25("span", { className: "medo-tab-bar-item__indicator", children: [
2427
+ /* @__PURE__ */ jsx26("span", { className: "medo-tab-bar-item__icon", "aria-hidden": "true", children: icon }),
2428
+ dot && /* @__PURE__ */ jsx26(BadgeDot, { tone: "danger", label: dotLabel, className: "medo-tab-bar-item__dot" })
2429
+ ] });
2430
+ }
2431
+ function TabBarMore({ label = "More", children, closeLabel }) {
2432
+ const hasCurrent = containsCurrent(children);
2433
+ return /* @__PURE__ */ jsx26("li", { className: "medo-tab-bar__entry", children: /* @__PURE__ */ jsxs25(DialogTrigger3, { children: [
2434
+ /* @__PURE__ */ jsxs25(AriaButton8, { className: "medo-tab-bar-item", "aria-haspopup": "dialog", "data-current": hasCurrent || void 0, children: [
2435
+ /* @__PURE__ */ jsx26(TabBarIcon, { icon: /* @__PURE__ */ jsx26(Ellipsis2, {}) }),
2436
+ /* @__PURE__ */ jsx26("span", { className: "medo-tab-bar-item__label", children: label })
2437
+ ] }),
2438
+ /* @__PURE__ */ jsx26(BottomSheet, { title: label, closeLabel, children: /* @__PURE__ */ jsx26("nav", { "aria-label": label, className: "medo-nav-sheet", children: /* @__PURE__ */ jsx26(NavVariantContext.Provider, { value: "sheet", children: /* @__PURE__ */ jsx26("ul", { className: "medo-nav__list", children }) }) }) })
2439
+ ] }) });
2440
+ }
2441
+ function useScrolled() {
2442
+ const [scrolled, setScrolled] = useState10(false);
2443
+ useEffect9(() => {
2444
+ const update = () => setScrolled(window.scrollY > 0);
2445
+ update();
2446
+ window.addEventListener("scroll", update, { passive: true });
2447
+ return () => window.removeEventListener("scroll", update);
2448
+ }, []);
2449
+ return scrolled;
2450
+ }
2451
+
2452
+ // src/list/List.tsx
2453
+ import { createContext as createContext8, useContext as useContext15 } from "react";
2454
+ import { Check as Check5, X as X11 } from "lucide-react";
2455
+ import { jsx as jsx27, jsxs as jsxs26 } from "react/jsx-runtime";
2456
+ var ListTypeContext = createContext8("bullet");
2457
+ function List({ type = "bullet", size, start, className, style, children, ...props }) {
2458
+ const Tag2 = type === "number" ? "ol" : "ul";
2459
+ const counter = type === "number" && start !== void 0 ? { counterReset: `medo-list ${start - 1}` } : void 0;
2460
+ return /* @__PURE__ */ jsx27(ListTypeContext.Provider, { value: type, children: /* @__PURE__ */ jsx27(
2461
+ Tag2,
2462
+ {
2463
+ ...props,
2464
+ start: type === "number" ? start : void 0,
2465
+ style: { ...counter, ...style },
2466
+ role: "list",
2467
+ className: cx("medo-list", `medo-list--${type}`, size && `medo-list--${size}`, className),
2468
+ children
2469
+ }
2470
+ ) });
2471
+ }
2472
+ function ListItem({ children, excluded, excludedLabel = "Not included:", className, ...props }) {
2473
+ const type = useContext15(ListTypeContext);
2474
+ const check = type === "check";
2475
+ return /* @__PURE__ */ jsxs26("li", { ...props, className: cx("medo-list__item", check && excluded && "medo-list__item--excluded", className), children: [
2476
+ check && /* @__PURE__ */ jsx27("span", { className: "medo-list__icon", "aria-hidden": "true", children: excluded ? /* @__PURE__ */ jsx27(X11, {}) : /* @__PURE__ */ jsx27(Check5, {}) }),
2477
+ check && excluded && /* @__PURE__ */ jsxs26("span", { className: "medo-visually-hidden", children: [
2478
+ excludedLabel,
2479
+ " "
2480
+ ] }),
2481
+ children
2482
+ ] });
2483
+ }
2484
+ function DescriptionList({ layout = "auto", className, children, ...props }) {
2485
+ return /* @__PURE__ */ jsx27("dl", { ...props, className: cx("medo-dl", `medo-dl--${layout}`, className), children });
2486
+ }
2487
+ function DescriptionItem({ term, children }) {
2488
+ return /* @__PURE__ */ jsxs26("div", { className: "medo-dl__row", children: [
2489
+ /* @__PURE__ */ jsx27("dt", { className: "medo-dl__term", children: term }),
2490
+ /* @__PURE__ */ jsx27("dd", { className: "medo-dl__value", children })
2491
+ ] });
2492
+ }
2493
+
2494
+ // src/list-view/ListView.tsx
2495
+ import { useEffect as useEffect10, useRef as useRef11, useState as useState11 } from "react";
2496
+ import { GridList, GridListItem, useLocale } from "react-aria-components";
2497
+ import { ChevronRight as ChevronRight2 } from "lucide-react";
2498
+ import { Fragment as Fragment16, jsx as jsx28, jsxs as jsxs27 } from "react/jsx-runtime";
2499
+ function ListView({ variant = "plain", isLoading = false, emptyState, className, children, items, ...props }) {
2500
+ if (isLoading) {
2501
+ return /* @__PURE__ */ jsx28("div", { role: "list", "aria-busy": "true", "aria-label": props["aria-label"], className: cx("medo-list-view", `medo-list-view--${variant}`, className), children: Array.from(
2502
+ { length: 3 },
2503
+ (_, i) => variant === "cards" ? /* @__PURE__ */ jsx28("div", { role: "listitem", className: "medo-list-card medo-list-card--loading", children: /* @__PURE__ */ jsxs27("span", { className: "medo-list-card__content", children: [
2504
+ /* @__PURE__ */ jsx28(Skeleton, { width: "50%" }),
2505
+ /* @__PURE__ */ jsx28(Skeleton, { width: "35%" }),
2506
+ /* @__PURE__ */ jsx28(Skeleton, { width: "70%" })
2507
+ ] }) }, i) : /* @__PURE__ */ jsxs27("div", { role: "listitem", className: "medo-list-row medo-list-row--loading", children: [
2508
+ /* @__PURE__ */ jsx28("span", { className: "medo-list-row__leading", children: /* @__PURE__ */ jsx28(Skeleton, { shape: "circle", width: 40, height: 40 }) }),
2509
+ /* @__PURE__ */ jsx28("span", { className: "medo-list-row__main", children: /* @__PURE__ */ jsxs27("span", { className: "medo-list-row__content", children: [
2510
+ /* @__PURE__ */ jsx28(Skeleton, { width: "40%" }),
2511
+ /* @__PURE__ */ jsx28(Skeleton, { width: "70%" })
2512
+ ] }) })
2513
+ ] }, i)
2514
+ ) });
2515
+ }
2516
+ return /* @__PURE__ */ jsx28(
2517
+ GridList,
2518
+ {
2519
+ ...props,
2520
+ items,
2521
+ keyboardNavigationBehavior: "tab",
2522
+ renderEmptyState: emptyState ? () => /* @__PURE__ */ jsx28("div", { className: "medo-list-view__empty", children: emptyState }) : void 0,
2523
+ className: cx("medo-list-view", `medo-list-view--${variant}`, className),
2524
+ children
2525
+ }
2526
+ );
2527
+ }
2528
+ function ListRow({ title, description, lines, leading, meta, trailing, chevron, unread, unreadLabel = "Unread", swipeActions, ...props }) {
2529
+ const rowLines = description ? lines ?? 2 : 1;
2530
+ const swipe = swipeActions?.start || swipeActions?.end;
2531
+ return /* @__PURE__ */ jsx28(
2532
+ GridListItem,
2533
+ {
2534
+ ...props,
2535
+ textValue: title,
2536
+ className: cx("medo-list-row", `medo-list-row--lines-${rowLines}`, unread && "medo-list-row--unread", swipe && "medo-list-row--swipe"),
2537
+ children: ({ selectionMode }) => {
2538
+ const content = /* @__PURE__ */ jsxs27(Fragment16, { children: [
2539
+ selectionMode !== "none" && /* @__PURE__ */ jsx28("span", { className: "medo-list-row__check", children: /* @__PURE__ */ jsx28(Checkbox, { slot: "selection" }) }),
2540
+ leading && // Avatars size themselves: sm in 1-line rows, lg in 2–3 lines.
2541
+ /* @__PURE__ */ jsx28(AvatarSizeContext.Provider, { value: rowLines === 1 ? "sm" : "lg", children: /* @__PURE__ */ jsx28("span", { className: "medo-list-row__leading", children: leading }) }),
2542
+ /* @__PURE__ */ jsxs27("span", { className: "medo-list-row__main", children: [
2543
+ /* @__PURE__ */ jsxs27("span", { className: "medo-list-row__content", children: [
2544
+ /* @__PURE__ */ jsxs27("span", { className: "medo-list-row__title", children: [
2545
+ unread && /* @__PURE__ */ jsxs27("span", { className: "medo-visually-hidden", children: [
2546
+ unreadLabel,
2547
+ ": "
2548
+ ] }),
2549
+ title
2550
+ ] }),
2551
+ description && /* @__PURE__ */ jsx28("span", { className: "medo-list-row__description", children: description })
2552
+ ] }),
2553
+ (meta || unread) && /* @__PURE__ */ jsxs27("span", { className: "medo-list-row__meta", children: [
2554
+ meta && /* @__PURE__ */ jsx28("span", { className: "medo-list-row__meta-text", children: meta }),
2555
+ unread && /* @__PURE__ */ jsx28("span", { className: "medo-list-row__dot", "aria-hidden": "true" })
2556
+ ] }),
2557
+ trailing && /* @__PURE__ */ jsx28("span", { className: "medo-list-row__trailing", children: trailing }),
2558
+ chevron && /* @__PURE__ */ jsx28("span", { className: "medo-list-row__chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx28(ChevronRight2, {}) })
2559
+ ] })
2560
+ ] });
2561
+ return swipe ? /* @__PURE__ */ jsx28(Swipeable, { actions: swipeActions, children: content }) : content;
2562
+ }
2563
+ }
2564
+ );
2565
+ }
2566
+ var SWIPE = {
2567
+ action: 88,
2568
+ // revealed action width – snaps open from here
2569
+ lock: 8,
2570
+ // px before the direction is decided
2571
+ full: 0.5,
2572
+ // share of the row width that triggers at once
2573
+ flick: 0.5
2574
+ // px per ms
2575
+ };
2576
+ var closeOpenRow = null;
2577
+ function Swipeable({ actions, children }) {
2578
+ const { direction } = useLocale();
2579
+ const rtl = direction === "rtl";
2580
+ const surface = useRef11(null);
2581
+ const drag = useRef11(null);
2582
+ const [offset, setOffset] = useState11(0);
2583
+ const [dragging, setDragging] = useState11(false);
2584
+ const [leaving, setLeaving] = useState11(false);
2585
+ const open = offset !== 0 && !dragging;
2586
+ const close = () => setOffset(0);
2587
+ useEffect10(() => {
2588
+ if (!open) return;
2589
+ closeOpenRow?.();
2590
+ closeOpenRow = close;
2591
+ const row = surface.current?.closest(".medo-list-row");
2592
+ const onDown = (e) => !row?.contains(e.target) && close();
2593
+ const onKey = (e) => e.key === "Escape" && close();
2594
+ document.addEventListener("pointerdown", onDown, true);
2595
+ window.addEventListener("scroll", close, true);
2596
+ document.addEventListener("keydown", onKey);
2597
+ return () => {
2598
+ if (closeOpenRow === close) closeOpenRow = null;
2599
+ document.removeEventListener("pointerdown", onDown, true);
2600
+ window.removeEventListener("scroll", close, true);
2601
+ document.removeEventListener("keydown", onKey);
2602
+ };
2603
+ }, [open]);
2604
+ const width = () => surface.current?.getBoundingClientRect().width ?? 0;
2605
+ const actionFor = (value) => value < 0 ? actions.end : value > 0 ? actions.start : void 0;
2606
+ const trigger = (action, side) => {
2607
+ setLeaving(true);
2608
+ setOffset(side * width());
2609
+ window.setTimeout(() => {
2610
+ action.onAction();
2611
+ setLeaving(false);
2612
+ setOffset(0);
2613
+ }, 250);
2614
+ };
2615
+ const onPointerDown = (e) => {
2616
+ if (e.pointerType === "mouse") return;
2617
+ drag.current = { x: e.clientX, y: e.clientY, t: e.timeStamp, base: offset, lock: null, id: e.pointerId, buzzed: false };
2618
+ };
2619
+ const onPointerMove = (e) => {
2620
+ const d = drag.current;
2621
+ if (!d || e.pointerId !== d.id) return;
2622
+ const dx = (e.clientX - d.x) * (rtl ? -1 : 1);
2623
+ const dy = e.clientY - d.y;
2624
+ if (!d.lock) {
2625
+ if (Math.abs(dy) > SWIPE.lock && Math.abs(dy) > Math.abs(dx)) d.lock = "y";
2626
+ else if (Math.abs(dx) > SWIPE.lock) {
2627
+ d.lock = "x";
2628
+ setDragging(true);
2629
+ surface.current?.setPointerCapture(e.pointerId);
2630
+ e.currentTarget.dispatchEvent(new PointerEvent("pointercancel", { bubbles: true, pointerId: e.pointerId, pointerType: e.pointerType }));
2631
+ }
2632
+ }
2633
+ if (d.lock !== "x") return;
2634
+ let next = d.base + dx;
2635
+ if (!actionFor(next)) next = next / 4;
2636
+ const past = Math.abs(next) >= width() * SWIPE.full && !!actionFor(next);
2637
+ if (past && !d.buzzed) navigator.vibrate?.(10);
2638
+ d.buzzed = past;
2639
+ setOffset(next);
2640
+ };
2641
+ const onPointerEnd = (e) => {
2642
+ const d = drag.current;
2643
+ drag.current = null;
2644
+ if (!d || d.lock !== "x") return;
2645
+ setDragging(false);
2646
+ const action = actionFor(offset);
2647
+ const velocity = (e.clientX - d.x) * (rtl ? -1 : 1) / Math.max(1, e.timeStamp - d.t);
2648
+ const side = Math.sign(offset);
2649
+ if (!action) return close();
2650
+ if (Math.abs(offset) >= width() * SWIPE.full || Math.abs(velocity) > SWIPE.flick && Math.sign(velocity) === side && Math.abs(offset) > SWIPE.action / 2)
2651
+ return trigger(action, side);
2652
+ setOffset(Math.abs(offset) >= SWIPE.action ? side * SWIPE.action : 0);
2653
+ };
2654
+ const revealed = actionFor(offset);
2655
+ return /* @__PURE__ */ jsxs27(Fragment16, { children: [
2656
+ revealed && // Visual shortcut for touch; keyboard and screen readers use the „…“ menu, so it is hidden from them.
2657
+ /* @__PURE__ */ jsx28(
2658
+ "span",
2659
+ {
2660
+ className: cx(
2661
+ "medo-list-row__swipe-action",
2662
+ `medo-list-row__swipe-action--${offset < 0 ? "end" : "start"}`,
2663
+ `medo-list-row__swipe-action--${revealed.tone ?? "primary"}`
2664
+ ),
2665
+ style: { inlineSize: Math.abs(offset) },
2666
+ "aria-hidden": "true",
2667
+ onPointerDown: (e) => e.stopPropagation(),
2668
+ onClick: (e) => {
2669
+ e.stopPropagation();
2670
+ trigger(revealed, Math.sign(offset));
2671
+ },
2672
+ children: /* @__PURE__ */ jsxs27("span", { className: "medo-list-row__swipe-label", children: [
2673
+ revealed.icon,
2674
+ revealed.label
2675
+ ] })
2676
+ }
2677
+ ),
2678
+ /* @__PURE__ */ jsxs27(
2679
+ "span",
2680
+ {
2681
+ ref: surface,
2682
+ className: cx("medo-list-row__surface", dragging && "medo-list-row__surface--dragging", leaving && "medo-list-row__surface--leaving"),
2683
+ style: { transform: offset ? `translateX(${rtl ? -offset : offset}px)` : void 0 },
2684
+ onPointerDown,
2685
+ onPointerMove,
2686
+ onPointerUp: onPointerEnd,
2687
+ onPointerCancel: (e) => {
2688
+ if (!e.isTrusted) return;
2689
+ onPointerEnd(e);
2690
+ },
2691
+ children: [
2692
+ children,
2693
+ open && /* @__PURE__ */ jsx28(
2694
+ "span",
2695
+ {
2696
+ className: "medo-list-row__swipe-shield",
2697
+ "aria-hidden": "true",
2698
+ onPointerDown: (e) => {
2699
+ e.stopPropagation();
2700
+ close();
2701
+ }
2702
+ }
2703
+ )
2704
+ ]
2705
+ }
2706
+ )
2707
+ ] });
2708
+ }
2709
+ function ListRowIcon({ children }) {
2710
+ return /* @__PURE__ */ jsx28("span", { className: "medo-list-row__icon", "aria-hidden": "true", children });
2711
+ }
2712
+ function ListCard({ title, amount, description, meta, status, ...props }) {
2713
+ return /* @__PURE__ */ jsx28(GridListItem, { ...props, textValue: title, className: "medo-list-card", children: ({ selectionMode }) => /* @__PURE__ */ jsxs27(Fragment16, { children: [
2714
+ selectionMode !== "none" && /* @__PURE__ */ jsx28("span", { className: "medo-list-card__check", children: /* @__PURE__ */ jsx28(Checkbox, { slot: "selection", size: "lg" }) }),
2715
+ /* @__PURE__ */ jsxs27("span", { className: "medo-list-card__content", children: [
2716
+ /* @__PURE__ */ jsxs27("span", { className: "medo-list-card__top", children: [
2717
+ /* @__PURE__ */ jsx28("span", { className: "medo-list-card__title", children: title }),
2718
+ amount && /* @__PURE__ */ jsx28("span", { className: "medo-list-card__amount", children: amount })
2719
+ ] }),
2720
+ description && /* @__PURE__ */ jsx28(AvatarSizeContext.Provider, { value: "xs", children: /* @__PURE__ */ jsx28("span", { className: "medo-list-card__description", children: description }) }),
2721
+ (meta || status) && /* @__PURE__ */ jsxs27("span", { className: "medo-list-card__bottom", children: [
2722
+ /* @__PURE__ */ jsx28("span", { className: "medo-list-card__meta", children: meta }),
2723
+ status
2724
+ ] })
2725
+ ] })
2726
+ ] }) });
2727
+ }
2728
+
2729
+ // src/bulk-action-bar/BulkActionBar.tsx
2730
+ import { useEffect as useEffect11, useRef as useRef12 } from "react";
2731
+ import { Button as AriaButton9, Toolbar as Toolbar2 } from "react-aria-components";
2732
+ import { Ellipsis as Ellipsis3, X as X12 } from "lucide-react";
2733
+ import { Fragment as Fragment17, jsx as jsx29, jsxs as jsxs28 } from "react/jsx-runtime";
2734
+ function BulkActionBar({
2735
+ count,
2736
+ actions,
2737
+ onClear,
2738
+ variant = "auto",
2739
+ countLabel = (n) => `${n} selected`,
2740
+ clearLabel = "Clear selection",
2741
+ moreLabel = "More actions",
2742
+ "aria-label": ariaLabel = "Bulk actions",
2743
+ className
2744
+ }) {
2745
+ const mobile = useMediaQuery(MOBILE_QUERY);
2746
+ const isMobile = variant === "mobile" || variant === "auto" && mobile;
2747
+ const visible = count > 0;
2748
+ const returnTo = useRef12(null);
2749
+ const focusInside = useRef12(false);
2750
+ useEffect11(() => {
2751
+ if (visible || !focusInside.current) return;
2752
+ focusInside.current = false;
2753
+ if (returnTo.current?.isConnected) returnTo.current.focus();
2754
+ }, [visible]);
2755
+ const shown = actions.slice(0, 3);
2756
+ const more = actions.slice(3);
2757
+ const onKeyDown = (e) => {
2758
+ if (e.key === "Escape" && !e.defaultPrevented) {
2759
+ e.preventDefault();
2760
+ onClear();
2761
+ }
2762
+ };
2763
+ const overflow = more.length > 0 && (isMobile ? /* @__PURE__ */ jsxs28(MenuTrigger, { children: [
2764
+ /* @__PURE__ */ jsx29(MenuButton, { "aria-label": moreLabel, size: "lg" }),
2765
+ /* @__PURE__ */ jsx29(OverflowMenu, { actions: more })
2766
+ ] }) : /* @__PURE__ */ jsxs28(MenuTrigger, { children: [
2767
+ /* @__PURE__ */ jsx29(AriaButton9, { "aria-label": moreLabel, className: "medo-toast__button medo-toast__close", children: /* @__PURE__ */ jsx29(Ellipsis3, { "aria-hidden": "true" }) }),
2768
+ /* @__PURE__ */ jsx29(OverflowMenu, { actions: more })
2769
+ ] }));
2770
+ return /* @__PURE__ */ jsxs28(Fragment17, { children: [
2771
+ /* @__PURE__ */ jsx29("span", { role: "status", className: "medo-visually-hidden", children: visible ? countLabel(count) : "" }),
2772
+ visible && // The wrapper handles Esc and remembers where focus came from (events bubble from the toolbar).
2773
+ /* @__PURE__ */ jsx29(
2774
+ "div",
2775
+ {
2776
+ className: "medo-bulk-bar-host",
2777
+ onKeyDown,
2778
+ onFocus: (e) => {
2779
+ focusInside.current = true;
2780
+ const from = e.relatedTarget;
2781
+ if (from && !e.currentTarget.contains(from)) returnTo.current = from;
2782
+ },
2783
+ onBlur: (e) => {
2784
+ if (!e.currentTarget.contains(e.relatedTarget)) focusInside.current = false;
2785
+ },
2786
+ children: /* @__PURE__ */ jsx29(Toolbar2, { "aria-label": ariaLabel, className: cx("medo-bulk-bar", isMobile ? "medo-bulk-bar--mobile" : "medo-bulk-bar--desktop", className), children: isMobile ? /* @__PURE__ */ jsxs28(Fragment17, { children: [
2787
+ /* @__PURE__ */ jsxs28("span", { className: "medo-bulk-bar__start", children: [
2788
+ /* @__PURE__ */ jsx29(IconButton, { variant: "ghost", size: "lg", icon: /* @__PURE__ */ jsx29(X12, {}), "aria-label": clearLabel, tooltip: false, onPress: onClear }),
2789
+ /* @__PURE__ */ jsx29("span", { className: "medo-bulk-bar__count", children: countLabel(count) })
2790
+ ] }),
2791
+ /* @__PURE__ */ jsxs28("span", { className: "medo-bulk-bar__actions", children: [
2792
+ shown.map((a) => /* @__PURE__ */ jsx29(Button, { variant: a.variant === "danger" ? "ghost-danger" : "ghost", size: "lg", onPress: a.onAction, children: a.label }, a.label)),
2793
+ overflow
2794
+ ] })
2795
+ ] }) : /* @__PURE__ */ jsxs28(Fragment17, { children: [
2796
+ /* @__PURE__ */ jsx29("span", { className: "medo-bulk-bar__count", children: countLabel(count) }),
2797
+ /* @__PURE__ */ jsx29("span", { className: "medo-bulk-bar__divider", "aria-hidden": "true" }),
2798
+ shown.map((a) => /* @__PURE__ */ jsx29(AriaButton9, { className: "medo-toast__button medo-toast__action", onPress: a.onAction, children: a.label }, a.label)),
2799
+ overflow,
2800
+ /* @__PURE__ */ jsx29("span", { className: "medo-bulk-bar__divider", "aria-hidden": "true" }),
2801
+ /* @__PURE__ */ jsx29(AriaButton9, { "aria-label": clearLabel, className: "medo-toast__button medo-toast__close", onPress: onClear, children: /* @__PURE__ */ jsx29(X12, { "aria-hidden": "true" }) })
2802
+ ] }) })
2803
+ }
2804
+ )
2805
+ ] });
2806
+ }
2807
+ function OverflowMenu({ actions }) {
2808
+ return /* @__PURE__ */ jsx29(Menu, { onAction: (key) => actions[Number(key)]?.onAction(), children: actions.map((a, i) => /* @__PURE__ */ jsx29(MenuItem, { id: i, icon: a.icon, variant: a.variant === "danger" ? "danger" : "default", children: a.label }, i)) });
2809
+ }
2810
+
2811
+ // src/empty-state/EmptyState.tsx
2812
+ import { useId as useId6 } from "react";
2813
+ import { CircleAlert as CircleAlert4, CircleCheck, Folder, Lock, Search as Search2 } from "lucide-react";
2814
+ import { jsx as jsx30, jsxs as jsxs29 } from "react/jsx-runtime";
2815
+ var defaults = {
2816
+ "first-use": { icon: /* @__PURE__ */ jsx30(Folder, {}), tone: "neutral" },
2817
+ "no-results": { icon: /* @__PURE__ */ jsx30(Search2, {}), tone: "neutral" },
2818
+ error: { icon: /* @__PURE__ */ jsx30(CircleAlert4, {}), tone: "danger" },
2819
+ "no-access": { icon: /* @__PURE__ */ jsx30(Lock, {}), tone: "neutral" },
2820
+ "all-done": { icon: /* @__PURE__ */ jsx30(CircleCheck, {}), tone: "success" }
2821
+ };
2822
+ var tileSize = { page: "lg", section: "md", inline: "sm" };
2823
+ function EmptyState({
2824
+ type = "first-use",
2825
+ size = "page",
2826
+ title,
2827
+ description,
2828
+ action,
2829
+ secondaryAction,
2830
+ icon,
2831
+ illustration,
2832
+ headingLevel,
2833
+ className,
2834
+ ...props
2835
+ }) {
2836
+ const titleId = useId6();
2837
+ const { icon: defaultIcon, tone } = defaults[type];
2838
+ const isInline = size === "inline";
2839
+ const Tag2 = isInline ? "div" : "section";
2840
+ const Title = isInline ? "p" : `h${headingLevel ?? (size === "page" ? 2 : 3)}`;
2841
+ const buttonSize = size === "page" ? "md" : "sm";
2842
+ return /* @__PURE__ */ jsxs29(Tag2, { ...props, "aria-labelledby": isInline ? void 0 : titleId, className: cx("medo-empty-state", `medo-empty-state--${size}`, className), children: [
2843
+ illustration ? /* @__PURE__ */ jsx30("span", { className: "medo-empty-state__illustration", "aria-hidden": "true", children: illustration }) : /* @__PURE__ */ jsx30(IconTile, { size: tileSize[size], tone, children: icon ?? defaultIcon }),
2844
+ /* @__PURE__ */ jsxs29("div", { className: "medo-empty-state__content", role: type === "no-results" ? "status" : void 0, children: [
2845
+ /* @__PURE__ */ jsx30(Title, { id: titleId, className: "medo-empty-state__title", children: title }),
2846
+ description && /* @__PURE__ */ jsx30("p", { className: "medo-empty-state__description", children: description })
2847
+ ] }),
2848
+ (action || secondaryAction) && /* @__PURE__ */ jsxs29("div", { className: "medo-empty-state__actions", children: [
2849
+ action && /* @__PURE__ */ jsx30(Button, { variant: type === "first-use" ? "primary" : "secondary", size: buttonSize, onPress: action.onPress, isDisabled: action.isDisabled, children: action.label }),
2850
+ secondaryAction && (secondaryAction.href ? /* @__PURE__ */ jsx30(Link, { variant: "standalone", href: secondaryAction.href, isDisabled: secondaryAction.isDisabled, children: secondaryAction.label }) : /* @__PURE__ */ jsx30(Button, { variant: "text", size: buttonSize, onPress: secondaryAction.onPress, isDisabled: secondaryAction.isDisabled, children: secondaryAction.label }))
2851
+ ] })
2852
+ ] });
2853
+ }
2854
+ function IconTile({ size = "md", tone = "neutral", children }) {
2855
+ return /* @__PURE__ */ jsx30("span", { className: cx("medo-icon-tile", `medo-icon-tile--${size}`, `medo-icon-tile--${tone}`), "aria-hidden": "true", children });
2856
+ }
2857
+
2858
+ // src/pagination/Pagination.tsx
2859
+ import { useId as useId7, useRef as useRef13, useState as useState12 } from "react";
2860
+ import { Button as AriaButton10, Link as AriaLink4 } from "react-aria-components";
2861
+ import { ChevronLeft as ChevronLeft2, ChevronRight as ChevronRight3 } from "lucide-react";
2862
+ import { Fragment as Fragment18, jsx as jsx31, jsxs as jsxs30 } from "react/jsx-runtime";
2863
+ function getPageItems(page, totalPages) {
2864
+ if (totalPages <= 7) return Array.from({ length: totalPages }, (_, i) => i + 1);
2865
+ if (page <= 4) return [1, 2, 3, 4, 5, "ellipsis", totalPages];
2866
+ if (page >= totalPages - 3) return [1, "ellipsis", ...Array.from({ length: 5 }, (_, i) => totalPages - 4 + i)];
2867
+ return [1, "ellipsis", page - 1, page, page + 1, "ellipsis", totalPages];
2868
+ }
2869
+ function Pagination({
2870
+ page: pageProp,
2871
+ defaultPage = 1,
2872
+ onChange,
2873
+ getPageHref,
2874
+ totalPages: totalPagesProp,
2875
+ totalItems,
2876
+ pageSize = 20,
2877
+ onPageSizeChange,
2878
+ pageSizeOptions = [10, 20, 50, 100],
2879
+ variant = "auto",
2880
+ size = "md",
2881
+ "aria-label": ariaLabel = "Pagination",
2882
+ summary = (from, to, total) => `${from}\u2013${to} of ${total}`,
2883
+ pageSizeLabel = "Rows per page",
2884
+ previousLabel = "Previous page",
2885
+ nextLabel = "Next page",
2886
+ pageLabel = (p) => `Page ${p}`,
2887
+ compactLabel = (p, total) => `Page ${p} of ${total}`,
2888
+ className
2889
+ }) {
2890
+ const [innerPage, setInnerPage] = useState12(defaultPage);
2891
+ const mobile = useMediaQuery(MOBILE_QUERY);
2892
+ const totalPages = totalPagesProp ?? (totalItems !== void 0 ? Math.ceil(totalItems / pageSize) : 1);
2893
+ const page = Math.min(Math.max(pageProp ?? innerPage, 1), Math.max(totalPages, 1));
2894
+ const compact = variant === "compact" || variant === "auto" && mobile;
2895
+ const itemSize = compact ? "lg" : size;
2896
+ const statusId = useId7();
2897
+ const navRef = useRef13(null);
2898
+ if (totalPages <= 1) return null;
2899
+ const go = (p) => {
2900
+ if (pageProp === void 0) setInnerPage(p);
2901
+ onChange?.(p);
2902
+ requestAnimationFrame(() => {
2903
+ const nav = navRef.current;
2904
+ if (!nav || document.activeElement && document.activeElement !== document.body) return;
2905
+ (nav.querySelector('[aria-current="page"]') ?? nav.querySelector(".medo-button:not([data-disabled])"))?.focus();
2906
+ });
2907
+ };
2908
+ const item = (target, props) => getPageHref ? /* @__PURE__ */ jsx31(AriaLink4, { ...props, href: props.isDisabled ? void 0 : getPageHref(target), onPress: () => go(target) }) : /* @__PURE__ */ jsx31(AriaButton10, { ...props, onPress: () => go(target) });
2909
+ const step = (target, label, icon) => item(target, {
2910
+ className: cx("medo-button", "medo-button--icon-only", "medo-button--ghost", `medo-button--${itemSize}`),
2911
+ "aria-label": label,
2912
+ isDisabled: target < 1 || target > totalPages,
2913
+ children: /* @__PURE__ */ jsx31("span", { className: "medo-button__icon", "aria-hidden": "true", children: icon })
2914
+ });
2915
+ const from = (page - 1) * pageSize + 1;
2916
+ const to = totalItems !== void 0 ? Math.min(page * pageSize, totalItems) : page * pageSize;
2917
+ return /* @__PURE__ */ jsx31("nav", { ref: navRef, "aria-label": ariaLabel, className: cx("medo-pagination", compact ? "medo-pagination--compact" : "medo-pagination--full", className), children: compact ? /* @__PURE__ */ jsxs30(Fragment18, { children: [
2918
+ step(page - 1, previousLabel, /* @__PURE__ */ jsx31(ChevronLeft2, {})),
2919
+ /* @__PURE__ */ jsx31("p", { role: "status", className: "medo-pagination__compact-label", children: compactLabel(page, totalPages) }),
2920
+ step(page + 1, nextLabel, /* @__PURE__ */ jsx31(ChevronRight3, {}))
2921
+ ] }) : /* @__PURE__ */ jsxs30(Fragment18, { children: [
2922
+ (totalItems !== void 0 || onPageSizeChange) && /* @__PURE__ */ jsxs30("div", { className: "medo-pagination__info", children: [
2923
+ totalItems !== void 0 && /* @__PURE__ */ jsx31("p", { id: statusId, role: "status", className: "medo-pagination__summary", children: summary(from, to, totalItems) }),
2924
+ onPageSizeChange && /* @__PURE__ */ jsx31(
2925
+ Select,
2926
+ {
2927
+ label: pageSizeLabel,
2928
+ size: "sm",
2929
+ selectedKey: pageSize,
2930
+ onSelectionChange: (key) => key != null && onPageSizeChange(Number(key)),
2931
+ className: "medo-pagination__size",
2932
+ children: pageSizeOptions.map((n) => /* @__PURE__ */ jsx31(Option, { id: n, children: String(n) }, n))
2933
+ }
2934
+ )
2935
+ ] }),
2936
+ /* @__PURE__ */ jsxs30("ul", { className: "medo-pagination__pages", role: "list", children: [
2937
+ /* @__PURE__ */ jsx31("li", { children: step(page - 1, previousLabel, /* @__PURE__ */ jsx31(ChevronLeft2, {})) }),
2938
+ getPageItems(page, totalPages).map(
2939
+ (p, i) => p === "ellipsis" ? (
2940
+ // Text, not a button – hidden from screen readers (the page names say where they jump).
2941
+ /* @__PURE__ */ jsx31("li", { className: cx("medo-page-item", "medo-page-item--ellipsis", `medo-page-item--${itemSize}`), "aria-hidden": "true", children: "\u2026" }, `e${i}`)
2942
+ ) : /* @__PURE__ */ jsx31("li", { children: item(p, {
2943
+ className: cx("medo-button", "medo-button--ghost", `medo-button--${itemSize}`, "medo-page-item", `medo-page-item--${itemSize}`),
2944
+ "aria-label": pageLabel(p),
2945
+ "aria-current": p === page ? "page" : void 0,
2946
+ children: p
2947
+ }) }, p)
2948
+ ),
2949
+ /* @__PURE__ */ jsx31("li", { children: step(page + 1, nextLabel, /* @__PURE__ */ jsx31(ChevronRight3, {})) })
2950
+ ] })
2951
+ ] }) });
2952
+ }
2953
+
2954
+ // src/search/SearchField.tsx
2955
+ import { Button as AriaButton11, Input as AriaInput2, SearchField as AriaSearchField } from "react-aria-components";
2956
+ import { Search as Search3, X as X13 } from "lucide-react";
2957
+ import { jsx as jsx32, jsxs as jsxs31 } from "react/jsx-runtime";
2958
+ function SearchField({ label, description, placeholder, size = "md", clearLabel = "Clear search", className, ...props }) {
2959
+ return /* @__PURE__ */ jsxs31(AriaSearchField, { ...props, className: cx("medo-field", "medo-search-field", className), children: [
2960
+ label && /* @__PURE__ */ jsx32(FieldLabel, { isRequired: props.isRequired, children: label }),
2961
+ /* @__PURE__ */ jsxs31("div", { className: cx("medo-input", `medo-input--${size}`), children: [
2962
+ /* @__PURE__ */ jsx32("span", { className: "medo-input__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx32(Search3, {}) }),
2963
+ /* @__PURE__ */ jsx32(AriaInput2, { className: "medo-input__control", placeholder }),
2964
+ /* @__PURE__ */ jsx32(AriaButton11, { "aria-label": clearLabel, className: "medo-input__action medo-search-field__clear", children: /* @__PURE__ */ jsx32(X13, {}) })
2965
+ ] }),
2966
+ description && /* @__PURE__ */ jsx32(FieldFooter, { description })
2967
+ ] });
2968
+ }
2969
+
2970
+ // src/search/SearchResult.tsx
2971
+ import { Link as AriaLink5 } from "react-aria-components";
2972
+ import { FileText, Globe } from "lucide-react";
2973
+ import { jsx as jsx33, jsxs as jsxs32 } from "react/jsx-runtime";
2974
+ var icons = { page: /* @__PURE__ */ jsx33(Globe, {}), document: /* @__PURE__ */ jsx33(FileText, {}) };
2975
+ function SearchResult({
2976
+ type = "page",
2977
+ variant = "base",
2978
+ title,
2979
+ href,
2980
+ query,
2981
+ path,
2982
+ snippet,
2983
+ badge,
2984
+ meta,
2985
+ leading,
2986
+ headingLevel = 3,
2987
+ linkProps,
2988
+ className
2989
+ }) {
2990
+ const Heading7 = `h${headingLevel}`;
2991
+ const lead = leading ?? (icons[type] && /* @__PURE__ */ jsx33(IconTile, { size: "sm", children: icons[type] }));
2992
+ return /* @__PURE__ */ jsxs32("article", { className: cx("medo-search-result", `medo-search-result--${variant}`, `medo-search-result--${type}`, className), children: [
2993
+ lead && /* @__PURE__ */ jsx33(AvatarSizeContext.Provider, { value: "lg", children: /* @__PURE__ */ jsx33("span", { className: "medo-search-result__leading", children: lead }) }),
2994
+ /* @__PURE__ */ jsxs32("div", { className: "medo-search-result__content", children: [
2995
+ path && /* @__PURE__ */ jsx33("span", { className: "medo-search-result__path", children: path }),
2996
+ /* @__PURE__ */ jsx33(Heading7, { className: "medo-search-result__title", children: /* @__PURE__ */ jsx33(AriaLink5, { ...linkProps, href, className: "medo-search-result__link", children: /* @__PURE__ */ jsx33(Highlight, { text: title, query, all: true, className: "medo-search-result__match" }) }) }),
2997
+ snippet && /* @__PURE__ */ jsx33("p", { className: "medo-search-result__snippet", children: typeof snippet === "string" ? /* @__PURE__ */ jsx33(Highlight, { text: snippet, query, all: true, className: "medo-search-result__match" }) : snippet }),
2998
+ (badge || meta) && /* @__PURE__ */ jsxs32("span", { className: "medo-search-result__meta", children: [
2999
+ badge && /* @__PURE__ */ jsx33(Badge, { tone: "neutral", size: "sm", children: badge }),
3000
+ meta && /* @__PURE__ */ jsx33("span", { className: "medo-search-result__meta-text", children: meta })
3001
+ ] })
3002
+ ] })
3003
+ ] });
3004
+ }
3005
+
3006
+ // src/search/QuickSearch.tsx
3007
+ import { useEffect as useEffect12, useLayoutEffect as useLayoutEffect5, useState as useState13 } from "react";
3008
+ import { Autocomplete as Autocomplete2, Button as AriaButton12, Dialog as Dialog3, Modal as Modal5, ModalOverlay as ModalOverlay5, useFilter as useFilter2 } from "react-aria-components";
3009
+ import { Search as Search4 } from "lucide-react";
3010
+ import { jsx as jsx34, jsxs as jsxs33 } from "react/jsx-runtime";
3011
+ function QuickSearch({
3012
+ isOpen,
3013
+ defaultOpen,
3014
+ onOpenChange,
3015
+ shortcut = true,
3016
+ inputValue,
3017
+ onInputChange,
3018
+ clientFilter = false,
3019
+ children,
3020
+ items,
3021
+ onAction,
3022
+ allResultsHref,
3023
+ onSearch,
3024
+ "aria-label": ariaLabel = "Search",
3025
+ placeholder = "Search pages, files, people \u2026",
3026
+ cancelLabel = "Cancel",
3027
+ suggestionsLabel = "Suggestions",
3028
+ allResultsLabel = (q) => `All results for \u201C${q}\u201D`,
3029
+ noResultsLabel = (q) => `No results for \u201C${q}\u201D. Check the spelling or try other words.`,
3030
+ searchAllLabel = (q) => `Search all content for \u201C${q}\u201D`,
3031
+ hints = { navigate: "Navigate", open: "Open", close: "Close" }
3032
+ }) {
3033
+ const mobile = useMediaQuery(MOBILE_QUERY);
3034
+ const [innerOpen, setInnerOpen] = useState13(defaultOpen ?? false);
3035
+ const open = isOpen ?? innerOpen;
3036
+ const setOpen = (value) => {
3037
+ if (isOpen === void 0) setInnerOpen(value);
3038
+ onOpenChange?.(value);
3039
+ };
3040
+ const [innerQuery, setInnerQuery] = useState13("");
3041
+ const query = inputValue ?? innerQuery;
3042
+ const setQuery = (value) => {
3043
+ if (inputValue === void 0) setInnerQuery(value);
3044
+ onInputChange?.(value);
3045
+ };
3046
+ const { contains } = useFilter2({ sensitivity: "base" });
3047
+ useEffect12(() => {
3048
+ if (!shortcut) return;
3049
+ const onKey = (e) => {
3050
+ if (e.key.toLowerCase() !== "k" || !(e.metaKey || e.ctrlKey) || e.altKey || e.shiftKey) return;
3051
+ const t = e.target;
3052
+ if (t && (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName))) return;
3053
+ e.preventDefault();
3054
+ setOpen(true);
3055
+ };
3056
+ document.addEventListener("keydown", onKey);
3057
+ return () => document.removeEventListener("keydown", onKey);
3058
+ });
3059
+ const term = query.trim();
3060
+ const close = () => setOpen(false);
3061
+ const [empty, setEmpty] = useState13(false);
3062
+ return /* @__PURE__ */ jsx34(
3063
+ ModalOverlay5,
3064
+ {
3065
+ isOpen: open,
3066
+ onOpenChange: setOpen,
3067
+ isDismissable: true,
3068
+ className: cx("medo-dialog-overlay", "medo-quick-search-overlay", mobile && "medo-quick-search-overlay--mobile"),
3069
+ children: /* @__PURE__ */ jsx34(Modal5, { className: cx("medo-dialog", "medo-quick-search", mobile && "medo-quick-search--mobile"), children: /* @__PURE__ */ jsxs33(Dialog3, { "aria-label": ariaLabel, className: "medo-quick-search__dialog", children: [
3070
+ /* @__PURE__ */ jsxs33(Autocomplete2, { inputValue: query, onInputChange: setQuery, filter: clientFilter ? contains : void 0, children: [
3071
+ /* @__PURE__ */ jsxs33("div", { className: "medo-quick-search__bar", children: [
3072
+ /* @__PURE__ */ jsx34(
3073
+ SearchField,
3074
+ {
3075
+ "aria-label": ariaLabel,
3076
+ placeholder,
3077
+ size: "lg",
3078
+ autoFocus: true,
3079
+ onSubmit: (value) => value.trim() && onSearch?.(value.trim()),
3080
+ className: "medo-quick-search__field"
3081
+ }
3082
+ ),
3083
+ mobile && /* @__PURE__ */ jsx34(Button, { variant: "text", size: "lg", onPress: close, children: cancelLabel })
3084
+ ] }),
3085
+ /* @__PURE__ */ jsxs33("div", { className: "medo-quick-search__scroll", tabIndex: 0, "aria-label": suggestionsLabel, role: "region", children: [
3086
+ /* @__PURE__ */ jsx34(OptionHighlightContext.Provider, { value: term, children: /* @__PURE__ */ jsx34(
3087
+ ListBox,
3088
+ {
3089
+ "aria-label": ariaLabel,
3090
+ size: mobile ? "lg" : "md",
3091
+ items,
3092
+ onAction: (key) => {
3093
+ onAction?.(key);
3094
+ close();
3095
+ },
3096
+ className: "medo-quick-search__list",
3097
+ renderEmptyState: () => term ? /* @__PURE__ */ jsx34(EmptyReporter, { onChange: setEmpty, message: noResultsLabel(term) }) : null,
3098
+ children
3099
+ }
3100
+ ) }),
3101
+ empty && term && onSearch && /* @__PURE__ */ jsxs33(AriaButton12, { className: "medo-quick-search__all", onPress: () => onSearch(term), children: [
3102
+ /* @__PURE__ */ jsx34(Search4, { "aria-hidden": "true" }),
3103
+ searchAllLabel(term)
3104
+ ] })
3105
+ ] })
3106
+ ] }),
3107
+ !mobile && /* @__PURE__ */ jsxs33("div", { className: "medo-quick-search__footer", children: [
3108
+ /* @__PURE__ */ jsxs33("span", { className: "medo-quick-search__hint", "aria-hidden": "true", children: [
3109
+ /* @__PURE__ */ jsx34(Badge, { size: "sm", children: "\u2191 \u2193" }),
3110
+ hints.navigate
3111
+ ] }),
3112
+ /* @__PURE__ */ jsxs33("span", { className: "medo-quick-search__hint", "aria-hidden": "true", children: [
3113
+ /* @__PURE__ */ jsx34(Badge, { size: "sm", children: "Enter" }),
3114
+ hints.open
3115
+ ] }),
3116
+ /* @__PURE__ */ jsxs33("span", { className: "medo-quick-search__hint", "aria-hidden": "true", children: [
3117
+ /* @__PURE__ */ jsx34(Badge, { size: "sm", children: "Esc" }),
3118
+ hints.close
3119
+ ] }),
3120
+ term && !empty && allResultsHref && /* @__PURE__ */ jsx34(Link, { variant: "standalone", size: "md", arrow: true, href: allResultsHref(term), className: "medo-quick-search__all-results", onPress: close, children: allResultsLabel(term) })
3121
+ ] })
3122
+ ] }) })
3123
+ }
3124
+ );
3125
+ }
3126
+ function EmptyReporter({ message, onChange }) {
3127
+ useLayoutEffect5(() => {
3128
+ onChange(true);
3129
+ return () => onChange(false);
3130
+ }, [onChange]);
3131
+ return /* @__PURE__ */ jsx34("p", { role: "status", className: "medo-quick-search__message", children: message });
3132
+ }
3133
+
3134
+ // src/breadcrumbs/Breadcrumbs.tsx
3135
+ import { useLayoutEffect as useLayoutEffect6, useRef as useRef14, useState as useState14 } from "react";
3136
+ import { Breadcrumb, Breadcrumbs as AriaBreadcrumbs, Button as AriaButton13, Link as AriaLink6 } from "react-aria-components";
3137
+ import { ChevronLeft as ChevronLeft3, ChevronRight as ChevronRight4, Ellipsis as Ellipsis4, Folder as Folder2, House } from "lucide-react";
3138
+ import { jsx as jsx35, jsxs as jsxs34 } from "react/jsx-runtime";
3139
+ function Breadcrumbs({
3140
+ items,
3141
+ size = "md",
3142
+ homeIcon = true,
3143
+ layout = "auto",
3144
+ "aria-label": ariaLabel = "Breadcrumb",
3145
+ moreLabel = (n) => `Show ${n} more levels`,
3146
+ className
3147
+ }) {
3148
+ const mobile = useMediaQuery(MOBILE_QUERY);
3149
+ const back = layout === "back" || layout === "auto" && mobile;
3150
+ if (items.length < 2) return null;
3151
+ if (back) {
3152
+ const parent = items[items.length - 2];
3153
+ return /* @__PURE__ */ jsx35("nav", { "aria-label": ariaLabel, className: cx("medo-breadcrumbs", `medo-breadcrumbs--${size}`, "medo-breadcrumbs--back", className), children: /* @__PURE__ */ jsxs34(AriaLink6, { href: parent.href, className: "medo-breadcrumbs__link medo-breadcrumbs__back", children: [
3154
+ /* @__PURE__ */ jsx35(ChevronLeft3, { className: "medo-breadcrumbs__back-icon", "aria-hidden": "true" }),
3155
+ /* @__PURE__ */ jsx35(Label3, { text: parent.label })
3156
+ ] }) });
3157
+ }
3158
+ const collapse = items.length >= 5;
3159
+ const hidden = collapse ? items.slice(1, -3) : [];
3160
+ const shown = collapse ? [items[0], null, ...items.slice(-3)] : items;
3161
+ const last = shown.length - 1;
3162
+ return /* @__PURE__ */ jsx35("nav", { "aria-label": ariaLabel, className: cx("medo-breadcrumbs", `medo-breadcrumbs--${size}`, className), children: /* @__PURE__ */ jsx35(AriaBreadcrumbs, { className: "medo-breadcrumbs__list", children: shown.map((item, i) => {
3163
+ const separator = i < last && /* @__PURE__ */ jsx35(ChevronRight4, { className: "medo-breadcrumbs__separator", "aria-hidden": "true" });
3164
+ if (!item) {
3165
+ return /* @__PURE__ */ jsxs34(Breadcrumb, { id: "more", className: "medo-breadcrumbs__item", children: [
3166
+ /* @__PURE__ */ jsxs34(MenuTrigger, { children: [
3167
+ /* @__PURE__ */ jsx35(AriaButton13, { "aria-label": moreLabel(hidden.length), className: "medo-breadcrumbs__icon-part", children: /* @__PURE__ */ jsx35(Ellipsis4, { "aria-hidden": "true" }) }),
3168
+ /* @__PURE__ */ jsx35(Menu, { children: hidden.map((h) => /* @__PURE__ */ jsx35(MenuItem, { id: h.label, href: h.href, icon: /* @__PURE__ */ jsx35(Folder2, {}), children: h.label }, h.label)) })
3169
+ ] }),
3170
+ separator
3171
+ ] }, "more");
3172
+ }
3173
+ const isHome = homeIcon && i === 0;
3174
+ return /* @__PURE__ */ jsxs34(Breadcrumb, { id: `${i}-${item.label}`, className: "medo-breadcrumbs__item", children: [
3175
+ isHome ? /* @__PURE__ */ jsx35(AriaLink6, { href: item.href, "aria-label": item.label, className: "medo-breadcrumbs__icon-part", children: /* @__PURE__ */ jsx35(House, { "aria-hidden": "true" }) }) : i === last ? (
3176
+ // The current page: not a link (React Aria sets aria-current="page").
3177
+ /* @__PURE__ */ jsx35(AriaLink6, { className: "medo-breadcrumbs__link medo-breadcrumbs__current", children: /* @__PURE__ */ jsx35(Label3, { text: item.label }) })
3178
+ ) : /* @__PURE__ */ jsx35(TruncatedLink, { href: item.href, label: item.label }),
3179
+ separator
3180
+ ] }, `${i}-${item.label}`);
3181
+ }) }) });
3182
+ }
3183
+ function Label3({ text }) {
3184
+ return /* @__PURE__ */ jsx35("span", { className: "medo-breadcrumbs__label", children: text });
3185
+ }
3186
+ function TruncatedLink({ href, label }) {
3187
+ const ref = useRef14(null);
3188
+ const [cut, setCut] = useState14(false);
3189
+ useLayoutEffect6(() => {
3190
+ const el = ref.current;
3191
+ if (el) setCut(el.scrollWidth > el.clientWidth);
3192
+ }, [label]);
3193
+ const text = /* @__PURE__ */ jsx35("span", { ref, className: "medo-breadcrumbs__label", children: label });
3194
+ if (!cut) {
3195
+ return /* @__PURE__ */ jsx35(AriaLink6, { href, className: "medo-breadcrumbs__link", children: text });
3196
+ }
3197
+ return /* @__PURE__ */ jsxs34(TooltipTrigger, { children: [
3198
+ /* @__PURE__ */ jsx35(TooltipLink, { href, className: "medo-breadcrumbs__link", children: text }),
3199
+ /* @__PURE__ */ jsx35(Tooltip, { children: label })
3200
+ ] });
3201
+ }
3202
+
3203
+ // src/accordion/Accordion.tsx
3204
+ import { Children as Children3, createContext as createContext9, isValidElement as isValidElement3, useContext as useContext16, useEffect as useEffect13, useId as useId8, useState as useState15 } from "react";
3205
+ import { Button as AriaButton14, Disclosure, DisclosureGroup, DisclosurePanel, Heading as Heading6 } from "react-aria-components";
3206
+ import { ChevronDown as ChevronDown4 } from "lucide-react";
3207
+ import { jsx as jsx36, jsxs as jsxs35 } from "react/jsx-runtime";
3208
+ var AccordionContext = createContext9({ size: "md", headingLevel: 3 });
3209
+ var hashId = () => typeof window === "undefined" ? "" : decodeURIComponent(window.location.hash.slice(1));
3210
+ function Accordion({
3211
+ size = "md",
3212
+ headingLevel = 3,
3213
+ allowsMultipleExpanded = true,
3214
+ expandAll = true,
3215
+ expandAllLabel = "Expand all",
3216
+ collapseAllLabel = "Collapse all",
3217
+ expandedKeys: expandedProp,
3218
+ defaultExpandedKeys,
3219
+ onExpandedChange,
3220
+ children,
3221
+ className,
3222
+ ...props
3223
+ }) {
3224
+ const ids = Children3.toArray(children).filter((c) => isValidElement3(c)).filter((c) => !c.props.isDisabled).map((c) => c.props.id);
3225
+ const [inner, setInner] = useState15(() => {
3226
+ const start = new Set(defaultExpandedKeys ?? []);
3227
+ const hash = hashId();
3228
+ if (ids.includes(hash)) start.add(hash);
3229
+ return start;
3230
+ });
3231
+ const expanded = expandedProp ? new Set(expandedProp) : inner;
3232
+ const setExpanded = (keys) => {
3233
+ if (!expandedProp) setInner(keys);
3234
+ onExpandedChange?.(keys);
3235
+ };
3236
+ useEffect13(() => {
3237
+ const open = () => {
3238
+ const hash = hashId();
3239
+ if (!ids.includes(hash)) return;
3240
+ setExpanded(new Set(allowsMultipleExpanded ? [...expanded, hash] : [hash]));
3241
+ requestAnimationFrame(() => document.getElementById(hash)?.scrollIntoView({ block: "start" }));
3242
+ };
3243
+ if (hashId()) requestAnimationFrame(() => document.getElementById(hashId())?.scrollIntoView({ block: "start" }));
3244
+ window.addEventListener("hashchange", open);
3245
+ return () => window.removeEventListener("hashchange", open);
3246
+ });
3247
+ const allOpen = ids.length > 0 && ids.every((id) => expanded.has(id));
3248
+ const showToggle = expandAll && allowsMultipleExpanded && Children3.count(children) > 6;
3249
+ return /* @__PURE__ */ jsx36(AccordionContext.Provider, { value: { size, headingLevel }, children: /* @__PURE__ */ jsxs35("div", { className: cx("medo-accordion-wrap", className), children: [
3250
+ showToggle && /* @__PURE__ */ jsx36("div", { className: "medo-accordion__toolbar", children: /* @__PURE__ */ jsx36(Button, { variant: "text", size: "sm", onPress: () => setExpanded(allOpen ? /* @__PURE__ */ new Set() : new Set(ids)), children: allOpen ? collapseAllLabel : expandAllLabel }) }),
3251
+ /* @__PURE__ */ jsx36(
3252
+ DisclosureGroup,
3253
+ {
3254
+ ...props,
3255
+ allowsMultipleExpanded,
3256
+ expandedKeys: expanded,
3257
+ onExpandedChange: setExpanded,
3258
+ className: cx("medo-accordion", `medo-accordion--${size}`),
3259
+ children
3260
+ }
3261
+ )
3262
+ ] }) });
3263
+ }
3264
+ function AccordionItem({ id, title, description, icon, badge, isDisabled, children }) {
3265
+ const { headingLevel } = useContext16(AccordionContext);
3266
+ const descId = useId8();
3267
+ return /* @__PURE__ */ jsxs35(Disclosure, { id, isDisabled, className: cx("medo-accordion__item", !!icon && "medo-accordion__item--icon"), children: [
3268
+ /* @__PURE__ */ jsx36(Heading6, { level: headingLevel, id, className: "medo-accordion__heading", children: /* @__PURE__ */ jsxs35(AriaButton14, { slot: "trigger", className: "medo-accordion__trigger", "aria-describedby": description ? descId : void 0, children: [
3269
+ icon && /* @__PURE__ */ jsx36("span", { className: "medo-accordion__icon", "aria-hidden": "true", children: icon }),
3270
+ /* @__PURE__ */ jsxs35("span", { className: "medo-accordion__text", children: [
3271
+ /* @__PURE__ */ jsx36("span", { className: "medo-accordion__title", children: title }),
3272
+ description && // Read as description, not as part of the name.
3273
+ /* @__PURE__ */ jsx36("span", { id: descId, className: "medo-accordion__description", "aria-hidden": "true", children: description })
3274
+ ] }),
3275
+ badge && /* @__PURE__ */ jsx36("span", { className: "medo-accordion__badge", children: badge }),
3276
+ /* @__PURE__ */ jsx36(ChevronDown4, { className: "medo-accordion__chevron", "aria-hidden": "true" })
3277
+ ] }) }),
3278
+ /* @__PURE__ */ jsx36(DisclosurePanel, { className: "medo-accordion__panel", children: /* @__PURE__ */ jsx36("div", { className: "medo-accordion__content", children }) })
3279
+ ] });
3280
+ }
3281
+
3282
+ // src/slider/Slider.tsx
3283
+ import { useMemo } from "react";
3284
+ import { Label as Label4, Slider as AriaSlider, SliderOutput, SliderThumb, SliderTrack, useLocale as useLocale2 } from "react-aria-components";
3285
+
3286
+ // src/number-field/NumberField.tsx
3287
+ import { Input as AriaInput3, NumberField as AriaNumberField } from "react-aria-components";
3288
+ import { jsx as jsx37, jsxs as jsxs36 } from "react/jsx-runtime";
3289
+ function NumberField({ label, description, errorMessage, placeholder, size = "md", className, ...props }) {
3290
+ return /* @__PURE__ */ jsxs36(AriaNumberField, { ...props, className: cx("medo-field", "medo-number-field", className), children: [
3291
+ label && /* @__PURE__ */ jsx37(FieldLabel, { isRequired: props.isRequired, children: label }),
3292
+ /* @__PURE__ */ jsx37("div", { className: cx("medo-input", `medo-input--${size}`), children: /* @__PURE__ */ jsx37(AriaInput3, { className: "medo-input__control medo-number-field__control", placeholder }) }),
3293
+ (description || errorMessage) && /* @__PURE__ */ jsx37(FieldFooter, { description, errorMessage })
3294
+ ] });
3295
+ }
3296
+
3297
+ // src/slider/Slider.tsx
3298
+ import { Fragment as Fragment19, jsx as jsx38, jsxs as jsxs37 } from "react/jsx-runtime";
3299
+ function Slider({
3300
+ label,
3301
+ thumbLabels,
3302
+ showTicks,
3303
+ showMinMax,
3304
+ showValue = true,
3305
+ minValue = 0,
3306
+ maxValue = 100,
3307
+ step = 1,
3308
+ formatOptions,
3309
+ className,
3310
+ ...props
3311
+ }) {
3312
+ const { locale } = useLocale2();
3313
+ const format = useMemo(() => new Intl.NumberFormat(locale, formatOptions), [locale, formatOptions]);
3314
+ const steps = Math.round((maxValue - minValue) / step);
3315
+ const labelText = typeof label === "string" ? label : props["aria-label"] ?? "";
3316
+ return /* @__PURE__ */ jsx38(AriaSlider, { ...props, minValue, maxValue, step, formatOptions, className: cx("medo-slider", className), children: ({ state }) => {
3317
+ const range = state.values.length > 1;
3318
+ const start = range ? state.getThumbPercent(0) : 0;
3319
+ const end = state.getThumbPercent(state.values.length - 1);
3320
+ return /* @__PURE__ */ jsxs37(Fragment19, { children: [
3321
+ (label || showValue) && /* @__PURE__ */ jsxs37("div", { className: "medo-slider__header", children: [
3322
+ label && /* @__PURE__ */ jsx38(Label4, { className: "medo-slider__label", "aria-disabled": props.isDisabled || void 0, children: label }),
3323
+ showValue && /* @__PURE__ */ jsx38(SliderOutput, { className: "medo-slider__value", "aria-disabled": props.isDisabled || void 0 })
3324
+ ] }),
3325
+ /* @__PURE__ */ jsxs37(SliderTrack, { className: "medo-slider__track", children: [
3326
+ /* @__PURE__ */ jsx38("span", { className: "medo-slider__rail", "aria-hidden": "true" }),
3327
+ showTicks && steps <= 10 && /* @__PURE__ */ jsx38("span", { className: "medo-slider__ticks", "aria-hidden": "true", children: Array.from({ length: steps + 1 }, (_, i) => /* @__PURE__ */ jsx38("span", { className: "medo-slider__tick", style: { insetInlineStart: `${i / steps * 100}%` } }, i)) }),
3328
+ /* @__PURE__ */ jsx38("span", { className: "medo-slider__fill", style: { insetInlineStart: `${start * 100}%`, inlineSize: `${(end - start) * 100}%` }, "aria-hidden": "true" }),
3329
+ state.values.map((_, i) => /* @__PURE__ */ jsx38(
3330
+ SliderThumb,
3331
+ {
3332
+ index: i,
3333
+ "aria-label": range ? thumbLabels?.[i] ?? `${i === 0 ? "Minimum" : "Maximum"} ${labelText}`.trim() : void 0,
3334
+ className: "medo-slider__thumb",
3335
+ children: ({ isDragging }) => (
3336
+ // The finger covers the thumb: show the value above it while dragging.
3337
+ isDragging && /* @__PURE__ */ jsx38("span", { className: "medo-tooltip medo-slider__bubble", "aria-hidden": "true", children: state.getThumbValueLabel(i) })
3338
+ )
3339
+ },
3340
+ i
3341
+ ))
3342
+ ] }),
3343
+ showMinMax && /* @__PURE__ */ jsxs37("div", { className: "medo-slider__limits", "aria-hidden": "true", children: [
3344
+ /* @__PURE__ */ jsx38("span", { children: format.format(minValue) }),
3345
+ /* @__PURE__ */ jsx38("span", { children: format.format(maxValue) })
3346
+ ] })
3347
+ ] });
3348
+ } });
3349
+ }
3350
+ function SliderField({ value, onChange, inputLabels, ...props }) {
3351
+ const { minValue = 0, maxValue = 100, step = 1, formatOptions, isDisabled, label, thumbLabels } = props;
3352
+ const labelText = typeof label === "string" ? label : props["aria-label"] ?? "";
3353
+ const field = { step, formatOptions, isDisabled, size: "md" };
3354
+ if (Array.isArray(value)) {
3355
+ const [lo, hi] = value;
3356
+ const names = inputLabels ?? thumbLabels ?? [`Minimum ${labelText}`.trim(), `Maximum ${labelText}`.trim()];
3357
+ return /* @__PURE__ */ jsxs37("div", { className: "medo-slider-field medo-slider-field--range", children: [
3358
+ /* @__PURE__ */ jsx38(Slider, { ...props, value, onChange }),
3359
+ /* @__PURE__ */ jsxs37("div", { className: "medo-slider-field__inputs", children: [
3360
+ /* @__PURE__ */ jsx38(NumberField, { ...field, "aria-label": names[0], minValue, maxValue: hi, value: lo, onChange: (v) => !Number.isNaN(v) && onChange([v, hi]) }),
3361
+ /* @__PURE__ */ jsx38("span", { className: "medo-slider-field__dash", "aria-hidden": "true", children: "\u2013" }),
3362
+ /* @__PURE__ */ jsx38(NumberField, { ...field, "aria-label": names[1], minValue: lo, maxValue, value: hi, onChange: (v) => !Number.isNaN(v) && onChange([lo, v]) })
3363
+ ] })
3364
+ ] });
3365
+ }
3366
+ return /* @__PURE__ */ jsxs37("div", { className: "medo-slider-field medo-slider-field--single", children: [
3367
+ /* @__PURE__ */ jsx38(Slider, { ...props, value, onChange, showValue: false }),
3368
+ /* @__PURE__ */ jsx38(
3369
+ NumberField,
3370
+ {
3371
+ ...field,
3372
+ "aria-label": inputLabels?.[0] ?? labelText,
3373
+ minValue,
3374
+ maxValue,
3375
+ value,
3376
+ onChange: (v) => !Number.isNaN(v) && onChange(v),
3377
+ className: "medo-slider-field__input"
3378
+ }
3379
+ )
3380
+ ] });
3381
+ }
3382
+
3383
+ // src/alert/Alert.tsx
3384
+ import { CircleAlert as CircleAlert5, CircleCheck as CircleCheck2, Info as Info3, TriangleAlert as TriangleAlert2, X as X14 } from "lucide-react";
3385
+ import { jsx as jsx39, jsxs as jsxs38 } from "react/jsx-runtime";
3386
+ var ICONS = {
3387
+ info: Info3,
3388
+ success: CircleCheck2,
3389
+ warning: TriangleAlert2,
3390
+ danger: CircleAlert5
3391
+ };
3392
+ function Alert({
3393
+ tone = "info",
3394
+ variant = "inline",
3395
+ title,
3396
+ children,
3397
+ actions,
3398
+ onDismiss,
3399
+ dismissLabel = "Dismiss",
3400
+ announce,
3401
+ className,
3402
+ ...props
3403
+ }) {
3404
+ const Icon = ICONS[tone];
3405
+ const role = announce ? tone === "danger" || tone === "warning" ? "alert" : "status" : void 0;
3406
+ return /* @__PURE__ */ jsxs38("div", { ...props, role: role ?? props.role, className: cx("medo-alert", `medo-alert--${tone}`, variant === "banner" && "medo-alert--banner", className), children: [
3407
+ /* @__PURE__ */ jsx39(Icon, { className: "medo-alert__icon", "aria-hidden": "true" }),
3408
+ /* @__PURE__ */ jsxs38("div", { className: "medo-alert__content", children: [
3409
+ /* @__PURE__ */ jsx39("p", { className: "medo-alert__title", children: title }),
3410
+ children && /* @__PURE__ */ jsx39("div", { className: "medo-alert__description", children }),
3411
+ actions && /* @__PURE__ */ jsx39("div", { className: "medo-alert__actions", children: actions })
3412
+ ] }),
3413
+ onDismiss && /* @__PURE__ */ jsx39(IconButton, { className: "medo-alert__close", variant: "ghost", size: "sm", icon: /* @__PURE__ */ jsx39(X14, {}), "aria-label": dismissLabel, onPress: onDismiss })
3414
+ ] });
3415
+ }
3416
+
3417
+ // src/card/Card.tsx
3418
+ import { Link as AriaLink7 } from "react-aria-components";
3419
+ import { jsx as jsx40, jsxs as jsxs39 } from "react/jsx-runtime";
3420
+ function Card({
3421
+ title,
3422
+ subtitle,
3423
+ headingLevel = 3,
3424
+ variant = "outlined",
3425
+ density = "standard",
3426
+ layout = "vertical",
3427
+ media,
3428
+ headerActions,
3429
+ footer,
3430
+ href,
3431
+ linkProps,
3432
+ children,
3433
+ className,
3434
+ style
3435
+ }) {
3436
+ const Heading7 = `h${headingLevel}`;
3437
+ const interactive = href !== void 0;
3438
+ return /* @__PURE__ */ jsxs39(
3439
+ "article",
3440
+ {
3441
+ className: cx(
3442
+ "medo-card",
3443
+ `medo-card--${variant}`,
3444
+ density === "compact" && "medo-card--compact",
3445
+ layout === "horizontal" && "medo-card--horizontal",
3446
+ interactive && "medo-card--interactive",
3447
+ className
3448
+ ),
3449
+ style,
3450
+ children: [
3451
+ media && /* @__PURE__ */ jsx40("div", { className: "medo-card__media", children: media }),
3452
+ /* @__PURE__ */ jsxs39("div", { className: "medo-card__main", children: [
3453
+ /* @__PURE__ */ jsxs39("div", { className: "medo-card__header", children: [
3454
+ /* @__PURE__ */ jsxs39("div", { className: "medo-card__heading", children: [
3455
+ /* @__PURE__ */ jsx40(Heading7, { className: "medo-card__title", children: interactive ? /* @__PURE__ */ jsx40(AriaLink7, { ...linkProps, href, className: "medo-card__link", children: title }) : title }),
3456
+ subtitle && /* @__PURE__ */ jsx40("p", { className: "medo-card__subtitle", children: subtitle })
3457
+ ] }),
3458
+ headerActions && /* @__PURE__ */ jsx40("div", { className: "medo-card__header-actions", children: headerActions })
3459
+ ] }),
3460
+ children && /* @__PURE__ */ jsx40("div", { className: "medo-card__body", children }),
3461
+ footer && /* @__PURE__ */ jsx40("div", { className: "medo-card__footer", children: footer })
3462
+ ] })
3463
+ ]
3464
+ }
3465
+ );
3466
+ }
3467
+
3468
+ // src/table/Table.tsx
3469
+ import {
3470
+ Cell as AriaCell,
3471
+ Checkbox as AriaCheckbox,
3472
+ Collection as Collection3,
3473
+ Column as AriaColumn,
3474
+ Row as AriaRow,
3475
+ Table as AriaTable,
3476
+ TableBody as AriaTableBody,
3477
+ TableHeader as AriaTableHeader,
3478
+ useTableOptions
3479
+ } from "react-aria-components";
3480
+ import { ArrowDown, ArrowUp, ArrowUpDown, Check as Check6, Minus as Minus2 } from "lucide-react";
3481
+ import { jsx as jsx41, jsxs as jsxs40 } from "react/jsx-runtime";
3482
+ function StaticTable({ children, caption, captionHidden, density = "standard", striped, plain: plain2, className, style }) {
3483
+ return /* @__PURE__ */ jsx41("div", { className: cx("medo-table-container", plain2 && "medo-table-container--plain", className), style, children: /* @__PURE__ */ jsxs40("table", { className: cx("medo-table", density === "compact" && "medo-table--compact", striped && "medo-table--striped"), children: [
3484
+ caption && /* @__PURE__ */ jsx41("caption", { className: captionHidden ? "medo-visually-hidden" : void 0, children: caption }),
3485
+ children
3486
+ ] }) });
3487
+ }
3488
+ function Table({ density = "standard", striped, plain: plain2, toolbar, footer, className, style, ...props }) {
3489
+ const table = /* @__PURE__ */ jsx41(AriaTable, { ...props, className: cx("medo-table", density === "compact" && "medo-table--compact", striped && "medo-table--striped") });
3490
+ if (!toolbar && !footer) {
3491
+ return /* @__PURE__ */ jsx41("div", { className: cx("medo-table-container", plain2 && "medo-table-container--plain", className), style, children: table });
3492
+ }
3493
+ return /* @__PURE__ */ jsxs40("div", { className: cx("medo-table-frame", plain2 && "medo-table-frame--plain", className), style, children: [
3494
+ toolbar && /* @__PURE__ */ jsx41("div", { className: "medo-table__toolbar", children: toolbar }),
3495
+ /* @__PURE__ */ jsx41("div", { className: "medo-table-container medo-table-container--plain", children: table }),
3496
+ footer && /* @__PURE__ */ jsx41("div", { className: "medo-table__footer", children: footer })
3497
+ ] });
3498
+ }
3499
+ function SelectionCheckbox() {
3500
+ return /* @__PURE__ */ jsx41(AriaCheckbox, { slot: "selection", className: "medo-choice medo-checkbox medo-choice--md medo-choice__button", children: ({ isSelected, isIndeterminate }) => /* @__PURE__ */ jsx41("span", { className: "medo-choice__control", "aria-hidden": "true", children: isIndeterminate ? /* @__PURE__ */ jsx41(Minus2, {}) : isSelected ? /* @__PURE__ */ jsx41(Check6, {}) : null }) });
3501
+ }
3502
+ function TableHeader({ columns, children, ...props }) {
3503
+ const { selectionBehavior, selectionMode, allowsDragging } = useTableOptions();
3504
+ return /* @__PURE__ */ jsxs40(AriaTableHeader, { ...props, className: "medo-table__header", children: [
3505
+ allowsDragging && /* @__PURE__ */ jsx41(AriaColumn, { className: "medo-table__column medo-table__column--utility" }),
3506
+ selectionBehavior === "toggle" && selectionMode !== "none" && /* @__PURE__ */ jsx41(AriaColumn, { className: "medo-table__column medo-table__column--utility", children: selectionMode === "multiple" && /* @__PURE__ */ jsx41(SelectionCheckbox, {}) }),
3507
+ /* @__PURE__ */ jsx41(Collection3, { items: columns, children })
3508
+ ] });
3509
+ }
3510
+ function Column({ children, align = "start", ...props }) {
3511
+ return /* @__PURE__ */ jsx41(AriaColumn, { ...props, "data-align": align, className: "medo-table__column", children: ({ allowsSorting, sortDirection }) => /* @__PURE__ */ jsxs40("span", { className: "medo-table__column-inner", children: [
3512
+ children,
3513
+ allowsSorting && (sortDirection === "ascending" ? /* @__PURE__ */ jsx41(ArrowUp, { className: "medo-table__sort", "aria-hidden": "true" }) : sortDirection === "descending" ? /* @__PURE__ */ jsx41(ArrowDown, { className: "medo-table__sort", "aria-hidden": "true" }) : /* @__PURE__ */ jsx41(ArrowUpDown, { className: "medo-table__sort", "aria-hidden": "true" }))
3514
+ ] }) });
3515
+ }
3516
+ function TableBody(props) {
3517
+ return /* @__PURE__ */ jsx41(AriaTableBody, { ...props, className: "medo-table__body" });
3518
+ }
3519
+ function Row({ id, columns, children, ...props }) {
3520
+ const { selectionBehavior, selectionMode, allowsDragging } = useTableOptions();
3521
+ return /* @__PURE__ */ jsxs40(AriaRow, { id, ...props, className: "medo-table__row", children: [
3522
+ allowsDragging && /* @__PURE__ */ jsx41(AriaCell, { className: "medo-table__cell medo-table__cell--utility" }),
3523
+ selectionBehavior === "toggle" && selectionMode !== "none" && /* @__PURE__ */ jsx41(AriaCell, { className: "medo-table__cell medo-table__cell--utility", children: /* @__PURE__ */ jsx41(SelectionCheckbox, {}) }),
3524
+ /* @__PURE__ */ jsx41(Collection3, { items: columns, children })
3525
+ ] });
3526
+ }
3527
+ function Cell({ align = "start", ...props }) {
3528
+ return /* @__PURE__ */ jsx41(AriaCell, { ...props, "data-align": align, className: "medo-table__cell" });
3529
+ }
3530
+
3531
+ // src/table/ColumnsMenu.tsx
3532
+ import { Columns3 } from "lucide-react";
3533
+ import { jsx as jsx42, jsxs as jsxs41 } from "react/jsx-runtime";
3534
+ function ColumnsMenu({ columns, visibleColumns, onVisibleColumnsChange, label = "Columns", title = "Show columns" }) {
3535
+ const locked = columns.filter((c) => c.isLocked).map((c) => c.id);
3536
+ return /* @__PURE__ */ jsxs41(PopoverTrigger, { children: [
3537
+ /* @__PURE__ */ jsx42(Button, { variant: "ghost", size: "sm", startIcon: /* @__PURE__ */ jsx42(Columns3, {}), children: label }),
3538
+ /* @__PURE__ */ jsx42(Popover5, { size: "sm", "aria-label": title, children: /* @__PURE__ */ jsx42(
3539
+ CheckboxGroup,
3540
+ {
3541
+ label: title,
3542
+ value: visibleColumns,
3543
+ onChange: (ids) => onVisibleColumnsChange(columns.map((c) => c.id).filter((id) => ids.includes(id) || locked.includes(id))),
3544
+ children: columns.map((c) => /* @__PURE__ */ jsx42(Checkbox, { value: c.id, isDisabled: c.isLocked, children: c.label }, c.id))
3545
+ }
3546
+ ) })
3547
+ ] });
3548
+ }
3549
+
3550
+ // src/upload/Upload.tsx
3551
+ import { useEffect as useEffect14, useId as useId9, useLayoutEffect as useLayoutEffect7, useMemo as useMemo2, useRef as useRef15, useState as useState16 } from "react";
3552
+ import {
3553
+ Button as AriaButton15,
3554
+ DropZone,
3555
+ FileTrigger as AriaFileTrigger,
3556
+ Text as Text9,
3557
+ useLocale as useLocale3
3558
+ } from "react-aria-components";
3559
+ import { CircleAlert as CircleAlert6, CircleCheck as CircleCheck3, CloudUpload, FileText as FileText2, Image as ImageIcon, RotateCw, Trash2, Upload, X as X15 } from "lucide-react";
3560
+ import { Fragment as Fragment20, jsx as jsx43, jsxs as jsxs42 } from "react/jsx-runtime";
3561
+ var units = ["byte", "kilobyte", "megabyte", "gigabyte"];
3562
+ function unitOf(bytes) {
3563
+ let i = 0;
3564
+ let value = bytes;
3565
+ while (value >= 1e3 && i < units.length - 1) {
3566
+ value /= 1e3;
3567
+ i++;
3568
+ }
3569
+ return { value, i, digits: i < 2 ? 0 : 1 };
3570
+ }
3571
+ function formatBytes(bytes, locale = "en-US") {
3572
+ const { value, i, digits } = unitOf(bytes);
3573
+ return new Intl.NumberFormat(locale, { style: "unit", unit: units[i], unitDisplay: "short", maximumFractionDigits: digits }).format(value);
3574
+ }
3575
+ function formatLoaded(loaded, total, locale) {
3576
+ const a = unitOf(loaded);
3577
+ if (a.i !== unitOf(total).i) return formatBytes(loaded, locale);
3578
+ return new Intl.NumberFormat(locale, { maximumFractionDigits: a.digits }).format(a.value);
3579
+ }
3580
+ function isAccepted(file, accepted) {
3581
+ if (!accepted?.length) return true;
3582
+ const name = file.name.toLowerCase();
3583
+ const type = file.type.toLowerCase();
3584
+ return accepted.some((a) => {
3585
+ const rule = a.trim().toLowerCase();
3586
+ if (rule.startsWith(".")) return name.endsWith(rule);
3587
+ if (rule.endsWith("/*")) return type.startsWith(rule.slice(0, -1));
3588
+ return type === rule;
3589
+ });
3590
+ }
3591
+ function FileName({ name, className }) {
3592
+ const keep = 12;
3593
+ const start = name.length > keep + 4 ? name.slice(0, -keep) : name;
3594
+ const end = name.length > keep + 4 ? name.slice(-keep) : "";
3595
+ return /* @__PURE__ */ jsxs42("span", { className: cx("medo-file-name", className), children: [
3596
+ /* @__PURE__ */ jsx43("span", { className: "medo-visually-hidden", children: name }),
3597
+ /* @__PURE__ */ jsxs42("span", { className: "medo-file-name__visual", title: name, "aria-hidden": "true", children: [
3598
+ /* @__PURE__ */ jsx43("span", { className: "medo-file-name__start", children: start }),
3599
+ end && /* @__PURE__ */ jsx43("span", { className: "medo-file-name__end", children: end })
3600
+ ] })
3601
+ ] });
3602
+ }
3603
+ var defaultUploadTexts = {
3604
+ title: "Drag and drop files here",
3605
+ dropTitle: "Drop files to upload",
3606
+ chooseFiles: "Choose files",
3607
+ chooseFile: "Choose file",
3608
+ listLabel: "Uploaded files",
3609
+ cancel: (name) => `Cancel upload of ${name}`,
3610
+ remove: (name) => `Remove ${name}`,
3611
+ retry: (name) => `Retry ${name}`,
3612
+ retryShort: "Retry",
3613
+ uploading: (name) => `Uploading ${name}`,
3614
+ progress: (loaded, total, percent) => `${loaded} of ${total} \xB7 ${percent} %`,
3615
+ typeNotAllowed: (names, types) => `${names} ${names.includes(",") ? "are" : "is"} not allowed.${types ? ` Use ${types}.` : ""}`,
3616
+ tooLarge: (size, max) => `File is ${size} \u2013 max. ${max} allowed`,
3617
+ tooMany: (max) => `You can add up to ${max} files. Remove one to add another.`,
3618
+ uploaded: (name) => `${name} uploaded`,
3619
+ failed: (name, error) => `${name}: ${error}`
3620
+ };
3621
+ var isImage = (file) => !!file.previewUrl && (!file.type || file.type.startsWith("image/"));
3622
+ var percentOf = (file) => file.loaded != null && file.size > 0 ? Math.min(100, Math.round(file.loaded / file.size * 100)) : void 0;
3623
+ function FileItem({ file, onRemove, onCancel, onRetry, texts: own, className }) {
3624
+ const t = { ...defaultUploadTexts, ...own };
3625
+ const { locale } = useLocale3();
3626
+ const percent = percentOf(file);
3627
+ const size = formatBytes(file.size, locale);
3628
+ let meta;
3629
+ if (file.status === "uploading") {
3630
+ meta = /* @__PURE__ */ jsxs42(Fragment20, { children: [
3631
+ /* @__PURE__ */ jsx43("span", { className: "medo-file-item__meta", children: percent != null ? t.progress(formatLoaded(file.loaded ?? 0, file.size, locale), size, percent) : size }),
3632
+ /* @__PURE__ */ jsx43(
3633
+ ProgressBar,
3634
+ {
3635
+ size: "sm",
3636
+ showValue: false,
3637
+ "aria-label": t.uploading(file.name),
3638
+ value: percent,
3639
+ isIndeterminate: percent == null,
3640
+ className: "medo-file-item__progress"
3641
+ }
3642
+ )
3643
+ ] });
3644
+ } else {
3645
+ const error = file.status === "error";
3646
+ meta = /* @__PURE__ */ jsxs42("span", { className: cx("medo-file-item__meta", error && "medo-file-item__meta--error"), children: [
3647
+ error ? /* @__PURE__ */ jsx43(CircleAlert6, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx43(CircleCheck3, { "aria-hidden": "true", className: "medo-file-item__done" }),
3648
+ /* @__PURE__ */ jsx43("span", { children: error ? file.error : size })
3649
+ ] });
3650
+ }
3651
+ return /* @__PURE__ */ jsxs42("div", { className: cx("medo-file-item", `medo-file-item--${file.status}`, className), children: [
3652
+ isImage(file) ? /* @__PURE__ */ jsx43("img", { className: "medo-file-item__thumb", src: file.previewUrl, alt: "" }) : /* @__PURE__ */ jsx43("span", { className: "medo-file-item__icon", "aria-hidden": "true", children: file.type?.startsWith("image/") ? /* @__PURE__ */ jsx43(ImageIcon, {}) : /* @__PURE__ */ jsx43(FileText2, {}) }),
3653
+ /* @__PURE__ */ jsxs42("div", { className: "medo-file-item__content", children: [
3654
+ /* @__PURE__ */ jsx43(FileName, { name: file.name, className: "medo-file-item__name" }),
3655
+ meta
3656
+ ] }),
3657
+ /* @__PURE__ */ jsxs42("div", { className: "medo-file-item__actions", children: [
3658
+ file.status === "uploading" && /* @__PURE__ */ jsx43(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx43(X15, {}), "aria-label": t.cancel(file.name), onPress: () => (onCancel ?? onRemove)(file.id) }),
3659
+ file.status === "error" && onRetry && file.canRetry !== false && /* @__PURE__ */ jsx43(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx43(RotateCw, {}), "aria-label": t.retry(file.name), onPress: () => onRetry(file.id) }),
3660
+ file.status !== "uploading" && /* @__PURE__ */ jsx43(IconButton, { size: "sm", icon: file.status === "done" ? /* @__PURE__ */ jsx43(Trash2, {}) : /* @__PURE__ */ jsx43(X15, {}), "aria-label": t.remove(file.name), onPress: () => onRemove(file.id) })
3661
+ ] })
3662
+ ] });
3663
+ }
3664
+ function FileTile({ file, onRemove, onCancel, onRetry, texts: own, className }) {
3665
+ const t = { ...defaultUploadTexts, ...own };
3666
+ const percent = percentOf(file);
3667
+ const retry = file.status === "error" && onRetry && file.canRetry !== false;
3668
+ return /* @__PURE__ */ jsxs42("div", { className: cx("medo-file-tile", `medo-file-tile--${file.status}`, className), children: [
3669
+ /* @__PURE__ */ jsxs42("div", { className: "medo-file-tile__media", children: [
3670
+ file.previewUrl ? /* @__PURE__ */ jsx43("img", { className: "medo-file-tile__image", src: file.previewUrl, alt: "" }) : /* @__PURE__ */ jsx43("span", { className: "medo-file-tile__placeholder", "aria-hidden": "true", children: /* @__PURE__ */ jsx43(ImageIcon, {}) }),
3671
+ file.status === "uploading" && /* @__PURE__ */ jsxs42("div", { className: "medo-file-tile__overlay", children: [
3672
+ /* @__PURE__ */ jsx43("span", { className: "medo-file-tile__percent", "aria-hidden": "true", children: percent != null ? `${percent} %` : "" }),
3673
+ /* @__PURE__ */ jsx43(
3674
+ ProgressBar,
3675
+ {
3676
+ size: "sm",
3677
+ showValue: false,
3678
+ "aria-label": t.uploading(file.name),
3679
+ value: percent,
3680
+ isIndeterminate: percent == null,
3681
+ className: "medo-file-tile__progress"
3682
+ }
3683
+ )
3684
+ ] }),
3685
+ file.status === "error" && (retry ? /* @__PURE__ */ jsxs42(AriaButton15, { className: "medo-file-tile__overlay medo-file-tile__retry", "aria-label": t.retry(file.name), onPress: () => onRetry(file.id), children: [
3686
+ /* @__PURE__ */ jsx43(CircleAlert6, { "aria-hidden": "true" }),
3687
+ /* @__PURE__ */ jsx43("span", { "aria-hidden": "true", children: t.retryShort })
3688
+ ] }) : /* @__PURE__ */ jsx43("div", { className: "medo-file-tile__overlay", "aria-hidden": "true", children: /* @__PURE__ */ jsx43(CircleAlert6, {}) })),
3689
+ /* @__PURE__ */ jsx43(
3690
+ IconButton,
3691
+ {
3692
+ variant: "media",
3693
+ size: "sm",
3694
+ icon: /* @__PURE__ */ jsx43(X15, {}),
3695
+ "aria-label": file.status === "uploading" ? t.cancel(file.name) : t.remove(file.name),
3696
+ onPress: () => (file.status === "uploading" ? onCancel ?? onRemove : onRemove)(file.id),
3697
+ className: "medo-file-tile__remove"
3698
+ }
3699
+ )
3700
+ ] }),
3701
+ file.status === "error" ? /* @__PURE__ */ jsxs42("span", { className: "medo-file-tile__caption medo-file-tile__caption--error", children: [
3702
+ /* @__PURE__ */ jsxs42("span", { className: "medo-visually-hidden", children: [
3703
+ file.name,
3704
+ ": "
3705
+ ] }),
3706
+ file.error
3707
+ ] }) : /* @__PURE__ */ jsx43(FileName, { name: file.name, className: "medo-file-tile__caption" })
3708
+ ] });
3709
+ }
3710
+ async function filesFrom(e) {
3711
+ const items = e.items.filter((i) => i.kind === "file");
3712
+ return Promise.all(items.map((i) => i.getFile()));
3713
+ }
3714
+ function Dropzone({
3715
+ acceptedFileTypes,
3716
+ allowsMultiple = true,
3717
+ defaultCamera,
3718
+ isDisabled,
3719
+ hint,
3720
+ errorMessage,
3721
+ onSelect,
3722
+ texts: own,
3723
+ className,
3724
+ ...props
3725
+ }) {
3726
+ const t = { ...defaultUploadTexts, ...own };
3727
+ const hintId = useId9();
3728
+ const errorId = useId9();
3729
+ return /* @__PURE__ */ jsx43(
3730
+ DropZone,
3731
+ {
3732
+ "aria-labelledby": props["aria-labelledby"],
3733
+ isDisabled,
3734
+ onDrop: async (e) => onSelect(await filesFrom(e)),
3735
+ className: cx("medo-dropzone", !!errorMessage && "medo-dropzone--invalid", className),
3736
+ children: ({ isDropTarget }) => /* @__PURE__ */ jsxs42(Fragment20, { children: [
3737
+ /* @__PURE__ */ jsx43(IconTile, { size: "md", children: /* @__PURE__ */ jsx43(CloudUpload, {}) }),
3738
+ /* @__PURE__ */ jsx43(Text9, { slot: "label", className: "medo-dropzone__title", "aria-disabled": isDisabled || void 0, children: isDropTarget ? t.dropTitle : t.title }),
3739
+ /* @__PURE__ */ jsx43(
3740
+ AriaFileTrigger,
3741
+ {
3742
+ acceptedFileTypes,
3743
+ allowsMultiple,
3744
+ defaultCamera,
3745
+ onSelect: (list) => list && onSelect([...list]),
3746
+ children: /* @__PURE__ */ jsx43(
3747
+ Button,
3748
+ {
3749
+ variant: "secondary",
3750
+ size: "sm",
3751
+ isDisabled,
3752
+ className: "medo-dropzone__button medo-upload__choose",
3753
+ "aria-describedby": [hint && hintId, errorMessage && errorId].filter(Boolean).join(" ") || void 0,
3754
+ children: allowsMultiple ? t.chooseFiles : t.chooseFile
3755
+ }
3756
+ )
3757
+ }
3758
+ ),
3759
+ hint && /* @__PURE__ */ jsx43("span", { id: hintId, className: "medo-dropzone__hint", "aria-disabled": isDisabled || void 0, children: hint }),
3760
+ errorMessage && /* @__PURE__ */ jsxs42("span", { id: errorId, className: "medo-field__error medo-dropzone__error", children: [
3761
+ /* @__PURE__ */ jsx43(CircleAlert6, { "aria-hidden": "true" }),
3762
+ /* @__PURE__ */ jsx43("span", { children: errorMessage })
3763
+ ] })
3764
+ ] })
3765
+ }
3766
+ );
3767
+ }
3768
+ function FileTrigger({
3769
+ label,
3770
+ isRequired,
3771
+ acceptedFileTypes,
3772
+ allowsMultiple = true,
3773
+ defaultCamera,
3774
+ isDisabled,
3775
+ hint,
3776
+ errorMessage,
3777
+ onSelect,
3778
+ texts: own,
3779
+ className
3780
+ }) {
3781
+ const t = { ...defaultUploadTexts, ...own };
3782
+ const labelId = useId9();
3783
+ const buttonId = useId9();
3784
+ const hintId = useId9();
3785
+ const errorId = useId9();
3786
+ return /* @__PURE__ */ jsxs42("div", { className: cx("medo-field", "medo-file-trigger", className), children: [
3787
+ label && /* @__PURE__ */ jsxs42("span", { id: labelId, className: "medo-field__label", children: [
3788
+ label,
3789
+ isRequired && /* @__PURE__ */ jsx43("span", { className: "medo-field__required", "aria-hidden": "true", children: "*" })
3790
+ ] }),
3791
+ /* @__PURE__ */ jsx43("div", { children: /* @__PURE__ */ jsx43(
3792
+ AriaFileTrigger,
3793
+ {
3794
+ acceptedFileTypes,
3795
+ allowsMultiple,
3796
+ defaultCamera,
3797
+ onSelect: (list) => list && onSelect([...list]),
3798
+ children: /* @__PURE__ */ jsx43(
3799
+ Button,
3800
+ {
3801
+ id: buttonId,
3802
+ variant: "secondary",
3803
+ startIcon: /* @__PURE__ */ jsx43(Upload, {}),
3804
+ isDisabled,
3805
+ className: "medo-upload__choose",
3806
+ "aria-labelledby": label ? `${labelId} ${buttonId}` : void 0,
3807
+ "aria-describedby": [hint && hintId, errorMessage && errorId].filter(Boolean).join(" ") || void 0,
3808
+ children: allowsMultiple ? t.chooseFiles : t.chooseFile
3809
+ }
3810
+ )
3811
+ }
3812
+ ) }),
3813
+ (hint || errorMessage) && /* @__PURE__ */ jsxs42("div", { className: "medo-field__footer", children: [
3814
+ hint && !errorMessage && /* @__PURE__ */ jsx43("span", { id: hintId, className: "medo-field__description", children: hint }),
3815
+ errorMessage && /* @__PURE__ */ jsxs42("span", { id: errorId, className: "medo-field__error", children: [
3816
+ /* @__PURE__ */ jsx43(CircleAlert6, { "aria-hidden": "true" }),
3817
+ /* @__PURE__ */ jsx43("span", { children: errorMessage })
3818
+ ] })
3819
+ ] })
3820
+ ] });
3821
+ }
3822
+ function UploadField({
3823
+ label,
3824
+ "aria-label": ariaLabel,
3825
+ files,
3826
+ onAdd,
3827
+ onRemove,
3828
+ onCancel,
3829
+ onRetry,
3830
+ layout = "auto",
3831
+ acceptedFileTypes,
3832
+ typesLabel,
3833
+ maxSize,
3834
+ maxFiles,
3835
+ defaultCamera,
3836
+ hint,
3837
+ errorMessage,
3838
+ isRequired,
3839
+ isDisabled,
3840
+ texts: own,
3841
+ className
3842
+ }) {
3843
+ const t = useMemo2(() => ({ ...defaultUploadTexts, ...own }), [own]);
3844
+ const { locale } = useLocale3();
3845
+ const mobile = useMediaQuery(MOBILE_QUERY);
3846
+ const labelId = useId9();
3847
+ const rootRef = useRef15(null);
3848
+ const [rejected, setRejected] = useState16([]);
3849
+ const [pickerError, setPickerError] = useState16();
3850
+ const [announcement, setAnnouncement] = useState16("");
3851
+ const compact = layout === "compact" || layout !== "list" && mobile;
3852
+ const grid = layout === "grid";
3853
+ const allowsMultiple = maxFiles !== 1;
3854
+ const maxLabel = maxSize ? formatBytes(maxSize, locale) : void 0;
3855
+ const shownHint = hint ?? ([typesLabel, maxLabel && `max. ${maxLabel}${allowsMultiple ? " each" : ""}`].filter(Boolean).join(" \xB7 ") || void 0);
3856
+ const add = (picked) => {
3857
+ const wrongType = picked.filter((f) => !isAccepted(f, acceptedFileTypes));
3858
+ let ok = picked.filter((f) => isAccepted(f, acceptedFileTypes));
3859
+ const tooLarge = maxSize ? ok.filter((f) => f.size > maxSize) : [];
3860
+ ok = ok.filter((f) => !tooLarge.includes(f));
3861
+ const messages = [];
3862
+ if (wrongType.length) messages.push(t.typeNotAllowed(wrongType.map((f) => `\u201C${f.name}\u201D`).join(", "), typesLabel ?? ""));
3863
+ if (maxFiles && files.length + ok.length > maxFiles) {
3864
+ ok = ok.slice(0, Math.max(0, maxFiles - files.length));
3865
+ messages.push(t.tooMany(maxFiles));
3866
+ }
3867
+ const errors = tooLarge.map((f) => ({
3868
+ id: `rejected-${f.name}-${f.size}-${f.lastModified}`,
3869
+ name: f.name,
3870
+ size: f.size,
3871
+ type: f.type,
3872
+ status: "error",
3873
+ error: t.tooLarge(formatBytes(f.size, locale), maxLabel ?? ""),
3874
+ canRetry: false
3875
+ }));
3876
+ setRejected((r) => [...r.filter((x) => !errors.some((e) => e.id === x.id)), ...errors]);
3877
+ setPickerError(messages.join(" ") || void 0);
3878
+ setAnnouncement([...messages, ...errors.map((e) => t.failed(e.name, e.error))].join(" "));
3879
+ if (ok.length) onAdd(ok);
3880
+ };
3881
+ const previous = useRef15(/* @__PURE__ */ new Map());
3882
+ useEffect14(() => {
3883
+ const said = [];
3884
+ for (const f of files) {
3885
+ const before = previous.current.get(f.id);
3886
+ if (before === f.status) continue;
3887
+ if (f.status === "done" && before) said.push(t.uploaded(f.name));
3888
+ if (f.status === "error" && f.error) said.push(t.failed(f.name, f.error));
3889
+ }
3890
+ previous.current = new Map(files.map((f) => [f.id, f.status]));
3891
+ if (said.length) setAnnouncement(said.join(" "));
3892
+ }, [files, t]);
3893
+ const focusAt = useRef15(null);
3894
+ const all = [...files, ...rejected];
3895
+ useLayoutEffect7(() => {
3896
+ if (focusAt.current == null) return;
3897
+ const items = rootRef.current?.querySelectorAll(".medo-upload__item");
3898
+ const target = items?.length ? items[Math.min(focusAt.current, items.length - 1)].querySelector("button") : rootRef.current?.querySelector(".medo-upload__choose");
3899
+ focusAt.current = null;
3900
+ target?.focus();
3901
+ });
3902
+ const remember = (id) => focusAt.current = all.findIndex((f) => f.id === id);
3903
+ const actions = {
3904
+ texts: own,
3905
+ onRetry,
3906
+ onCancel: (id) => {
3907
+ remember(id);
3908
+ (onCancel ?? onRemove)(id);
3909
+ },
3910
+ onRemove: (id) => {
3911
+ remember(id);
3912
+ if (rejected.some((r) => r.id === id)) setRejected((r) => r.filter((x) => x.id !== id));
3913
+ else onRemove(id);
3914
+ }
3915
+ };
3916
+ const picker = { acceptedFileTypes, allowsMultiple, defaultCamera, isDisabled, hint: shownHint, errorMessage: errorMessage ?? pickerError, onSelect: add, texts: own };
3917
+ return /* @__PURE__ */ jsxs42(
3918
+ "div",
3919
+ {
3920
+ ref: rootRef,
3921
+ role: compact ? void 0 : "group",
3922
+ "aria-labelledby": label && !compact ? labelId : void 0,
3923
+ "aria-label": label || compact ? void 0 : ariaLabel,
3924
+ className: cx("medo-upload", grid && "medo-upload--grid", compact && "medo-upload--compact", className),
3925
+ children: [
3926
+ compact ? /* @__PURE__ */ jsx43(FileTrigger, { ...picker, label, isRequired }) : /* @__PURE__ */ jsxs42(Fragment20, { children: [
3927
+ label && /* @__PURE__ */ jsxs42("span", { id: labelId, className: "medo-field__label", children: [
3928
+ label,
3929
+ isRequired && /* @__PURE__ */ jsx43("span", { className: "medo-field__required", "aria-hidden": "true", children: "*" })
3930
+ ] }),
3931
+ /* @__PURE__ */ jsx43(Dropzone, { ...picker, "aria-labelledby": label ? labelId : void 0 })
3932
+ ] }),
3933
+ all.length > 0 && /* @__PURE__ */ jsx43("ul", { className: cx("medo-upload__list", grid && "medo-upload__list--grid"), "aria-label": t.listLabel, children: all.map((file) => /* @__PURE__ */ jsx43("li", { className: "medo-upload__item", children: grid ? /* @__PURE__ */ jsx43(FileTile, { file, ...actions }) : /* @__PURE__ */ jsx43(FileItem, { file, ...actions }) }, file.id)) }),
3934
+ /* @__PURE__ */ jsx43("span", { role: "status", className: "medo-visually-hidden", children: announcement })
3935
+ ]
3936
+ }
3937
+ );
3938
+ }
3939
+
3940
+ // src/date-time/Calendar.tsx
3941
+ import { useEffect as useEffect15, useMemo as useMemo3, useRef as useRef16, useState as useState17 } from "react";
3942
+ import {
3943
+ Button as AriaButton16,
3944
+ Calendar as AriaCalendar,
3945
+ CalendarCell,
3946
+ CalendarGrid,
3947
+ CalendarGridBody,
3948
+ CalendarGridHeader,
3949
+ CalendarHeaderCell,
3950
+ RangeCalendar as AriaRangeCalendar,
3951
+ useLocale as useLocale4
3952
+ } from "react-aria-components";
3953
+ import { DateFormatter, getDayOfWeek, getLocalTimeZone, today } from "@internationalized/date";
3954
+ import { ChevronDown as ChevronDown5, ChevronLeft as ChevronLeft4, ChevronRight as ChevronRight5 } from "lucide-react";
3955
+ import { jsx as jsx44, jsxs as jsxs43 } from "react/jsx-runtime";
3956
+ var defaultCalendarTexts = {
3957
+ previousMonth: "Previous month",
3958
+ nextMonth: "Next month",
3959
+ previousYear: "Previous year",
3960
+ nextYear: "Next year",
3961
+ previousYears: "Previous years",
3962
+ nextYears: "Next years",
3963
+ chooseMonth: (title) => `${title} \u2013 choose month and year`,
3964
+ chooseYear: (year) => `${year} \u2013 choose year`,
3965
+ weekNumber: "Wk"
3966
+ };
3967
+ function isoWeek(date) {
3968
+ const d = new Date(Date.UTC(date.year, date.month - 1, date.day));
3969
+ const day = d.getUTCDay() || 7;
3970
+ d.setUTCDate(d.getUTCDate() + 4 - day);
3971
+ const yearStart = Date.UTC(d.getUTCFullYear(), 0, 1);
3972
+ return Math.ceil(((d.getTime() - yearStart) / 864e5 + 1) / 7);
3973
+ }
3974
+ function Header3({
3975
+ view,
3976
+ setView,
3977
+ state,
3978
+ size,
3979
+ months,
3980
+ t
3981
+ }) {
3982
+ const { locale } = useLocale4();
3983
+ const monthTitle = useMemo3(() => new DateFormatter(locale, { month: "long", year: "numeric", timeZone: getLocalTimeZone() }), [locale]);
3984
+ const yearTitle = useMemo3(() => new DateFormatter(locale, { year: "numeric", timeZone: getLocalTimeZone() }), [locale]);
3985
+ const tz = getLocalTimeZone();
3986
+ const focused = state.focusedDate;
3987
+ const buttonSize = size === "lg" ? "lg" : "sm";
3988
+ const yearStart = focused.year - 5;
3989
+ const shift = (step) => state.setFocusedDate(focused.add(view === "months" ? { years: step } : { years: 12 * step }));
3990
+ if (view !== "days") {
3991
+ const title = view === "months" ? yearTitle.format(focused.toDate(tz)) : `${yearStart} \u2013 ${yearStart + 11}`;
3992
+ return /* @__PURE__ */ jsxs43("div", { className: "medo-calendar__header", children: [
3993
+ view === "months" ? /* @__PURE__ */ jsx44(
3994
+ Button,
3995
+ {
3996
+ slot: null,
3997
+ variant: "ghost",
3998
+ size: buttonSize,
3999
+ endIcon: /* @__PURE__ */ jsx44(ChevronDown5, {}),
4000
+ onPress: () => setView("years"),
4001
+ "aria-label": t.chooseYear(title),
4002
+ className: "medo-calendar__title",
4003
+ children: title
4004
+ }
4005
+ ) : /* @__PURE__ */ jsx44("span", { className: "medo-calendar__title medo-calendar__title--static", "aria-live": "polite", children: title }),
4006
+ /* @__PURE__ */ jsxs43("div", { className: "medo-calendar__nav", children: [
4007
+ /* @__PURE__ */ jsx44(IconButton, { slot: null, size: buttonSize, icon: /* @__PURE__ */ jsx44(ChevronLeft4, {}), "aria-label": view === "months" ? t.previousYear : t.previousYears, onPress: () => shift(-1) }),
4008
+ /* @__PURE__ */ jsx44(IconButton, { slot: null, size: buttonSize, icon: /* @__PURE__ */ jsx44(ChevronRight5, {}), "aria-label": view === "months" ? t.nextYear : t.nextYears, onPress: () => shift(1) })
4009
+ ] })
4010
+ ] });
4011
+ }
4012
+ return /* @__PURE__ */ jsx44("div", { className: "medo-calendar__headers", style: { gridTemplateColumns: `repeat(${months}, 1fr)` }, children: Array.from({ length: months }, (_, i) => {
4013
+ const title = monthTitle.format(focused.add({ months: i }).toDate(tz));
4014
+ return /* @__PURE__ */ jsxs43("div", { className: "medo-calendar__header", children: [
4015
+ /* @__PURE__ */ jsx44(
4016
+ Button,
4017
+ {
4018
+ slot: null,
4019
+ variant: "ghost",
4020
+ size: buttonSize,
4021
+ endIcon: /* @__PURE__ */ jsx44(ChevronDown5, {}),
4022
+ onPress: () => setView("months"),
4023
+ "aria-label": t.chooseMonth(title),
4024
+ className: "medo-calendar__title",
4025
+ children: title
4026
+ }
4027
+ ),
4028
+ /* @__PURE__ */ jsxs43("div", { className: "medo-calendar__nav", children: [
4029
+ i === 0 && /* @__PURE__ */ jsx44(IconButton, { slot: "previous", size: buttonSize, icon: /* @__PURE__ */ jsx44(ChevronLeft4, {}), "aria-label": t.previousMonth }),
4030
+ i === months - 1 && /* @__PURE__ */ jsx44(IconButton, { slot: "next", size: buttonSize, icon: /* @__PURE__ */ jsx44(ChevronRight5, {}), "aria-label": t.nextMonth })
4031
+ ] })
4032
+ ] }, i);
4033
+ }) });
4034
+ }
4035
+ function PeriodGrid({ view, setView, state }) {
4036
+ const { locale } = useLocale4();
4037
+ const tz = getLocalTimeZone();
4038
+ const format = useMemo3(() => new DateFormatter(locale, { month: "short", timeZone: tz }), [locale, tz]);
4039
+ const focused = state.focusedDate;
4040
+ const now2 = today(tz);
4041
+ const ref = useRef16(null);
4042
+ const yearStart = focused.year - 5;
4043
+ const items = Array.from({ length: 12 }, (_, i) => {
4044
+ const date = view === "months" ? focused.set({ month: i + 1, day: 1 }) : focused.set({ year: yearStart + i, month: 1, day: 1 });
4045
+ const last = view === "months" ? date.add({ months: 1 }).subtract({ days: 1 }) : date.add({ years: 1 }).subtract({ days: 1 });
4046
+ const outside = !!state.minValue && last.compare(state.minValue) < 0 || !!state.maxValue && date.compare(state.maxValue) > 0;
4047
+ const same = (d) => !!d && d.year === date.year && (view === "years" || d.month === date.month);
4048
+ return {
4049
+ key: `${date.year}-${date.month}`,
4050
+ label: view === "months" ? format.format(date.toDate(tz)) : String(date.year),
4051
+ isCurrent: same(now2),
4052
+ isSelected: same(state.value),
4053
+ isFocused: view === "months" ? date.month === focused.month : date.year === focused.year,
4054
+ isDisabled: state.isDisabled || outside,
4055
+ pick: () => {
4056
+ state.setFocusedDate(view === "months" ? focused.set({ month: i + 1 }) : focused.set({ year: yearStart + i }));
4057
+ setView(view === "months" ? "days" : "months");
4058
+ }
4059
+ };
4060
+ });
4061
+ useEffect15(() => {
4062
+ ref.current?.querySelector("[data-focus-target]")?.focus();
4063
+ }, [view]);
4064
+ return /* @__PURE__ */ jsx44("div", { ref, className: "medo-calendar__periods", role: "group", children: items.map((item) => /* @__PURE__ */ jsx44(
4065
+ AriaButton16,
4066
+ {
4067
+ slot: null,
4068
+ className: "medo-calendar__period",
4069
+ isDisabled: item.isDisabled,
4070
+ onPress: item.pick,
4071
+ "aria-pressed": item.isSelected,
4072
+ "aria-current": item.isCurrent ? "date" : void 0,
4073
+ "data-current": item.isCurrent || void 0,
4074
+ "data-selected": item.isSelected || void 0,
4075
+ "data-focus-target": item.isFocused || void 0,
4076
+ children: item.label
4077
+ },
4078
+ item.key
4079
+ )) });
4080
+ }
4081
+ function DayGrid({ offset, showWeekNumbers, t }) {
4082
+ const { locale } = useLocale4();
4083
+ return /* @__PURE__ */ jsxs43("div", { className: cx("medo-calendar__month", showWeekNumbers && "medo-calendar__month--weeks"), children: [
4084
+ showWeekNumbers && /* @__PURE__ */ jsx44("span", { className: "medo-calendar__weeks-label", "aria-hidden": "true", children: t.weekNumber }),
4085
+ /* @__PURE__ */ jsxs43(CalendarGrid, { offset: offset ? { months: offset } : void 0, weekdayStyle: "short", className: "medo-calendar__grid", children: [
4086
+ /* @__PURE__ */ jsx44(CalendarGridHeader, { children: (day) => /* @__PURE__ */ jsx44(CalendarHeaderCell, { className: "medo-calendar__weekday", children: day.replace(/\.$/, "").slice(0, 2) }) }),
4087
+ /* @__PURE__ */ jsx44(CalendarGridBody, { children: (date) => /* @__PURE__ */ jsx44(CalendarCell, { date, className: "medo-calendar__cell", children: ({ formattedDate }) => (
4088
+ // The week number is drawn by CSS (data-week), so it is not part of the day's visible name (WCAG 2.5.3).
4089
+ /* @__PURE__ */ jsx44("span", { className: "medo-calendar__day", "data-week": showWeekNumbers && getDayOfWeek(date, locale) === 0 ? isoWeek(date.add({ days: 3 })) : void 0, children: formattedDate })
4090
+ ) }) })
4091
+ ] })
4092
+ ] });
4093
+ }
4094
+ function CalendarBody({ state, size, months, showWeekNumbers, t }) {
4095
+ const [view, setView] = useState17("days");
4096
+ const ref = useRef16(null);
4097
+ const back = useRef16(false);
4098
+ useEffect15(() => {
4099
+ if (view !== "days" || !back.current) return;
4100
+ back.current = false;
4101
+ ref.current?.querySelector('.medo-calendar__cell[tabindex="0"]')?.focus();
4102
+ }, [view]);
4103
+ const changeView = (v) => {
4104
+ back.current = v === "days";
4105
+ setView(v);
4106
+ };
4107
+ return /* @__PURE__ */ jsxs43("div", { ref, className: "medo-calendar__body", children: [
4108
+ /* @__PURE__ */ jsx44(Header3, { view, setView: changeView, state, size, months: view === "days" ? months : 1, t }),
4109
+ view === "days" ? /* @__PURE__ */ jsx44("div", { className: "medo-calendar__months", style: { gridTemplateColumns: `repeat(${months}, auto)` }, children: Array.from({ length: months }, (_, i) => /* @__PURE__ */ jsx44(DayGrid, { offset: i, showWeekNumbers, t }, i)) }) : /* @__PURE__ */ jsx44(PeriodGrid, { view, setView: changeView, state })
4110
+ ] });
4111
+ }
4112
+ function Calendar({ size = "md", showWeekNumbers, texts, className, ...props }) {
4113
+ const t = { ...defaultCalendarTexts, ...texts };
4114
+ return /* @__PURE__ */ jsx44(AriaCalendar, { ...props, className: cx("medo-calendar", `medo-calendar--${size}`, className), children: ({ state }) => /* @__PURE__ */ jsx44(CalendarBody, { state, size, months: 1, showWeekNumbers, t }) });
4115
+ }
4116
+ function RangeCalendar({ size = "md", showWeekNumbers, visibleMonths = "auto", texts, className, ...props }) {
4117
+ const t = { ...defaultCalendarTexts, ...texts };
4118
+ const mobile = useMediaQuery(MOBILE_QUERY);
4119
+ const months = visibleMonths === "auto" ? mobile ? 1 : 2 : visibleMonths;
4120
+ return /* @__PURE__ */ jsx44(AriaRangeCalendar, { ...props, visibleDuration: { months }, className: cx("medo-calendar", "medo-calendar--range", `medo-calendar--${size}`, className), children: ({ state }) => /* @__PURE__ */ jsx44(
4121
+ CalendarBody,
4122
+ {
4123
+ state: {
4124
+ focusedDate: state.focusedDate,
4125
+ setFocusedDate: state.setFocusedDate,
4126
+ minValue: state.minValue,
4127
+ maxValue: state.maxValue,
4128
+ value: state.value?.start ?? null,
4129
+ isDisabled: state.isDisabled
4130
+ },
4131
+ size,
4132
+ months,
4133
+ showWeekNumbers,
4134
+ t
4135
+ }
4136
+ ) });
4137
+ }
4138
+
4139
+ // src/date-time/DateField.tsx
4140
+ import { useMemo as useMemo4, useState as useState18 } from "react";
4141
+ import {
4142
+ DateField as AriaDateField,
4143
+ DateInput,
4144
+ DateSegment,
4145
+ TimeField as AriaTimeField,
4146
+ useLocale as useLocale5
4147
+ } from "react-aria-components";
4148
+ import { DateFormatter as DateFormatter2 } from "@internationalized/date";
4149
+ import { Clock } from "lucide-react";
4150
+ import { jsx as jsx45, jsxs as jsxs44 } from "react/jsx-runtime";
4151
+ function DateSegments({ size = "md", children }) {
4152
+ return /* @__PURE__ */ jsxs44("div", { className: cx("medo-input", `medo-input--${size}`, "medo-date-box"), children: [
4153
+ /* @__PURE__ */ jsx45(DateInput, { className: "medo-date-input", children: (segment) => /* @__PURE__ */ jsx45(DateSegment, { segment, className: "medo-date-segment" }) }),
4154
+ children
4155
+ ] });
4156
+ }
4157
+ function DateField({ label, description, errorMessage, size = "md", className, ...props }) {
4158
+ return /* @__PURE__ */ jsxs44(AriaDateField, { shouldForceLeadingZeros: true, ...props, className: cx("medo-field", "medo-date-field", className), children: [
4159
+ label && /* @__PURE__ */ jsx45(FieldLabel, { isRequired: props.isRequired, children: label }),
4160
+ /* @__PURE__ */ jsx45(DateSegments, { size }),
4161
+ /* @__PURE__ */ jsx45(FieldFooter, { description, errorMessage })
4162
+ ] });
4163
+ }
4164
+ function TimeField({
4165
+ label,
4166
+ description,
4167
+ errorMessage,
4168
+ size = "md",
4169
+ slots,
4170
+ slotsLabel = "Available times",
4171
+ className,
4172
+ defaultValue,
4173
+ onChange,
4174
+ ...props
4175
+ }) {
4176
+ const [inner, setInner] = useState18(defaultValue ?? null);
4177
+ const value = props.value !== void 0 ? props.value : inner;
4178
+ const change = (v) => {
4179
+ setInner(v);
4180
+ onChange?.(v);
4181
+ };
4182
+ const { locale } = useLocale5();
4183
+ const format = useMemo4(
4184
+ () => new DateFormatter2(locale, { hour: "2-digit", minute: "2-digit", hourCycle: props.hourCycle === 12 ? "h12" : props.hourCycle === 24 ? "h23" : void 0 }),
4185
+ [locale, props.hourCycle]
4186
+ );
4187
+ const key = (v) => v ? `${v.hour}:${v.minute}` : "";
4188
+ const text = (v) => format.format(new Date(2e3, 0, 1, v.hour, v.minute));
4189
+ return /* @__PURE__ */ jsxs44(AriaTimeField, { shouldForceLeadingZeros: true, ...props, value, onChange: change, className: cx("medo-field", "medo-date-field", "medo-time-field", className), children: [
4190
+ label && /* @__PURE__ */ jsx45(FieldLabel, { isRequired: props.isRequired, children: label }),
4191
+ /* @__PURE__ */ jsx45(DateSegments, { size, children: slots && /* @__PURE__ */ jsxs44(PopoverTrigger, { children: [
4192
+ /* @__PURE__ */ jsx45(InputAction, { icon: /* @__PURE__ */ jsx45(Clock, {}), "aria-label": slotsLabel, isDisabled: props.isDisabled || props.isReadOnly }),
4193
+ /* @__PURE__ */ jsx45(Popover5, { size: "auto", "aria-label": slotsLabel, placement: "bottom end", className: "medo-time-slots", children: (close) => /* @__PURE__ */ jsx45(
4194
+ ListBox,
4195
+ {
4196
+ "aria-label": slotsLabel,
4197
+ size,
4198
+ autoFocus: true,
4199
+ selectionMode: "single",
4200
+ selectedKeys: value ? [key(value)] : [],
4201
+ disabledKeys: slots.filter((s) => s.isDisabled).map((s) => key(s.value)),
4202
+ onSelectionChange: (keys) => {
4203
+ const slot = slots.find((s) => keys !== "all" && keys.has(key(s.value)));
4204
+ if (slot) change(slot.value);
4205
+ close();
4206
+ },
4207
+ children: slots.map((s) => /* @__PURE__ */ jsx45(Option, { id: key(s.value), textValue: text(s.value), description: s.isDisabled ? s.reason : void 0, children: text(s.value) }, key(s.value)))
4208
+ }
4209
+ ) })
4210
+ ] }) }),
4211
+ /* @__PURE__ */ jsx45(FieldFooter, { description, errorMessage })
4212
+ ] });
4213
+ }
4214
+
4215
+ // src/date-time/DatePicker.tsx
4216
+ import { useContext as useContext17, useMemo as useMemo5 } from "react";
4217
+ import {
4218
+ DateInput as DateInput2,
4219
+ DatePicker as AriaDatePicker,
4220
+ DatePickerStateContext,
4221
+ DateRangePicker as AriaDateRangePicker,
4222
+ DateRangePickerStateContext,
4223
+ DateSegment as DateSegment2,
4224
+ Dialog as AriaDialog5,
4225
+ Group as Group2,
4226
+ Popover as AriaPopover2,
4227
+ useLocale as useLocale6
4228
+ } from "react-aria-components";
4229
+ import { DateFormatter as DateFormatter3, endOfMonth, getLocalTimeZone as getLocalTimeZone2, startOfMonth, today as today2 } from "@internationalized/date";
4230
+ import { Calendar as CalendarIcon } from "lucide-react";
4231
+ import { jsx as jsx46, jsxs as jsxs45 } from "react/jsx-runtime";
4232
+ var defaultDatePickerTexts = {
4233
+ openCalendar: "Open calendar",
4234
+ done: "Done",
4235
+ time: "Time",
4236
+ presets: "Periods",
4237
+ beforeMin: (date) => `Choose a date from ${date} on.`,
4238
+ afterMax: (date) => `Choose a date until ${date}.`,
4239
+ unavailable: "This day is not available. Choose another day.",
4240
+ reversed: "The end date must be after the start date."
4241
+ };
4242
+ function useMessages(t, minValue, maxValue) {
4243
+ const { locale } = useLocale6();
4244
+ const format = useMemo5(() => new DateFormatter3(locale, { day: "2-digit", month: "2-digit", year: "numeric", timeZone: getLocalTimeZone2() }), [locale]);
4245
+ const show = (d) => format.format(d.toDate(getLocalTimeZone2()));
4246
+ return ({ validationDetails: v, validationErrors }) => {
4247
+ if (v.rangeUnderflow && v.rangeOverflow) return t.reversed;
4248
+ if (v.rangeUnderflow && minValue) return t.beforeMin(show(minValue));
4249
+ if (v.rangeOverflow && maxValue) return t.afterMax(show(maxValue));
4250
+ if (v.badInput) return t.unavailable;
4251
+ return validationErrors.join(" ");
4252
+ };
4253
+ }
4254
+ function PickerOverlay({ title, sheet, t, children }) {
4255
+ if (sheet) {
4256
+ return /* @__PURE__ */ jsx46(
4257
+ BottomSheet,
4258
+ {
4259
+ title,
4260
+ className: "medo-date-sheet",
4261
+ footer: (close) => /* @__PURE__ */ jsx46(Button, { variant: "primary", size: "lg", onPress: close, children: t.done }),
4262
+ children: /* @__PURE__ */ jsx46("div", { className: "medo-date-panel", children })
4263
+ }
4264
+ );
4265
+ }
4266
+ return /* @__PURE__ */ jsx46(AriaPopover2, { placement: "bottom start", offset: 8, containerPadding: 16, className: "medo-popover medo-popover--auto medo-date-popover", children: /* @__PURE__ */ jsx46(AriaDialog5, { "aria-label": title, className: "medo-popover__dialog", children: /* @__PURE__ */ jsx46("div", { className: "medo-popover__body medo-date-panel", children }) }) });
4267
+ }
4268
+ function PanelTime({ label, size }) {
4269
+ const state = useContext17(DatePickerStateContext);
4270
+ if (!state) return null;
4271
+ return /* @__PURE__ */ jsx46("div", { className: "medo-date-panel__time", children: /* @__PURE__ */ jsx46(TimeField, { label, size, value: state.timeValue, onChange: (v) => v && state.setTimeValue(v) }) });
4272
+ }
4273
+ var textOf = (label, ariaLabel) => typeof label === "string" ? label : ariaLabel ?? "";
4274
+ function DatePicker({
4275
+ label,
4276
+ description,
4277
+ errorMessage,
4278
+ size = "md",
4279
+ overlay = "auto",
4280
+ showWeekNumbers,
4281
+ texts,
4282
+ className,
4283
+ ...props
4284
+ }) {
4285
+ const t = { ...defaultCalendarTexts, ...defaultDatePickerTexts, ...texts };
4286
+ const mobile = useMediaQuery(MOBILE_QUERY);
4287
+ const sheet = overlay === "sheet" || overlay === "auto" && mobile;
4288
+ const messages = useMessages(t, props.minValue, props.maxValue);
4289
+ const hasTime = !!props.granularity && props.granularity !== "day";
4290
+ return /* @__PURE__ */ jsxs45(
4291
+ AriaDatePicker,
4292
+ {
4293
+ shouldForceLeadingZeros: true,
4294
+ ...props,
4295
+ className: cx("medo-field", "medo-date-field", "medo-date-picker", hasTime && "medo-date-picker--time", className),
4296
+ children: [
4297
+ label && /* @__PURE__ */ jsx46(FieldLabel, { isRequired: props.isRequired, children: label }),
4298
+ /* @__PURE__ */ jsxs45(Group2, { className: cx("medo-input", `medo-input--${size}`, "medo-date-box"), children: [
4299
+ /* @__PURE__ */ jsx46(DateInput2, { className: "medo-date-input", children: (segment) => /* @__PURE__ */ jsx46(DateSegment2, { segment, className: "medo-date-segment" }) }),
4300
+ /* @__PURE__ */ jsx46(InputAction, { icon: /* @__PURE__ */ jsx46(CalendarIcon, {}), "aria-label": t.openCalendar })
4301
+ ] }),
4302
+ /* @__PURE__ */ jsx46(FieldFooter, { description, errorMessage: errorMessage ?? messages }),
4303
+ /* @__PURE__ */ jsxs45(PickerOverlay, { title: textOf(label, props["aria-label"]), sheet, t, children: [
4304
+ /* @__PURE__ */ jsx46(Calendar, { size: sheet ? "lg" : "md", showWeekNumbers, texts: t }),
4305
+ hasTime && /* @__PURE__ */ jsx46(PanelTime, { label: t.time, size: sheet ? "lg" : "md" })
4306
+ ] })
4307
+ ]
4308
+ }
4309
+ );
4310
+ }
4311
+ function defaultDatePresets(labels = {}) {
4312
+ const now2 = today2(getLocalTimeZone2());
4313
+ const lastMonth = now2.subtract({ months: 1 });
4314
+ return [
4315
+ { label: labels.today ?? "Today", value: { start: now2, end: now2 } },
4316
+ { label: labels.yesterday ?? "Yesterday", value: { start: now2.subtract({ days: 1 }), end: now2.subtract({ days: 1 }) } },
4317
+ { label: labels.last7 ?? "Last 7 days", value: { start: now2.subtract({ days: 6 }), end: now2 } },
4318
+ { label: labels.last14 ?? "Last 14 days", value: { start: now2.subtract({ days: 13 }), end: now2 } },
4319
+ { label: labels.last30 ?? "Last 30 days", value: { start: now2.subtract({ days: 29 }), end: now2 } },
4320
+ { label: labels.thisMonth ?? "This month", value: { start: startOfMonth(now2), end: now2 } },
4321
+ { label: labels.lastMonth ?? "Last month", value: { start: startOfMonth(lastMonth), end: endOfMonth(lastMonth) } }
4322
+ ];
4323
+ }
4324
+ function Presets({ presets, label, size }) {
4325
+ const state = useContext17(DateRangePickerStateContext);
4326
+ if (!state) return null;
4327
+ const same = (a, b) => !!a && !!b && a.compare(b) === 0;
4328
+ const active = presets.findIndex((p) => same(state.value?.start, p.value.start) && same(state.value?.end, p.value.end));
4329
+ return /* @__PURE__ */ jsx46(
4330
+ ListBox,
4331
+ {
4332
+ "aria-label": label,
4333
+ size,
4334
+ selectionMode: "single",
4335
+ selectedKeys: active >= 0 ? [String(active)] : [],
4336
+ disallowEmptySelection: false,
4337
+ onAction: (key) => {
4338
+ const preset = presets[Number(key)];
4339
+ state.setValue(preset.value);
4340
+ state.close();
4341
+ },
4342
+ className: "medo-date-panel__presets",
4343
+ children: presets.map((p, i) => /* @__PURE__ */ jsx46(Option, { id: String(i), children: p.label }, i))
4344
+ }
4345
+ );
4346
+ }
4347
+ function DateRangePicker({
4348
+ label,
4349
+ description,
4350
+ errorMessage,
4351
+ size = "md",
4352
+ overlay = "auto",
4353
+ showWeekNumbers,
4354
+ presets,
4355
+ texts,
4356
+ className,
4357
+ ...props
4358
+ }) {
4359
+ const t = { ...defaultCalendarTexts, ...defaultDatePickerTexts, ...texts };
4360
+ const mobile = useMediaQuery(MOBILE_QUERY);
4361
+ const sheet = overlay === "sheet" || overlay === "auto" && mobile;
4362
+ const messages = useMessages(t, props.minValue, props.maxValue);
4363
+ const list = presets === true ? defaultDatePresets() : presets || void 0;
4364
+ return /* @__PURE__ */ jsxs45(AriaDateRangePicker, { shouldForceLeadingZeros: true, ...props, className: cx("medo-field", "medo-date-field", "medo-date-picker", "medo-date-picker--range", className), children: [
4365
+ label && /* @__PURE__ */ jsx46(FieldLabel, { isRequired: props.isRequired, children: label }),
4366
+ /* @__PURE__ */ jsxs45(Group2, { className: cx("medo-input", `medo-input--${size}`, "medo-date-box"), children: [
4367
+ /* @__PURE__ */ jsx46(DateInput2, { slot: "start", className: "medo-date-input", children: (segment) => /* @__PURE__ */ jsx46(DateSegment2, { segment, className: "medo-date-segment" }) }),
4368
+ /* @__PURE__ */ jsx46("span", { className: "medo-date-box__dash", "aria-hidden": "true", children: "\u2013" }),
4369
+ /* @__PURE__ */ jsx46(DateInput2, { slot: "end", className: "medo-date-input", children: (segment) => /* @__PURE__ */ jsx46(DateSegment2, { segment, className: "medo-date-segment" }) }),
4370
+ /* @__PURE__ */ jsx46(InputAction, { icon: /* @__PURE__ */ jsx46(CalendarIcon, {}), "aria-label": t.openCalendar })
4371
+ ] }),
4372
+ /* @__PURE__ */ jsx46(FieldFooter, { description, errorMessage: errorMessage ?? messages }),
4373
+ /* @__PURE__ */ jsx46(PickerOverlay, { title: textOf(label, props["aria-label"]), sheet, t, children: /* @__PURE__ */ jsxs45("div", { className: cx("medo-date-panel__range", !!list && "medo-date-panel__range--presets"), children: [
4374
+ list && /* @__PURE__ */ jsx46(Presets, { presets: list, label: t.presets, size: sheet ? "lg" : "md" }),
4375
+ /* @__PURE__ */ jsx46(RangeCalendar, { size: sheet ? "lg" : "md", visibleMonths: sheet ? 1 : 2, showWeekNumbers, texts: t })
4376
+ ] }) })
4377
+ ] });
4378
+ }
4379
+
4380
+ // src/index.ts
4381
+ import {
4382
+ CalendarDate,
4383
+ CalendarDateTime,
4384
+ Time,
4385
+ ZonedDateTime,
4386
+ parseDate,
4387
+ parseDateTime,
4388
+ parseTime,
4389
+ parseZonedDateTime,
4390
+ today as today3,
4391
+ now,
4392
+ getLocalTimeZone as getLocalTimeZone3,
4393
+ isWeekend,
4394
+ isToday
4395
+ } from "@internationalized/date";
4396
+
4397
+ // src/media/Image.tsx
4398
+ import { useEffect as useEffect16, useLayoutEffect as useLayoutEffect8, useRef as useRef17, useState as useState19 } from "react";
4399
+ import { ImageOff, Play } from "lucide-react";
4400
+ import { jsx as jsx47, jsxs as jsxs46 } from "react/jsx-runtime";
4401
+ function MediaPlayButton({ size = "md", className }) {
4402
+ return /* @__PURE__ */ jsx47("span", { className: cx("medo-media-play", `medo-media-play--${size}`, className), "aria-hidden": "true", children: /* @__PURE__ */ jsx47(Play, {}) });
4403
+ }
4404
+ function Image({
4405
+ src,
4406
+ alt,
4407
+ ratio = "original",
4408
+ fit = "cover",
4409
+ radius = "md",
4410
+ bordered,
4411
+ placeholder,
4412
+ priority,
4413
+ overlay,
4414
+ badge,
4415
+ play,
4416
+ className,
4417
+ style,
4418
+ onLoad,
4419
+ onError,
4420
+ ...props
4421
+ }) {
4422
+ const ref = useRef17(null);
4423
+ const [state, setState] = useState19("loading");
4424
+ useLayoutEffect8(() => setState("loading"), [src]);
4425
+ useEffect16(() => {
4426
+ const img = ref.current;
4427
+ if (img?.complete && src) setState(img.naturalWidth > 0 ? "loaded" : "error");
4428
+ }, [src]);
4429
+ const original = ratio === "original";
4430
+ const { width, height } = props;
4431
+ const aspect = original ? width && height ? `${width} / ${height}` : state === "loaded" ? void 0 : "3 / 2" : ratio.replace(":", " / ");
4432
+ return /* @__PURE__ */ jsxs46(
4433
+ "div",
4434
+ {
4435
+ className: cx("medo-image", `medo-image--${fit}`, `medo-image--radius-${radius}`, bordered && "medo-image--bordered", className),
4436
+ "data-state": state,
4437
+ style: { aspectRatio: aspect, ...style },
4438
+ children: [
4439
+ state === "loading" && (placeholder ? /* @__PURE__ */ jsx47("img", { className: "medo-image__preview", src: placeholder, alt: "", "aria-hidden": "true" }) : /* @__PURE__ */ jsx47(Skeleton, { shape: "rect", className: "medo-image__skeleton" })),
4440
+ state === "error" && /* @__PURE__ */ jsx47("span", { className: "medo-image__error", role: alt ? "img" : void 0, "aria-label": alt || void 0, "aria-hidden": alt ? void 0 : true, children: /* @__PURE__ */ jsx47(ImageOff, { "aria-hidden": "true" }) }),
4441
+ /* @__PURE__ */ jsx47(
4442
+ "img",
4443
+ {
4444
+ ...props,
4445
+ ref,
4446
+ src,
4447
+ alt,
4448
+ hidden: state === "error",
4449
+ loading: priority ? "eager" : props.loading ?? "lazy",
4450
+ fetchPriority: priority ? "high" : props.fetchPriority,
4451
+ decoding: "async",
4452
+ className: "medo-image__img",
4453
+ onLoad: (e) => {
4454
+ setState("loaded");
4455
+ onLoad?.(e);
4456
+ },
4457
+ onError: (e) => {
4458
+ setState("error");
4459
+ onError?.(e);
4460
+ }
4461
+ }
4462
+ ),
4463
+ state === "loaded" && overlay && /* @__PURE__ */ jsxs46("div", { className: cx("medo-image__overlay", `medo-image__overlay--${overlay.position ?? "bottom"}`), children: [
4464
+ /* @__PURE__ */ jsx47("span", { className: "medo-image__title", children: overlay.title }),
4465
+ overlay.meta && /* @__PURE__ */ jsx47("span", { className: "medo-image__meta", children: overlay.meta })
4466
+ ] }),
4467
+ state === "loaded" && badge && /* @__PURE__ */ jsx47("span", { className: "medo-image__badge", children: badge }),
4468
+ state === "loaded" && play && /* @__PURE__ */ jsx47(MediaPlayButton, { size: play, className: "medo-image__play" })
4469
+ ]
4470
+ }
4471
+ );
4472
+ }
4473
+ function Figure({ children, caption, credit, className }) {
4474
+ return /* @__PURE__ */ jsxs46("figure", { className: cx("medo-figure", className), children: [
4475
+ children,
4476
+ (caption || credit) && /* @__PURE__ */ jsxs46("figcaption", { className: "medo-figure__caption", children: [
4477
+ caption && /* @__PURE__ */ jsx47("span", { children: caption }),
4478
+ credit && /* @__PURE__ */ jsx47("span", { className: "medo-figure__credit", children: credit })
4479
+ ] })
4480
+ ] });
4481
+ }
4482
+
4483
+ // src/media/EmbedConsent.tsx
4484
+ import { useEffect as useEffect17, useLayoutEffect as useLayoutEffect9, useRef as useRef18, useState as useState20 } from "react";
4485
+ import { Globe as Globe2, MapPin, Play as Play2 } from "lucide-react";
4486
+ import { jsx as jsx48, jsxs as jsxs47 } from "react/jsx-runtime";
4487
+ function defaultEmbedTexts(type, provider, company) {
4488
+ const to = company ?? provider;
4489
+ if (type === "video")
4490
+ return {
4491
+ title: "Load external video",
4492
+ body: `This video is provided by ${provider ?? "a third party"}. Loading it sends data such as your IP address to ${to ?? "the provider"}.`,
4493
+ bodyCompact: `Loading this video sends data to ${provider && company && provider !== company ? `${provider} (${company})` : to ?? "a third party"}.`,
4494
+ load: "Load video",
4495
+ remember: provider ? `Always load ${provider} videos` : "Always load videos from this provider",
4496
+ privacy: "Privacy policy"
4497
+ };
4498
+ if (type === "map")
4499
+ return {
4500
+ title: "Load map",
4501
+ body: `This map is provided by ${provider ?? "a third party"}. Loading it sends data such as your IP address to ${to ?? "the provider"}.`,
4502
+ bodyCompact: `Loading this map sends data to ${to ?? "a third party"}.`,
4503
+ load: "Load map",
4504
+ remember: provider ? `Always load ${provider}` : "Always load maps from this provider",
4505
+ privacy: "Privacy policy"
4506
+ };
4507
+ return {
4508
+ title: "Load external content",
4509
+ body: `This content is provided by ${provider ?? "a third party"}. Loading it sends data such as your IP address to ${to ?? "the provider"}.`,
4510
+ bodyCompact: `Loading this content sends data to ${to ?? "a third party"}.`,
4511
+ load: "Load content",
4512
+ remember: provider ? `Always load content from ${provider}` : "Always load content from this provider",
4513
+ privacy: "Privacy policy"
4514
+ };
4515
+ }
4516
+ var icons2 = { video: /* @__PURE__ */ jsx48(Play2, {}), map: /* @__PURE__ */ jsx48(MapPin, {}), generic: /* @__PURE__ */ jsx48(Globe2, {}) };
4517
+ function EmbedConsent({
4518
+ type = "generic",
4519
+ provider,
4520
+ company,
4521
+ src,
4522
+ title,
4523
+ ratio = "16:9",
4524
+ privacyHref,
4525
+ isConsented,
4526
+ onConsent,
4527
+ allow,
4528
+ texts,
4529
+ className
4530
+ }) {
4531
+ const t = { ...defaultEmbedTexts(type, provider, company), ...texts };
4532
+ const ref = useRef18(null);
4533
+ const frame = useRef18(null);
4534
+ const [clicked, setClicked] = useState20(false);
4535
+ const [remember, setRemember] = useState20(false);
4536
+ const [compact, setCompact] = useState20(false);
4537
+ const loaded = isConsented || clicked;
4538
+ useLayoutEffect9(() => {
4539
+ const el = ref.current;
4540
+ if (!el || loaded) return;
4541
+ const ro = new ResizeObserver(([entry]) => setCompact(entry.contentRect.width < 480));
4542
+ ro.observe(el);
4543
+ return () => ro.disconnect();
4544
+ }, [loaded]);
4545
+ useEffect17(() => {
4546
+ if (clicked) frame.current?.focus();
4547
+ }, [clicked]);
4548
+ const style = { aspectRatio: ratio.replace(":", " / ") };
4549
+ if (loaded) {
4550
+ return /* @__PURE__ */ jsx48("div", { className: cx("medo-embed", "medo-embed--loaded", className), style, children: /* @__PURE__ */ jsx48(
4551
+ "iframe",
4552
+ {
4553
+ ref: frame,
4554
+ src,
4555
+ title,
4556
+ allow,
4557
+ allowFullScreen: true,
4558
+ referrerPolicy: "strict-origin-when-cross-origin",
4559
+ loading: "lazy",
4560
+ className: "medo-embed__frame"
4561
+ }
4562
+ ) });
4563
+ }
4564
+ return /* @__PURE__ */ jsxs47("div", { ref, role: "region", "aria-label": title, className: cx("medo-embed", compact && "medo-embed--compact", className), style, children: [
4565
+ !compact && /* @__PURE__ */ jsx48(IconTile, { size: "sm", tone: "raised", children: icons2[type] }),
4566
+ /* @__PURE__ */ jsxs47("div", { className: "medo-embed__content", children: [
4567
+ /* @__PURE__ */ jsx48("p", { className: "medo-embed__title", children: t.title }),
4568
+ /* @__PURE__ */ jsx48("p", { className: "medo-embed__body", children: compact ? t.bodyCompact : t.body })
4569
+ ] }),
4570
+ /* @__PURE__ */ jsxs47("div", { className: "medo-embed__actions", children: [
4571
+ /* @__PURE__ */ jsx48(
4572
+ Button,
4573
+ {
4574
+ size: compact ? "sm" : "md",
4575
+ onPress: () => {
4576
+ onConsent?.(remember);
4577
+ setClicked(true);
4578
+ },
4579
+ children: t.load
4580
+ }
4581
+ ),
4582
+ /* @__PURE__ */ jsx48(Link, { variant: "standalone", size: compact ? "sm" : "md", href: privacyHref, children: t.privacy })
4583
+ ] }),
4584
+ !compact && /* @__PURE__ */ jsx48(Checkbox, { isSelected: remember, onChange: setRemember, children: t.remember })
4585
+ ] });
4586
+ }
4587
+ export {
4588
+ Accordion,
4589
+ AccordionItem,
4590
+ Alert,
4591
+ AlertDialog,
4592
+ AppHeader,
4593
+ Avatar,
4594
+ AvatarGroup,
4595
+ Badge,
4596
+ BadgeCount,
4597
+ BadgeDot,
4598
+ BottomSheet,
4599
+ BottomTabBar,
4600
+ Breadcrumbs,
4601
+ BulkActionBar,
4602
+ Button,
4603
+ ButtonGroup,
4604
+ Calendar,
4605
+ CalendarDate,
4606
+ CalendarDateTime,
4607
+ Card,
4608
+ Cell,
4609
+ Checkbox,
4610
+ CheckboxGroup,
4611
+ Column,
4612
+ ColumnsMenu,
4613
+ Combobox,
4614
+ DateField,
4615
+ DatePicker,
4616
+ DateRangePicker,
4617
+ DescriptionItem,
4618
+ DescriptionList,
4619
+ Dialog2 as Dialog,
4620
+ DialogTrigger,
4621
+ Drawer,
4622
+ Dropzone,
4623
+ EmbedConsent,
4624
+ EmptyState,
4625
+ FieldFooter,
4626
+ FieldLabel,
4627
+ Figure,
4628
+ FileItem,
4629
+ FileTile,
4630
+ FileTrigger,
4631
+ Highlight,
4632
+ IconButton,
4633
+ Image,
4634
+ InputAction,
4635
+ Link,
4636
+ List,
4637
+ ListBox,
4638
+ ListBoxMessage,
4639
+ ListBoxSection,
4640
+ ListCard,
4641
+ ListItem,
4642
+ ListRow,
4643
+ ListRowIcon,
4644
+ ListView,
4645
+ MediaPlayButton,
4646
+ Menu,
4647
+ MenuButton,
4648
+ MenuItem,
4649
+ MenuSection,
4650
+ MenuTrigger,
4651
+ NavGroup,
4652
+ NavItem,
4653
+ NavList,
4654
+ NavSection,
4655
+ NumberField,
4656
+ Option,
4657
+ Pagination,
4658
+ Popover5 as Popover,
4659
+ PopoverTrigger,
4660
+ ProgressBar,
4661
+ QuickSearch,
4662
+ Radio,
4663
+ RadioGroup,
4664
+ RangeCalendar,
4665
+ Row,
4666
+ SearchField,
4667
+ SearchResult,
4668
+ Select,
4669
+ SideNav,
4670
+ SiteHeader,
4671
+ Skeleton,
4672
+ SkipLink,
4673
+ Slider,
4674
+ SliderField,
4675
+ Spinner,
4676
+ StaticTable,
4677
+ Submenu,
4678
+ Switch,
4679
+ Tab,
4680
+ TabBarItem,
4681
+ TabBarMore,
4682
+ TabList,
4683
+ TabPanel,
4684
+ Table,
4685
+ TableBody,
4686
+ TableHeader,
4687
+ Tabs,
4688
+ Tag,
4689
+ TagGroup,
4690
+ TextField,
4691
+ Time,
4692
+ TimeField,
4693
+ ToastRegion,
4694
+ ToggleButton,
4695
+ ToggleButtonGroup,
4696
+ Toggletip,
4697
+ Toolbar,
4698
+ ToolbarSeparator,
4699
+ Tooltip,
4700
+ TooltipTrigger,
4701
+ UploadField,
4702
+ ZonedDateTime,
4703
+ defaultCalendarTexts,
4704
+ defaultDatePickerTexts,
4705
+ defaultDatePresets,
4706
+ defaultEmbedTexts,
4707
+ defaultUploadTexts,
4708
+ formatBytes,
4709
+ getLocalTimeZone3 as getLocalTimeZone,
4710
+ initialsOf,
4711
+ isToday,
4712
+ isWeekend,
4713
+ now,
4714
+ parseDate,
4715
+ parseDateTime,
4716
+ parseTime,
4717
+ parseZonedDateTime,
4718
+ toast,
4719
+ today3 as today
4720
+ };
4721
+ //# sourceMappingURL=index.js.map