najm-kit 2.11.23 → 2.12.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 (34) hide show
  1. package/CHANGELOG.md +21 -12
  2. package/README.md +28 -1
  3. package/dist/NIcon-D73-C51g.d.ts +21 -0
  4. package/dist/{NNotFoundState-BmGDW2Bx.d.ts → NNotFoundState-Z2n2gbhS.d.ts} +1 -16
  5. package/dist/{NajmUIProvider-BwL5HFIV.d.ts → NajmUIProvider-C9IJW1ya.d.ts} +2 -19
  6. package/dist/adapters/app.d.ts +6 -4
  7. package/dist/adapters/app.mjs +9 -6
  8. package/dist/adapters/next.d.ts +2 -1
  9. package/dist/adapters/next.mjs +5 -3
  10. package/dist/chunk-5UY7U2ED.mjs +9 -0
  11. package/dist/chunk-A34LRQYI.mjs +7 -0
  12. package/dist/chunk-BFFTC7LD.mjs +66 -0
  13. package/dist/{chunk-5ZTOCWRA.mjs → chunk-F32O3WRX.mjs} +7 -197
  14. package/dist/chunk-FZX3DONZ.mjs +200 -0
  15. package/dist/chunk-GYPQLOQ5.mjs +2202 -0
  16. package/dist/{chunk-BDQK6ECA.mjs → chunk-LMW4DM5L.mjs} +43 -101
  17. package/dist/{chunk-DKHIQFXX.mjs → chunk-QDFLHSWO.mjs} +1 -1
  18. package/dist/{chunk-5JVFMOLI.mjs → chunk-QJ22WAQY.mjs} +2 -28
  19. package/dist/{chunk-LK3SMYUP.mjs → chunk-TZRTOQCC.mjs} +1 -6
  20. package/dist/chunk-VZXMMC72.mjs +31 -0
  21. package/dist/{chunk-M5VGBZ7R.mjs → chunk-WHGRN7PP.mjs} +2 -1
  22. package/dist/formFill-CQkTA0JS.d.ts +17 -0
  23. package/dist/index.d.ts +20 -436
  24. package/dist/index.mjs +338 -1950
  25. package/dist/json.mjs +5 -3
  26. package/dist/location/google.d.ts +14 -0
  27. package/dist/location/google.mjs +184 -0
  28. package/dist/location/index.d.ts +33 -0
  29. package/dist/location/index.mjs +9 -0
  30. package/dist/location/leaflet.d.ts +13 -0
  31. package/dist/location/leaflet.mjs +168 -0
  32. package/dist/theme.css +23 -2
  33. package/dist/types-BQBx0RZd.d.ts +578 -0
  34. package/package.json +30 -3
@@ -0,0 +1,2202 @@
1
+ import { NLoadingState } from './chunk-FZX3DONZ.mjs';
2
+ import { NajmScroll } from './chunk-QJ22WAQY.mjs';
3
+ import { inputBorderClasses, Button, surfaceBorderClasses, NIcon } from './chunk-WHGRN7PP.mjs';
4
+ import { useNajmComponentStyle, NajmThemeContainerCtx } from './chunk-TZRTOQCC.mjs';
5
+ import { cn } from './chunk-5UY7U2ED.mjs';
6
+ import { resolveRadiusValue } from './chunk-TFHWLE7N.mjs';
7
+ import * as React12 from 'react';
8
+ import React12__default, { createContext, useContext, useState, useCallback, useMemo } from 'react';
9
+ import { MapPin, CircleAlert, MapPinCheck, X, Search, Plus, Minus, Crosshair, LoaderCircle, Trash2, Check, XIcon, Menu } from 'lucide-react';
10
+ import { cva } from 'class-variance-authority';
11
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
12
+ import * as SheetPrimitive from '@radix-ui/react-dialog';
13
+ import { create } from 'zustand';
14
+ import { FormProvider, Controller, useFormContext } from 'react-hook-form';
15
+ import { Slot } from '@radix-ui/react-slot';
16
+ import * as LabelPrimitive from '@radix-ui/react-label';
17
+
18
+ // src/theme/focus.ts
19
+ var focusRingClasses = "outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50";
20
+ var focusRingWithinClasses = "focus-visible:ring-[3px] focus-visible:ring-ring/50 has-[:focus-visible]:ring-[3px] has-[:focus-visible]:ring-ring/50";
21
+ var NO_SHADE = /* @__PURE__ */ new Set(["black", "white"]);
22
+ var PRESETS = {
23
+ muted: "border-muted-foreground",
24
+ primary: "border-primary",
25
+ accent: "border-accent",
26
+ success: "border-green-600",
27
+ warning: "border-amber-600",
28
+ destructive: "border-red-600"
29
+ };
30
+ var inputVariants = cva(
31
+ "relative w-full flex items-center h-10 bg-card text-foreground overflow-hidden transition-colors",
32
+ {
33
+ variants: {
34
+ variant: {
35
+ default: "p-2 rounded-md",
36
+ rounded: "p-2 rounded-full",
37
+ ghost: "p-0! !bg-transparent !bg-none"
38
+ },
39
+ status: {
40
+ default: "",
41
+ error: ""
42
+ },
43
+ hasIcon: { true: "pl-8", false: "pl-3" },
44
+ disabled: { true: "cursor-not-allowed", false: "" }
45
+ },
46
+ defaultVariants: { variant: "default", status: "default", hasIcon: false, disabled: false }
47
+ }
48
+ );
49
+ var ERROR_BORDER = "border !border-red-600";
50
+ var ERROR_FOCUS_RING = "focus-visible:ring-destructive/20 has-[:focus-visible]:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:has-[:focus-visible]:ring-destructive/40";
51
+ var FOCUS_INTERACTIVE_BASE = "border focus-within:border-primary";
52
+ var HOVER_INTERACTIVE_BASE = "hover:border-primary";
53
+ var STATIC_BASE = "border";
54
+ var BaseInput = React12__default.forwardRef(
55
+ ({ children, variant = "default", status = "default", bordered, borderColor, className, style, disabled = false, onHover, onClick, hasIcon, ...rest }, ref) => {
56
+ const recipe = useNajmComponentStyle("input");
57
+ const isGhost = variant === "ghost";
58
+ const isError = status === "error";
59
+ const hasExplicitColor = !!borderColor;
60
+ const isBordered = bordered !== false;
61
+ const recipeRadius = variant === "default" ? resolveRadiusValue(recipe?.radius) : void 0;
62
+ let colorClass = "";
63
+ let borderClass = "";
64
+ if (!isGhost) {
65
+ if (isError) {
66
+ borderClass = ERROR_BORDER;
67
+ } else if (hasExplicitColor) {
68
+ borderClass = `${STATIC_BASE} ${FOCUS_INTERACTIVE_BASE} ${HOVER_INTERACTIVE_BASE}`;
69
+ colorClass = PRESETS[borderColor] ?? (NO_SHADE.has(borderColor) ? `border-${borderColor}` : `border-${borderColor}-600`);
70
+ } else {
71
+ const restingBorderClass = inputBorderClasses(isBordered);
72
+ borderClass = isBordered ? `${STATIC_BASE} ${restingBorderClass} [&:not(:focus-within):not([data-state=open]):hover]:border-input focus-within:border-ring data-[state=open]:border-ring` : restingBorderClass;
73
+ }
74
+ }
75
+ return /* @__PURE__ */ jsx(
76
+ "div",
77
+ {
78
+ ref,
79
+ "data-bordered": !isGhost && !isBordered ? "false" : !isGhost ? "true" : void 0,
80
+ style: recipeRadius ? { borderRadius: recipeRadius, ...style } : style,
81
+ className: cn(
82
+ inputVariants({ variant, status, hasIcon, disabled }),
83
+ // The wrapper is what the user reads as "the field": the inner
84
+ // control is stripped of its own ring precisely so this element can
85
+ // carry the border. Until this was added, the border merely changed
86
+ // colour on focus — a 1px hue shift and nothing else — and the number
87
+ // fields and the multi-select trigger came out of the acceptance walk
88
+ // as tab stops with no visible indicator at all.
89
+ !isGhost && focusRingWithinClasses,
90
+ !isGhost && isError && ERROR_FOCUS_RING,
91
+ !isGhost && borderClass,
92
+ !isGhost && colorClass,
93
+ className
94
+ ),
95
+ onMouseEnter: disabled ? void 0 : onHover,
96
+ onClick: disabled ? void 0 : onClick,
97
+ ...rest,
98
+ children
99
+ }
100
+ );
101
+ }
102
+ );
103
+ BaseInput.displayName = "BaseInput";
104
+ var variantClasses = {
105
+ primary: "bg-primary text-primary-foreground hover:bg-primary/90",
106
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
107
+ ghost: "bg-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground",
108
+ destructive: "bg-transparent text-destructive hover:bg-destructive/10"
109
+ };
110
+ var sizeClasses = {
111
+ sm: "h-6 w-6",
112
+ md: "h-8 w-8",
113
+ lg: "h-10 w-10"
114
+ };
115
+ var IconButton = React12.forwardRef(
116
+ ({
117
+ className,
118
+ variant = "ghost",
119
+ size = "md",
120
+ active = false,
121
+ type = "button",
122
+ children,
123
+ style,
124
+ ...props
125
+ }, ref) => {
126
+ const recipe = useNajmComponentStyle("button");
127
+ const recipeRadius = resolveRadiusValue(recipe?.radius);
128
+ return /* @__PURE__ */ jsx(
129
+ "button",
130
+ {
131
+ ref,
132
+ type,
133
+ "aria-pressed": active || void 0,
134
+ style: recipeRadius ? { borderRadius: recipeRadius, ...style } : style,
135
+ className: cn(
136
+ "inline-flex shrink-0 cursor-pointer items-center justify-center rounded-md transition-colors",
137
+ "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
138
+ "disabled:cursor-not-allowed disabled:opacity-50",
139
+ variantClasses[variant],
140
+ sizeClasses[size],
141
+ active && "bg-accent text-accent-foreground",
142
+ className
143
+ ),
144
+ ...props,
145
+ children
146
+ }
147
+ );
148
+ }
149
+ );
150
+ IconButton.displayName = "IconButton";
151
+ var Input = React12.forwardRef(function Input2({ className, type, ...props }, ref) {
152
+ return /* @__PURE__ */ jsx(
153
+ "input",
154
+ {
155
+ ref,
156
+ type,
157
+ "data-slot": "input",
158
+ className: cn(
159
+ "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground border-input flex h-9 w-full min-w-0 rounded-md border bg-card px-3 py-1 text-base text-foreground shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
160
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
161
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
162
+ className
163
+ ),
164
+ ...props
165
+ }
166
+ );
167
+ });
168
+
169
+ // src/location/contracts.ts
170
+ var DEFAULT_LOCATION_VALUE = {
171
+ address: "",
172
+ latitude: null,
173
+ longitude: null
174
+ };
175
+ function isCompleteCoordinatePair(value) {
176
+ const { latitude, longitude } = value;
177
+ return typeof latitude === "number" && Number.isFinite(latitude) && latitude >= -90 && latitude <= 90 && typeof longitude === "number" && Number.isFinite(longitude) && longitude >= -180 && longitude <= 180;
178
+ }
179
+ function normalizeLocationValue(value) {
180
+ const address = typeof value?.address === "string" ? value.address : "";
181
+ if (!isCompleteCoordinatePair({ latitude: value?.latitude ?? null, longitude: value?.longitude ?? null })) {
182
+ return { address, latitude: null, longitude: null };
183
+ }
184
+ return { address, latitude: value.latitude, longitude: value.longitude };
185
+ }
186
+ function locationCoordinates(value) {
187
+ return isCompleteCoordinatePair(value) ? { latitude: value.latitude, longitude: value.longitude } : null;
188
+ }
189
+ function moveCoordinates(value, latitudeDelta, longitudeDelta) {
190
+ return {
191
+ latitude: Math.max(-90, Math.min(90, value.latitude + latitudeDelta)),
192
+ longitude: Math.max(-180, Math.min(180, value.longitude + longitudeDelta))
193
+ };
194
+ }
195
+ function Sheet({ ...props }) {
196
+ return /* @__PURE__ */ jsx(SheetPrimitive.Root, { "data-slot": "sheet", ...props });
197
+ }
198
+ function SheetTrigger({ ...props }) {
199
+ return /* @__PURE__ */ jsx(SheetPrimitive.Trigger, { "data-slot": "sheet-trigger", ...props });
200
+ }
201
+ function SheetClose({ ...props }) {
202
+ return /* @__PURE__ */ jsx(SheetPrimitive.Close, { "data-slot": "sheet-close", ...props });
203
+ }
204
+ function SheetPortal({ ...props }) {
205
+ const container = React12.useContext(NajmThemeContainerCtx);
206
+ return /* @__PURE__ */ jsx(SheetPrimitive.Portal, { "data-slot": "sheet-portal", container: container ?? void 0, ...props });
207
+ }
208
+ function SheetOverlay({ className, ...props }) {
209
+ return /* @__PURE__ */ jsx(
210
+ SheetPrimitive.Overlay,
211
+ {
212
+ "data-slot": "sheet-overlay",
213
+ className: cn(
214
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
215
+ className
216
+ ),
217
+ ...props
218
+ }
219
+ );
220
+ }
221
+ var sheetVariants = cva(
222
+ "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 fixed z-50 gap-4 shadow-lg transition ease-in-out border-border",
223
+ {
224
+ variants: {
225
+ side: {
226
+ top: "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 border-b",
227
+ bottom: "data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 border-t",
228
+ left: "data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
229
+ right: "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm"
230
+ }
231
+ },
232
+ defaultVariants: { side: "right" }
233
+ }
234
+ );
235
+ function SheetContent({ className, portalClassName, side, children, ...props }) {
236
+ return /* @__PURE__ */ jsx(SheetPortal, { children: /* @__PURE__ */ jsxs("div", { className: portalClassName, children: [
237
+ /* @__PURE__ */ jsx(SheetOverlay, {}),
238
+ /* @__PURE__ */ jsxs(
239
+ SheetPrimitive.Content,
240
+ {
241
+ "data-slot": "sheet-content",
242
+ className: cn(sheetVariants({ side }), className),
243
+ ...props,
244
+ children: [
245
+ children,
246
+ /* @__PURE__ */ jsxs(SheetPrimitive.Close, { className: "ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 end-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none", children: [
247
+ /* @__PURE__ */ jsx(X, { className: "size-4" }),
248
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
249
+ ] })
250
+ ]
251
+ }
252
+ )
253
+ ] }) });
254
+ }
255
+ function SheetHeader({ className, ...props }) {
256
+ return /* @__PURE__ */ jsx("div", { "data-slot": "sheet-header", className: cn("flex flex-col gap-1.5 p-6", className), ...props });
257
+ }
258
+ function SheetFooter({ className, ...props }) {
259
+ return /* @__PURE__ */ jsx("div", { "data-slot": "sheet-footer", className: cn("mt-auto flex flex-col gap-2 p-6", className), ...props });
260
+ }
261
+ function SheetTitle({ className, ...props }) {
262
+ return /* @__PURE__ */ jsx(SheetPrimitive.Title, { "data-slot": "sheet-title", className: cn("text-foreground font-semibold", className), ...props });
263
+ }
264
+ function SheetDescription({ className, ...props }) {
265
+ return /* @__PURE__ */ jsx(SheetPrimitive.Description, { "data-slot": "sheet-description", className: cn("text-muted-foreground text-sm", className), ...props });
266
+ }
267
+ var PortalScopeContext = createContext(void 0);
268
+ function NPortalScopeProvider({ className, children }) {
269
+ return /* @__PURE__ */ jsx(PortalScopeContext.Provider, { value: className, children });
270
+ }
271
+ var useNPortalScope = () => useContext(PortalScopeContext);
272
+ var responsiveSectionPadding = "px-3 py-3 lg:px-3 2xl:px-4";
273
+ function NSheet({
274
+ open,
275
+ onOpenChange,
276
+ icon: Icon,
277
+ title,
278
+ description,
279
+ width = 480,
280
+ side = "right",
281
+ portalClassName,
282
+ classNames,
283
+ bodyClassName,
284
+ contentClassName,
285
+ children,
286
+ footer
287
+ }) {
288
+ const inheritedScope = useNPortalScope();
289
+ const portal = portalClassName ?? inheritedScope;
290
+ const style = {
291
+ width: `min(${width}px, 95vw)`,
292
+ maxWidth: `${width}px`
293
+ };
294
+ return /* @__PURE__ */ jsx(Sheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs(
295
+ SheetContent,
296
+ {
297
+ side,
298
+ portalClassName: portal,
299
+ className: cn(
300
+ "flex h-dvh max-h-dvh flex-col gap-0 overflow-hidden bg-background p-0 text-foreground",
301
+ contentClassName,
302
+ classNames?.content
303
+ ),
304
+ style,
305
+ children: [
306
+ /* @__PURE__ */ jsx(
307
+ SheetHeader,
308
+ {
309
+ className: cn(
310
+ "shrink-0 border-b border-border p-0",
311
+ responsiveSectionPadding,
312
+ classNames?.header
313
+ ),
314
+ children: /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2 lg:gap-3 xl:gap-3 2xl:gap-4", children: [
315
+ /* @__PURE__ */ jsx("div", { className: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary/10", children: /* @__PURE__ */ jsx(Icon, { className: "size-4 text-primary" }) }),
316
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
317
+ /* @__PURE__ */ jsx(SheetTitle, { className: "truncate text-sm font-semibold text-foreground", children: title }),
318
+ description && /* @__PURE__ */ jsx(SheetDescription, { className: "truncate text-xs text-muted-foreground", children: description })
319
+ ] })
320
+ ] })
321
+ }
322
+ ),
323
+ /* @__PURE__ */ jsx(NajmScroll, { axis: "y", className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx(
324
+ "div",
325
+ {
326
+ "data-slot": "sheet-body",
327
+ className: cn(responsiveSectionPadding, bodyClassName, classNames?.body),
328
+ children
329
+ }
330
+ ) }),
331
+ footer && /* @__PURE__ */ jsx(
332
+ "div",
333
+ {
334
+ "data-slot": "sheet-footer",
335
+ className: cn(
336
+ "shrink-0 border-t border-border",
337
+ responsiveSectionPadding,
338
+ classNames?.footer
339
+ ),
340
+ children: footer
341
+ }
342
+ )
343
+ ]
344
+ }
345
+ ) });
346
+ }
347
+ function Dialog({ ...props }) {
348
+ return /* @__PURE__ */ jsx(SheetPrimitive.Root, { "data-slot": "dialog", ...props });
349
+ }
350
+ function DialogTrigger({ ...props }) {
351
+ return /* @__PURE__ */ jsx(SheetPrimitive.Trigger, { "data-slot": "dialog-trigger", ...props });
352
+ }
353
+ function DialogPortal({ ...props }) {
354
+ const container = React12.useContext(NajmThemeContainerCtx);
355
+ return /* @__PURE__ */ jsx(SheetPrimitive.Portal, { "data-slot": "dialog-portal", container: container ?? void 0, ...props });
356
+ }
357
+ function DialogClose({ ...props }) {
358
+ return /* @__PURE__ */ jsx(SheetPrimitive.Close, { "data-slot": "dialog-close cursor-pointer", ...props });
359
+ }
360
+ function DialogOverlay({ className, ...props }) {
361
+ return /* @__PURE__ */ jsx(
362
+ SheetPrimitive.Overlay,
363
+ {
364
+ "data-slot": "dialog-overlay",
365
+ className: cn(
366
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50 backdrop-blur-sm",
367
+ className
368
+ ),
369
+ ...props
370
+ }
371
+ );
372
+ }
373
+ var dialogPaddingMap = {
374
+ none: "p-0 gap-0 overflow-hidden",
375
+ sm: "p-3",
376
+ md: "p-6",
377
+ lg: "p-8"
378
+ };
379
+ var MOBILE_PADDING_QUERY = "(max-width: 639.98px)";
380
+ function getDefaultPadding() {
381
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return "md";
382
+ return window.matchMedia(MOBILE_PADDING_QUERY).matches ? "sm" : "md";
383
+ }
384
+ function useResponsivePadding(value) {
385
+ const [resolved, setResolved] = React12.useState(() => value ?? getDefaultPadding());
386
+ React12.useEffect(() => {
387
+ if (value) {
388
+ setResolved(value);
389
+ return;
390
+ }
391
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
392
+ const mql = window.matchMedia(MOBILE_PADDING_QUERY);
393
+ const update = () => setResolved(mql.matches ? "sm" : "md");
394
+ update();
395
+ mql.addEventListener("change", update);
396
+ return () => mql.removeEventListener("change", update);
397
+ }, [value]);
398
+ return resolved;
399
+ }
400
+ function DialogContent({
401
+ className,
402
+ children,
403
+ padding,
404
+ hideClose = false,
405
+ onOpenAutoFocus,
406
+ onCloseAutoFocus,
407
+ ...props
408
+ }) {
409
+ const resolvedPadding = useResponsivePadding(padding);
410
+ const portalClassName = useNPortalScope();
411
+ const openerRef = React12.useRef(null);
412
+ return /* @__PURE__ */ jsx(DialogPortal, { "data-slot": "dialog-portal", children: /* @__PURE__ */ jsxs("div", { className: portalClassName, children: [
413
+ /* @__PURE__ */ jsx(DialogOverlay, {}),
414
+ /* @__PURE__ */ jsxs(
415
+ SheetPrimitive.Content,
416
+ {
417
+ "data-slot": "dialog-content",
418
+ "data-padding": resolvedPadding,
419
+ onOpenAutoFocus: (event) => {
420
+ const active = document.activeElement;
421
+ openerRef.current = active instanceof HTMLElement && active !== document.body ? active : null;
422
+ onOpenAutoFocus?.(event);
423
+ },
424
+ onCloseAutoFocus: (event) => {
425
+ onCloseAutoFocus?.(event);
426
+ if (event.defaultPrevented) return;
427
+ const opener = openerRef.current;
428
+ setTimeout(() => {
429
+ if (!opener?.isConnected) return;
430
+ const active = document.activeElement;
431
+ const lost = !active || active === document.body || active === document.documentElement || !active.isConnected;
432
+ if (lost) opener.focus();
433
+ }, 0);
434
+ },
435
+ className: cn(
436
+ "bg-card text-card-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-sm translate-x-[-50%] translate-y-[-50%] gap-4 rounded-none lg:rounded-lg najm-border border-border shadow-lg duration-200",
437
+ dialogPaddingMap[resolvedPadding],
438
+ className
439
+ ),
440
+ ...props,
441
+ children: [
442
+ children,
443
+ !hideClose && /* @__PURE__ */ jsxs(SheetPrimitive.Close, { className: "ring-offset-background cursor-pointer focus:ring-ring absolute top-4 end-4 rounded-xs text-muted-foreground hover:text-foreground transition-colors focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", children: [
444
+ /* @__PURE__ */ jsx(XIcon, {}),
445
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
446
+ ] })
447
+ ]
448
+ }
449
+ )
450
+ ] }) });
451
+ }
452
+ function DialogHeader({ className, ...props }) {
453
+ return /* @__PURE__ */ jsx("div", { "data-slot": "dialog-header", className: cn("flex flex-col gap-2 text-center sm:text-start", className), ...props });
454
+ }
455
+ function DialogFooter({ className, ...props }) {
456
+ return /* @__PURE__ */ jsx("div", { "data-slot": "dialog-footer", className: cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className), ...props });
457
+ }
458
+ function DialogTitle({ className, ...props }) {
459
+ return /* @__PURE__ */ jsx(SheetPrimitive.Title, { "data-slot": "dialog-title", className: cn("text-lg leading-none font-semibold", className), ...props });
460
+ }
461
+ function DialogDescription({ className, ...props }) {
462
+ return /* @__PURE__ */ jsx(SheetPrimitive.Description, { "data-slot": "dialog-description", className: cn("text-muted-foreground text-sm", className), ...props });
463
+ }
464
+ var NSidebarContext = createContext(null);
465
+ function useNSidebar() {
466
+ return useContext(NSidebarContext);
467
+ }
468
+ function NSidebarProvider({
469
+ children,
470
+ defaultCollapsed = false,
471
+ mobileBreakpoint = "md"
472
+ }) {
473
+ const [collapsed, setCollapsed] = useState(defaultCollapsed);
474
+ const [mobileOpen, setMobileOpen] = useState(false);
475
+ const openMobile = useCallback(() => setMobileOpen(true), []);
476
+ const closeMobile = useCallback(() => setMobileOpen(false), []);
477
+ const toggleCollapsed = useCallback(() => setCollapsed((prev) => !prev), []);
478
+ const value = useMemo(
479
+ () => ({
480
+ collapsed,
481
+ mobileOpen,
482
+ openMobile,
483
+ closeMobile,
484
+ setMobileOpen,
485
+ setCollapsed,
486
+ toggleCollapsed,
487
+ mobileBreakpoint
488
+ }),
489
+ [collapsed, mobileOpen, openMobile, closeMobile, toggleCollapsed, mobileBreakpoint]
490
+ );
491
+ return /* @__PURE__ */ jsx(NSidebarContext.Provider, { value, children });
492
+ }
493
+ function NPageHeaderActions({ children, className }) {
494
+ return /* @__PURE__ */ jsx("div", { className: cn("flex shrink-0 items-center gap-0 lg:gap-1 xl:gap-2 2xl:gap-2", className), children });
495
+ }
496
+ NPageHeaderActions.displayName = "NPageHeaderActions";
497
+ function NPageHeaderCompactActions({ children, className }) {
498
+ return /* @__PURE__ */ jsx("div", { className: cn("flex shrink-0 items-center", className), children });
499
+ }
500
+ NPageHeaderCompactActions.displayName = "NPageHeaderCompactActions";
501
+ function NPageHeaderFilters({ children, className }) {
502
+ return /* @__PURE__ */ jsx("div", { className: cn("py-3 lg:px-3 xl:px-4 2xl:px-5", className), children });
503
+ }
504
+ NPageHeaderFilters.displayName = "NPageHeaderFilters";
505
+ function NPageHeaderTop({ children, className }) {
506
+ return /* @__PURE__ */ jsx("div", { className, children });
507
+ }
508
+ NPageHeaderTop.displayName = "NPageHeaderTop";
509
+ function isPageHeaderSlot(child, slot) {
510
+ return React12__default.isValidElement(child) && child.type === slot;
511
+ }
512
+ function getSlotElements(children, slot) {
513
+ const matches = React12__default.Children.toArray(children).filter((child) => isPageHeaderSlot(child, slot));
514
+ return matches.length > 0 ? matches : void 0;
515
+ }
516
+ var responsiveClasses = {
517
+ sm: {
518
+ main: "sm:flex sm:justify-between",
519
+ identity: "sm:col-start-auto sm:row-start-auto sm:justify-self-auto",
520
+ controls: "sm:col-start-auto sm:row-start-auto sm:justify-self-auto",
521
+ fullActions: "sm:flex",
522
+ compactActions: "sm:hidden",
523
+ sidebarTrigger: "sm:hidden",
524
+ icon: "sm:flex",
525
+ subtitle: "sm:block",
526
+ minH: "sm:min-h-14"
527
+ },
528
+ md: {
529
+ main: "md:flex md:justify-between",
530
+ identity: "md:col-start-auto md:row-start-auto md:justify-self-auto",
531
+ controls: "md:col-start-auto md:row-start-auto md:justify-self-auto",
532
+ fullActions: "md:flex",
533
+ compactActions: "md:hidden",
534
+ sidebarTrigger: "md:hidden",
535
+ icon: "md:flex",
536
+ subtitle: "md:block",
537
+ minH: "md:min-h-14"
538
+ },
539
+ lg: {
540
+ main: "lg:flex lg:justify-between lg:px-3 2xl:px-4",
541
+ identity: "lg:col-start-auto lg:row-start-auto lg:justify-self-auto",
542
+ controls: "lg:col-start-auto lg:row-start-auto lg:justify-self-auto",
543
+ fullActions: "lg:flex",
544
+ compactActions: "lg:hidden",
545
+ sidebarTrigger: "lg:hidden",
546
+ icon: "lg:flex",
547
+ subtitle: "lg:block",
548
+ minH: "lg:min-h-14"
549
+ }
550
+ };
551
+ function NPageHeader({
552
+ icon: Icon,
553
+ title,
554
+ subtitle,
555
+ actions,
556
+ compactActions,
557
+ filters,
558
+ top,
559
+ mobileBreakpoint: mobileBreakpointProp,
560
+ onSidebarOpen,
561
+ sidebarTriggerLabel = "Open sidebar",
562
+ sidebarTriggerClassName,
563
+ search,
564
+ children,
565
+ className,
566
+ headerClassName,
567
+ card,
568
+ bordered
569
+ }) {
570
+ const [internalSearch, setInternalSearch] = useState("");
571
+ const recipe = useNajmComponentStyle("pageHeader");
572
+ const sidebar = useNSidebar();
573
+ const resolvedOnSidebarOpen = onSidebarOpen ?? sidebar?.openMobile;
574
+ const mobileBreakpoint = mobileBreakpointProp ?? sidebar?.mobileBreakpoint ?? "md";
575
+ const searchValue = search?.value ?? internalSearch;
576
+ const handleSearchChange = useCallback((e) => {
577
+ setInternalSearch(e.currentTarget.value);
578
+ search?.onChange?.(e);
579
+ }, [search?.onChange]);
580
+ const slottedActions = getSlotElements(children, NPageHeaderActions);
581
+ const slottedCompactActions = getSlotElements(children, NPageHeaderCompactActions);
582
+ const slottedFilters = getSlotElements(children, NPageHeaderFilters);
583
+ const slottedTop = getSlotElements(children, NPageHeaderTop);
584
+ const resolvedActions = slottedActions ?? actions;
585
+ const resolvedCompactActions = slottedCompactActions ?? compactActions;
586
+ const resolvedFilters = slottedFilters ?? filters;
587
+ const resolvedTop = slottedTop ?? top;
588
+ const hasCompactActions = resolvedCompactActions !== void 0 && resolvedCompactActions !== null && resolvedCompactActions !== false;
589
+ const breakpointClasses = responsiveClasses[mobileBreakpoint];
590
+ const isCard = card ?? recipe?.card ?? bordered === true;
591
+ const recipeRadius = resolveRadiusValue(recipe?.radius);
592
+ const bleedStyle = isCard ? void 0 : {
593
+ marginTop: "calc(var(--najm-section-gap, 0px) * -1)",
594
+ marginInline: "calc(var(--najm-page-gutter, 0px) * -1)"
595
+ };
596
+ const recipeStyle = recipeRadius || recipe?.borderWidth || bleedStyle ? {
597
+ ...bleedStyle,
598
+ ...recipeRadius ? { borderRadius: recipeRadius } : {},
599
+ ...recipe?.borderWidth ? { borderWidth: recipe.borderWidth } : {}
600
+ } : void 0;
601
+ return /* @__PURE__ */ jsxs(
602
+ "div",
603
+ {
604
+ "data-slot": "page-header",
605
+ "data-card": isCard ? "true" : void 0,
606
+ "data-bordered": bordered === false ? "false" : bordered ? "true" : void 0,
607
+ style: recipeStyle,
608
+ className: cn(
609
+ isCard ? cn("rounded-xl bg-card text-card-foreground shadow-none", surfaceBorderClasses(true)) : cn(
610
+ "border-b bg-background text-foreground",
611
+ surfaceBorderClasses(true, "bottom").replace("najm-border-b", "najm-border-b")
612
+ ),
613
+ className
614
+ ),
615
+ children: [
616
+ resolvedTop,
617
+ /* @__PURE__ */ jsxs(
618
+ "div",
619
+ {
620
+ "data-slot": "page-header-main",
621
+ className: cn(
622
+ // min-h-14 matches NSidebarHeader so the two bottom rules line up.
623
+ "relative grid min-h-14 grid-cols-[minmax(2.75rem,1fr)_minmax(0,auto)_minmax(2.75rem,1fr)] items-center gap-2 px-2 lg:px-3 2xl:px-4",
624
+ breakpointClasses.main,
625
+ breakpointClasses.minH,
626
+ headerClassName
627
+ ),
628
+ children: [
629
+ resolvedOnSidebarOpen && /* @__PURE__ */ jsx(
630
+ Button,
631
+ {
632
+ type: "button",
633
+ variant: "ghost",
634
+ size: "icon",
635
+ onClick: resolvedOnSidebarOpen,
636
+ "aria-label": sidebarTriggerLabel,
637
+ "data-slot": "page-header-sidebar-trigger",
638
+ className: cn(
639
+ "col-start-1 row-start-1 justify-self-start",
640
+ breakpointClasses.sidebarTrigger,
641
+ sidebarTriggerClassName
642
+ ),
643
+ children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
644
+ }
645
+ ),
646
+ /* @__PURE__ */ jsxs(
647
+ "div",
648
+ {
649
+ "data-slot": "page-header-identity",
650
+ className: cn(
651
+ "col-start-2 row-start-1 flex min-w-0 max-w-full items-center justify-self-center gap-2 lg:gap-3 xl:gap-3 2xl:gap-4",
652
+ breakpointClasses.identity
653
+ ),
654
+ children: [
655
+ /* @__PURE__ */ jsx("div", { className: cn("hidden h-8 w-8 shrink-0 rounded-lg bg-primary/10 items-center justify-center", breakpointClasses.icon), children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-primary" }) }),
656
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
657
+ /* @__PURE__ */ jsx("h2", { className: "truncate text-sm font-semibold text-foreground", children: title }),
658
+ subtitle && /* @__PURE__ */ jsx("p", { className: cn("hidden truncate text-xs leading-relaxed text-muted-foreground sm:text-sm sm:leading-normal", breakpointClasses.subtitle), children: subtitle })
659
+ ] })
660
+ ]
661
+ }
662
+ ),
663
+ /* @__PURE__ */ jsxs(
664
+ "div",
665
+ {
666
+ "data-slot": "page-header-controls",
667
+ className: cn(
668
+ "col-start-3 row-start-1 flex min-w-0 shrink-0 items-center justify-self-end gap-3",
669
+ breakpointClasses.controls
670
+ ),
671
+ children: [
672
+ hasCompactActions && /* @__PURE__ */ jsx(
673
+ "div",
674
+ {
675
+ "data-slot": "page-header-compact-actions",
676
+ className: cn("flex items-center", breakpointClasses.compactActions),
677
+ children: slottedCompactActions ? resolvedCompactActions : /* @__PURE__ */ jsx(NPageHeaderCompactActions, { children: resolvedCompactActions })
678
+ }
679
+ ),
680
+ /* @__PURE__ */ jsxs(
681
+ "div",
682
+ {
683
+ "data-slot": "page-header-full-actions",
684
+ className: cn(
685
+ "shrink-0 items-center gap-3",
686
+ hasCompactActions ? "hidden" : "flex",
687
+ hasCompactActions && breakpointClasses.fullActions
688
+ ),
689
+ children: [
690
+ search && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
691
+ /* @__PURE__ */ jsx(Search, { size: 14, className: "absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground pointer-events-none" }),
692
+ /* @__PURE__ */ jsx(
693
+ "input",
694
+ {
695
+ ref: search.ref,
696
+ type: "text",
697
+ value: searchValue,
698
+ placeholder: search.placeholder ?? "Search...",
699
+ className: cn(
700
+ "h-9 w-full max-w-[280px] rounded-lg bg-background pl-9 pr-3 text-sm text-foreground placeholder:text-muted-foreground outline-none border border-border focus:border-primary focus:ring-1 focus:ring-primary/20 transition-colors",
701
+ search.className
702
+ ),
703
+ onChange: handleSearchChange
704
+ }
705
+ )
706
+ ] }),
707
+ resolvedActions && (slottedActions ? resolvedActions : /* @__PURE__ */ jsx(NPageHeaderActions, { children: resolvedActions }))
708
+ ]
709
+ }
710
+ )
711
+ ]
712
+ }
713
+ )
714
+ ]
715
+ }
716
+ ),
717
+ resolvedFilters && (slottedFilters ? resolvedFilters : /* @__PURE__ */ jsx(NPageHeaderFilters, { children: resolvedFilters }))
718
+ ]
719
+ }
720
+ );
721
+ }
722
+ var generateId = () => `dialog-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
723
+ var buildButtonConfig = (partial, defaults) => {
724
+ return { ...defaults, ...partial };
725
+ };
726
+ function createDialogStore() {
727
+ return create((set, get) => ({
728
+ dialogs: [],
729
+ pushDialog: (config) => {
730
+ return new Promise((resolve, reject) => {
731
+ const id = generateId();
732
+ const primaryButtonConfig = buildButtonConfig(config.primaryButton, {
733
+ text: "Confirm",
734
+ variant: "default",
735
+ loading: false,
736
+ disabled: false,
737
+ loadingText: "Processing..."
738
+ });
739
+ const secondaryButtonConfig = buildButtonConfig(config.secondaryButton, {
740
+ text: "Cancel",
741
+ variant: "outline",
742
+ loading: false,
743
+ disabled: false,
744
+ loadingText: "Processing..."
745
+ });
746
+ const dialogConfig = {
747
+ id,
748
+ title: config.title,
749
+ description: config.description,
750
+ pageHeader: config.pageHeader,
751
+ padding: config.padding,
752
+ children: config.children,
753
+ primaryButton: primaryButtonConfig,
754
+ secondaryButton: secondaryButtonConfig,
755
+ showButtons: config.showButtons ?? true,
756
+ size: config.size,
757
+ width: config.width,
758
+ height: config.height,
759
+ className: config.className,
760
+ actionMode: config.actionMode ?? "auto",
761
+ render: config.render,
762
+ resolve,
763
+ reject
764
+ };
765
+ set((state) => ({ dialogs: [...state.dialogs, dialogConfig] }));
766
+ });
767
+ },
768
+ popDialog: async (result) => {
769
+ const { dialogs } = get();
770
+ if (dialogs.length === 0) return;
771
+ const dialog = dialogs[dialogs.length - 1];
772
+ const closeCurrentDialog = () => {
773
+ dialog.resolve?.(result);
774
+ set((state) => ({ dialogs: state.dialogs.slice(0, -1) }));
775
+ };
776
+ const handler = dialog.primaryButton?.onClick ?? dialog.primaryButton?.onConfirm;
777
+ if (handler && result != null) {
778
+ get().setPrimaryLoading(true);
779
+ try {
780
+ await handler(result);
781
+ closeCurrentDialog();
782
+ } finally {
783
+ get().setPrimaryLoading(false);
784
+ }
785
+ return;
786
+ }
787
+ dialog.resolve?.(result);
788
+ set((state) => ({ dialogs: state.dialogs.slice(0, -1) }));
789
+ },
790
+ closeDialog: (id, result) => {
791
+ const { dialogs } = get();
792
+ const dialog = dialogs.find((d) => d.id === id);
793
+ if (!dialog) return;
794
+ dialog.resolve?.(result);
795
+ set((state) => ({ dialogs: state.dialogs.filter((d) => d.id !== id) }));
796
+ },
797
+ closeAllDialogs: () => {
798
+ const { dialogs } = get();
799
+ dialogs.forEach((d) => d.reject?.(new Error("All dialogs closed")));
800
+ set({ dialogs: [] });
801
+ },
802
+ getCurrentDialog: () => {
803
+ const { dialogs } = get();
804
+ return dialogs[dialogs.length - 1];
805
+ },
806
+ setPrimaryLoading: (loading) => {
807
+ set((state) => {
808
+ const newDialogs = [...state.dialogs];
809
+ const idx = newDialogs.length - 1;
810
+ if (idx < 0 || !newDialogs[idx].primaryButton) return state;
811
+ newDialogs[idx] = {
812
+ ...newDialogs[idx],
813
+ primaryButton: { ...newDialogs[idx].primaryButton, loading }
814
+ };
815
+ return { dialogs: newDialogs };
816
+ });
817
+ },
818
+ setSecondaryLoading: (loading) => {
819
+ set((state) => {
820
+ const newDialogs = [...state.dialogs];
821
+ const idx = newDialogs.length - 1;
822
+ if (idx < 0 || !newDialogs[idx].secondaryButton) return state;
823
+ newDialogs[idx] = {
824
+ ...newDialogs[idx],
825
+ secondaryButton: { ...newDialogs[idx].secondaryButton, loading }
826
+ };
827
+ return { dialogs: newDialogs };
828
+ });
829
+ },
830
+ updatePrimaryButton: (config, dialogId) => {
831
+ set((state) => {
832
+ const newDialogs = [...state.dialogs];
833
+ const idx = dialogId ? newDialogs.findIndex((d) => d.id === dialogId) : newDialogs.length - 1;
834
+ if (idx === -1 || !newDialogs[idx].primaryButton) return state;
835
+ newDialogs[idx] = {
836
+ ...newDialogs[idx],
837
+ primaryButton: { ...newDialogs[idx].primaryButton, ...config }
838
+ };
839
+ return { dialogs: newDialogs };
840
+ });
841
+ },
842
+ updateSecondaryButton: (config, dialogId) => {
843
+ set((state) => {
844
+ const newDialogs = [...state.dialogs];
845
+ const idx = dialogId ? newDialogs.findIndex((d) => d.id === dialogId) : newDialogs.length - 1;
846
+ if (idx === -1 || !newDialogs[idx].secondaryButton) return state;
847
+ newDialogs[idx] = {
848
+ ...newDialogs[idx],
849
+ secondaryButton: { ...newDialogs[idx].secondaryButton, ...config }
850
+ };
851
+ return { dialogs: newDialogs };
852
+ });
853
+ },
854
+ updateShowButtons: (show, dialogId) => {
855
+ set((state) => {
856
+ const newDialogs = [...state.dialogs];
857
+ const idx = dialogId ? newDialogs.findIndex((d) => d.id === dialogId) : newDialogs.length - 1;
858
+ if (idx === -1) return state;
859
+ newDialogs[idx] = { ...newDialogs[idx], showButtons: show };
860
+ return { dialogs: newDialogs };
861
+ });
862
+ },
863
+ handlePrimaryClick: async (dialogId, data) => {
864
+ const { dialogs, setPrimaryLoading, closeDialog } = get();
865
+ const dialog = dialogs.find((d) => d.id === dialogId);
866
+ if (!dialog?.primaryButton) return;
867
+ const handler = dialog.primaryButton.onClick ?? dialog.primaryButton.onConfirm;
868
+ if (handler) {
869
+ setPrimaryLoading(true);
870
+ try {
871
+ await handler(data);
872
+ closeDialog(dialogId, data);
873
+ } finally {
874
+ setPrimaryLoading(false);
875
+ }
876
+ } else {
877
+ closeDialog(dialogId, data);
878
+ }
879
+ },
880
+ handleSecondaryClick: async (dialogId, data) => {
881
+ const { dialogs, closeDialog } = get();
882
+ const dialog = dialogs.find((d) => d.id === dialogId);
883
+ if (!dialog?.secondaryButton) {
884
+ closeDialog(dialogId, null);
885
+ return;
886
+ }
887
+ const handler = dialog.secondaryButton.onClick ?? dialog.secondaryButton.onConfirm;
888
+ if (handler) {
889
+ await handler(data);
890
+ }
891
+ closeDialog(dialogId, null);
892
+ },
893
+ handleConfirm: async (data) => {
894
+ const { dialogs, handlePrimaryClick } = get();
895
+ const current = dialogs[dialogs.length - 1];
896
+ if (!current) return;
897
+ await handlePrimaryClick(current.id, data);
898
+ },
899
+ handleOpenChange: (dialogId, open) => {
900
+ if (!open) {
901
+ const { closeDialog } = get();
902
+ closeDialog(dialogId, null);
903
+ }
904
+ }
905
+ }));
906
+ }
907
+ var defaultStore = createDialogStore();
908
+ function useDialogStore() {
909
+ return defaultStore;
910
+ }
911
+ function NDialogHeader(_props) {
912
+ return null;
913
+ }
914
+ function NDialogDescription(_props) {
915
+ return null;
916
+ }
917
+ function NDialogPrimaryButton(_props) {
918
+ return null;
919
+ }
920
+ function NDialogSecondaryButton(_props) {
921
+ return null;
922
+ }
923
+ function parseDirectDialogSlots(children) {
924
+ const body = [];
925
+ const parsed = {};
926
+ React12__default.Children.forEach(children, (child) => {
927
+ if (!React12__default.isValidElement(child)) {
928
+ body.push(child);
929
+ return;
930
+ }
931
+ if (child.type === NDialogHeader) {
932
+ parsed.header = child.props;
933
+ return;
934
+ }
935
+ if (child.type === NDialogDescription) {
936
+ parsed.description = child.props;
937
+ return;
938
+ }
939
+ if (child.type === NDialogPrimaryButton || child.type === NDialogSecondaryButton) {
940
+ const { children: text, label, ...config } = child.props;
941
+ const button = { ...config, text: label ?? text ?? (child.type === NDialogPrimaryButton ? "Confirm" : "Cancel") };
942
+ if (child.type === NDialogPrimaryButton) parsed.primaryButton = button;
943
+ else parsed.secondaryButton = button;
944
+ return;
945
+ }
946
+ body.push(child);
947
+ });
948
+ return { ...parsed, body };
949
+ }
950
+ var dialogVariants = cva(
951
+ "flex flex-col w-full max-w-full h-full max-h-screen",
952
+ {
953
+ variants: {
954
+ variant: {
955
+ default: "",
956
+ window: "rounded-none lg:rounded-sm"
957
+ },
958
+ size: {
959
+ sm: "lg:max-w-md",
960
+ md: "lg:max-w-lg",
961
+ lg: "lg:max-w-xl",
962
+ xl: "lg:max-w-2xl",
963
+ xxl: "lg:max-w-3xl",
964
+ full: "lg:max-w-4xl"
965
+ },
966
+ width: {
967
+ sm: "lg:max-w-sm",
968
+ md: "lg:max-w-md",
969
+ lg: "lg:max-w-lg",
970
+ xl: "lg:max-w-xl",
971
+ xxl: "lg:max-w-2xl",
972
+ "3xl": "lg:max-w-3xl",
973
+ "4xl": "lg:max-w-4xl",
974
+ "5xl": "lg:max-w-5xl",
975
+ "6xl": "lg:max-w-6xl",
976
+ "7xl": "lg:max-w-7xl",
977
+ full: "lg:max-w-5/6",
978
+ auto: "lg:w-auto"
979
+ },
980
+ height: {
981
+ auto: "lg:h-auto lg:max-h-[95vh]",
982
+ sm: "lg:h-[40vh]",
983
+ md: "lg:h-[60vh]",
984
+ lg: "lg:h-[70vh]",
985
+ xl: "lg:h-[80vh]",
986
+ xxl: "lg:h-[95vh]",
987
+ full: "lg:h-[95vh]"
988
+ }
989
+ },
990
+ defaultVariants: {
991
+ variant: "default",
992
+ size: "xl",
993
+ width: "3xl",
994
+ height: "auto"
995
+ }
996
+ }
997
+ );
998
+ var CONTENT_ACTIONS_SELECTOR = '[data-najm-dialog-actions="content"], [data-najm-wizard-form="true"]';
999
+ function useContentOwnsActions() {
1000
+ const [contentElement, setContentElement] = React12__default.useState(null);
1001
+ const [contentOwnsActions, setContentOwnsActions] = React12__default.useState(false);
1002
+ const contentElementRef = React12__default.useRef(null);
1003
+ const handleContentElementRef = React12__default.useCallback((node) => {
1004
+ if (contentElementRef.current === node) return;
1005
+ contentElementRef.current = node;
1006
+ setContentElement(node);
1007
+ }, []);
1008
+ React12__default.useLayoutEffect(() => {
1009
+ if (!contentElement) {
1010
+ setContentOwnsActions((current) => current ? false : current);
1011
+ return;
1012
+ }
1013
+ const update = () => {
1014
+ const next = !!contentElement.querySelector(CONTENT_ACTIONS_SELECTOR);
1015
+ setContentOwnsActions((current) => current === next ? current : next);
1016
+ };
1017
+ update();
1018
+ const observer = new MutationObserver(update);
1019
+ observer.observe(contentElement, {
1020
+ childList: true,
1021
+ subtree: true,
1022
+ attributes: true,
1023
+ attributeFilter: ["data-najm-dialog-actions", "data-najm-wizard-form"]
1024
+ });
1025
+ return () => observer.disconnect();
1026
+ }, [contentElement]);
1027
+ return { contentOwnsActions, setContentElement: handleContentElementRef };
1028
+ }
1029
+ function shouldRenderDialogButtons(showButtons, actionMode = "auto", contentOwnsActions) {
1030
+ if (!showButtons || actionMode === "content") return false;
1031
+ if (actionMode === "auto" && contentOwnsActions) return false;
1032
+ return true;
1033
+ }
1034
+ var windowHeaderSpacing = {
1035
+ none: "px-3 py-2",
1036
+ sm: "-mx-3 -mt-3 px-3 py-2",
1037
+ md: "-mx-6 -mt-6 px-3 py-2",
1038
+ lg: "-mx-8 -mt-8 px-3 py-2"
1039
+ };
1040
+ function DialogChrome({
1041
+ pageHeader,
1042
+ title,
1043
+ description,
1044
+ padding,
1045
+ variant = "default",
1046
+ headerClassName,
1047
+ titleClassName,
1048
+ descriptionClassName,
1049
+ closeButtonClassName,
1050
+ contentRef,
1051
+ children
1052
+ }) {
1053
+ const resolvedPadding = useResponsivePadding(padding);
1054
+ const noTitle = !title || title.trim() === "";
1055
+ const noDescription = !description || description.trim() === "";
1056
+ const noHeader = noTitle && noDescription;
1057
+ const flush = resolvedPadding === "none";
1058
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1059
+ pageHeader ? /* @__PURE__ */ jsxs(Fragment, { children: [
1060
+ /* @__PURE__ */ jsx(DialogTitle, { className: cn("sr-only", titleClassName), children: pageHeader.title || title || "Dialog" }),
1061
+ (pageHeader.subtitle || description) && /* @__PURE__ */ jsx(DialogDescription, { className: cn("sr-only", descriptionClassName), children: pageHeader.subtitle || description }),
1062
+ /* @__PURE__ */ jsx("div", { className: cn("shrink-0", headerClassName), children: /* @__PURE__ */ jsx(
1063
+ NPageHeader,
1064
+ {
1065
+ ...pageHeader,
1066
+ card: false,
1067
+ actions: /* @__PURE__ */ jsxs(Fragment, { children: [
1068
+ pageHeader.actions,
1069
+ /* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { "aria-label": "Close", variant: "ghost", className: closeButtonClassName, children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) }) })
1070
+ ] })
1071
+ }
1072
+ ) })
1073
+ ] }) : variant === "window" ? /* @__PURE__ */ jsxs(
1074
+ DialogHeader,
1075
+ {
1076
+ className: cn(
1077
+ "flex-row items-center justify-between gap-3 border-b border-border bg-secondary text-start text-secondary-foreground",
1078
+ windowHeaderSpacing[resolvedPadding],
1079
+ headerClassName
1080
+ ),
1081
+ children: [
1082
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-1", children: [
1083
+ /* @__PURE__ */ jsx(DialogTitle, { className: cn("truncate text-sm", noTitle && "sr-only", titleClassName), children: title || "Dialog" }),
1084
+ /* @__PURE__ */ jsx(DialogDescription, { className: cn("text-xs", noDescription && "sr-only", descriptionClassName), children: description })
1085
+ ] }),
1086
+ /* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx(
1087
+ IconButton,
1088
+ {
1089
+ "aria-label": "Close",
1090
+ variant: "ghost",
1091
+ size: "sm",
1092
+ className: cn("border border-border bg-background text-foreground hover:bg-accent", closeButtonClassName),
1093
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
1094
+ }
1095
+ ) })
1096
+ ]
1097
+ }
1098
+ ) : /* @__PURE__ */ jsxs(DialogHeader, { className: cn(noHeader && "sr-only", headerClassName), children: [
1099
+ /* @__PURE__ */ jsx(DialogTitle, { className: cn(noTitle && "sr-only", titleClassName), children: title }),
1100
+ /* @__PURE__ */ jsx(DialogDescription, { className: cn(noDescription && "sr-only", descriptionClassName), children: description })
1101
+ ] }),
1102
+ flush ? (
1103
+ // Full-bleed content owns its own layout/scrolling; no outer scroll wrapper.
1104
+ /* @__PURE__ */ jsx("div", { ref: contentRef, className: "flex min-h-0 flex-1 flex-col", children })
1105
+ ) : /* @__PURE__ */ jsx(NajmScroll, { axis: "y", className: "flex-1 px-1 -mx-1", children: /* @__PURE__ */ jsx("div", { ref: contentRef, style: { display: "contents" }, children }) })
1106
+ ] });
1107
+ }
1108
+ function DialogItem({ dialog, index, store }) {
1109
+ if (dialog.render) {
1110
+ return /* @__PURE__ */ jsx(Fragment, { children: dialog.render({
1111
+ dialog,
1112
+ index,
1113
+ zIndex: 9990 + index,
1114
+ close: (result) => store.getState().closeDialog(dialog.id, result),
1115
+ confirm: (data) => store.getState().handlePrimaryClick(dialog.id, data),
1116
+ cancel: (data) => store.getState().handleSecondaryClick(dialog.id, data),
1117
+ onOpenChange: (open) => store.getState().handleOpenChange(dialog.id, open)
1118
+ }) });
1119
+ }
1120
+ const {
1121
+ id,
1122
+ title,
1123
+ description,
1124
+ pageHeader,
1125
+ padding,
1126
+ children,
1127
+ primaryButton,
1128
+ secondaryButton,
1129
+ showButtons,
1130
+ size,
1131
+ width,
1132
+ height,
1133
+ className,
1134
+ headerClassName,
1135
+ titleClassName,
1136
+ descriptionClassName,
1137
+ closeButtonClassName,
1138
+ variant = "default",
1139
+ actionMode = "auto"
1140
+ } = dialog;
1141
+ const { contentOwnsActions, setContentElement } = useContentOwnsActions();
1142
+ const renderDialogButtons = shouldRenderDialogButtons(showButtons, actionMode, contentOwnsActions);
1143
+ const handlePrimary = async (e) => {
1144
+ const hasForm = !!primaryButton?.form;
1145
+ if (!hasForm && e) e.preventDefault();
1146
+ await store.getState().handlePrimaryClick(id);
1147
+ };
1148
+ const handleSecondary = async (e) => {
1149
+ const hasForm = !!secondaryButton?.form;
1150
+ if (!hasForm && e) e.preventDefault();
1151
+ await store.getState().handleSecondaryClick(id);
1152
+ };
1153
+ return /* @__PURE__ */ jsx(Dialog, { open: true, onOpenChange: (open) => store.getState().handleOpenChange(id, open), children: /* @__PURE__ */ jsxs(
1154
+ DialogContent,
1155
+ {
1156
+ "data-dialog-id": id,
1157
+ "data-variant": variant,
1158
+ padding,
1159
+ hideClose: !!pageHeader || variant === "window",
1160
+ className: cn(dialogVariants({ variant, size, width, height }), className),
1161
+ style: { zIndex: 9990 + index },
1162
+ children: [
1163
+ /* @__PURE__ */ jsx(
1164
+ DialogChrome,
1165
+ {
1166
+ pageHeader,
1167
+ title,
1168
+ description,
1169
+ padding,
1170
+ variant,
1171
+ headerClassName,
1172
+ titleClassName,
1173
+ descriptionClassName,
1174
+ closeButtonClassName,
1175
+ contentRef: setContentElement,
1176
+ children
1177
+ }
1178
+ ),
1179
+ /* @__PURE__ */ jsxs(DialogFooter, { className: cn(!renderDialogButtons && "sr-only"), children: [
1180
+ renderDialogButtons && secondaryButton && /* @__PURE__ */ jsx(
1181
+ Button,
1182
+ {
1183
+ type: secondaryButton.form ? "submit" : "button",
1184
+ "data-button-type": "secondary",
1185
+ onClick: secondaryButton.form ? void 0 : handleSecondary,
1186
+ disabled: secondaryButton.disabled || secondaryButton.loading || false,
1187
+ variant: secondaryButton.variant || "outline",
1188
+ leftIcon: secondaryButton.icon,
1189
+ loading: secondaryButton.loading,
1190
+ loadingText: secondaryButton.loadingText,
1191
+ form: secondaryButton.form,
1192
+ className: cn("w-auto", secondaryButton.className),
1193
+ children: secondaryButton.text
1194
+ }
1195
+ ),
1196
+ renderDialogButtons && primaryButton && /* @__PURE__ */ jsx(
1197
+ Button,
1198
+ {
1199
+ type: primaryButton.form ? "submit" : "button",
1200
+ "data-button-type": "primary",
1201
+ onClick: primaryButton.form ? void 0 : handlePrimary,
1202
+ disabled: primaryButton.disabled || primaryButton.loading || false,
1203
+ variant: primaryButton.variant || "default",
1204
+ leftIcon: primaryButton.icon,
1205
+ loading: primaryButton.loading,
1206
+ loadingText: primaryButton.loadingText,
1207
+ form: primaryButton.form,
1208
+ className: cn("w-auto", primaryButton.className),
1209
+ children: primaryButton.text
1210
+ }
1211
+ )
1212
+ ] })
1213
+ ]
1214
+ }
1215
+ ) }, id);
1216
+ }
1217
+ function NMultiDialog({ store: storeProp }) {
1218
+ const defaultStore2 = useDialogStore();
1219
+ const store = storeProp ?? defaultStore2;
1220
+ const dialogs = store((s) => s.dialogs);
1221
+ return /* @__PURE__ */ jsx(Fragment, { children: dialogs.map((dialog, index) => /* @__PURE__ */ jsx(DialogItem, { dialog, index, store }, dialog.id)) });
1222
+ }
1223
+ var buildButtonConfig2 = (partial, defaults) => {
1224
+ return { ...defaults, ...partial };
1225
+ };
1226
+ var isDirectDialogProps = (props) => {
1227
+ return "trigger" in props || "open" in props || "defaultOpen" in props || "onOpenChange" in props || "title" in props || "description" in props || "children" in props || "primaryButton" in props || "secondaryButton" in props || "showButtons" in props || "size" in props || "width" in props || "height" in props || "className" in props || "headerClassName" in props || "titleClassName" in props || "descriptionClassName" in props || "closeButtonClassName" in props || "variant" in props || "pageHeader" in props || "padding" in props;
1228
+ };
1229
+ function NDirectDialog({
1230
+ trigger,
1231
+ open,
1232
+ defaultOpen = false,
1233
+ onOpenChange,
1234
+ title,
1235
+ description,
1236
+ pageHeader,
1237
+ padding,
1238
+ children,
1239
+ primaryButton,
1240
+ secondaryButton,
1241
+ showButtons = true,
1242
+ size,
1243
+ width,
1244
+ height,
1245
+ className,
1246
+ headerClassName,
1247
+ titleClassName,
1248
+ descriptionClassName,
1249
+ closeButtonClassName,
1250
+ variant = "default",
1251
+ actionMode = "auto",
1252
+ closeOnPrimary = true,
1253
+ closeOnSecondary = true
1254
+ }) {
1255
+ const slots = parseDirectDialogSlots(children);
1256
+ const resolvedTitle = title ?? slots.header?.label ?? slots.header?.children;
1257
+ const resolvedDescription = description ?? slots.description?.label ?? slots.description?.children;
1258
+ const resolvedHeaderClassName = cn(slots.header?.className, headerClassName);
1259
+ const resolvedTitleClassName = cn(slots.header?.titleClassName, titleClassName);
1260
+ const resolvedDescriptionClassName = cn(slots.description?.className, descriptionClassName);
1261
+ const [internalOpen, setInternalOpen] = React12__default.useState(defaultOpen);
1262
+ const [primaryLoading, setPrimaryLoading] = React12__default.useState(false);
1263
+ const isControlled = open !== void 0;
1264
+ const currentOpen = isControlled ? open : internalOpen;
1265
+ const setOpen = (nextOpen) => {
1266
+ if (!isControlled) {
1267
+ setInternalOpen(nextOpen);
1268
+ }
1269
+ onOpenChange?.(nextOpen);
1270
+ };
1271
+ const resolvedPrimaryButton = buildButtonConfig2(primaryButton ?? slots.primaryButton, {
1272
+ text: "Confirm",
1273
+ variant: "default",
1274
+ loading: false,
1275
+ disabled: false,
1276
+ loadingText: "Processing..."
1277
+ });
1278
+ const resolvedSecondaryButton = buildButtonConfig2(secondaryButton ?? slots.secondaryButton, {
1279
+ text: "Cancel",
1280
+ variant: "outline",
1281
+ loading: false,
1282
+ disabled: false,
1283
+ loadingText: "Processing..."
1284
+ });
1285
+ const isPrimaryLoading = primaryLoading || !!resolvedPrimaryButton.loading;
1286
+ const { contentOwnsActions, setContentElement } = useContentOwnsActions();
1287
+ const renderDialogButtons = shouldRenderDialogButtons(showButtons, actionMode, contentOwnsActions);
1288
+ const handlePrimary = async (e) => {
1289
+ const hasForm = !!resolvedPrimaryButton.form;
1290
+ if (!hasForm && e) e.preventDefault();
1291
+ if (resolvedPrimaryButton.onClick) {
1292
+ setPrimaryLoading(true);
1293
+ try {
1294
+ await resolvedPrimaryButton.onClick();
1295
+ } finally {
1296
+ setPrimaryLoading(false);
1297
+ }
1298
+ }
1299
+ if (closeOnPrimary) {
1300
+ setOpen(false);
1301
+ }
1302
+ };
1303
+ const handleSecondary = async (e) => {
1304
+ const hasForm = !!resolvedSecondaryButton.form;
1305
+ if (!hasForm && e) e.preventDefault();
1306
+ if (resolvedSecondaryButton.onClick) {
1307
+ await resolvedSecondaryButton.onClick();
1308
+ }
1309
+ if (closeOnSecondary) {
1310
+ setOpen(false);
1311
+ }
1312
+ };
1313
+ return /* @__PURE__ */ jsxs(Dialog, { open: currentOpen, onOpenChange: setOpen, children: [
1314
+ trigger ? /* @__PURE__ */ jsx(DialogTrigger, { asChild: true, children: trigger }) : null,
1315
+ /* @__PURE__ */ jsxs(
1316
+ DialogContent,
1317
+ {
1318
+ "data-variant": variant,
1319
+ padding,
1320
+ hideClose: !!pageHeader || variant === "window",
1321
+ className: cn(dialogVariants({ variant, size, width, height }), className),
1322
+ children: [
1323
+ /* @__PURE__ */ jsx(
1324
+ DialogChrome,
1325
+ {
1326
+ pageHeader,
1327
+ title: resolvedTitle,
1328
+ description: resolvedDescription,
1329
+ padding,
1330
+ variant,
1331
+ headerClassName: resolvedHeaderClassName,
1332
+ titleClassName: resolvedTitleClassName,
1333
+ descriptionClassName: resolvedDescriptionClassName,
1334
+ closeButtonClassName,
1335
+ contentRef: setContentElement,
1336
+ children: slots.body
1337
+ }
1338
+ ),
1339
+ /* @__PURE__ */ jsx(DialogFooter, { className: cn(!renderDialogButtons && "sr-only"), children: renderDialogButtons && /* @__PURE__ */ jsxs(Fragment, { children: [
1340
+ /* @__PURE__ */ jsx(
1341
+ Button,
1342
+ {
1343
+ type: resolvedSecondaryButton.form ? "submit" : "button",
1344
+ "data-button-type": "secondary",
1345
+ onClick: resolvedSecondaryButton.form ? void 0 : handleSecondary,
1346
+ disabled: resolvedSecondaryButton.disabled || resolvedSecondaryButton.loading || false,
1347
+ variant: resolvedSecondaryButton.variant || "outline",
1348
+ leftIcon: resolvedSecondaryButton.icon,
1349
+ loading: resolvedSecondaryButton.loading,
1350
+ loadingText: resolvedSecondaryButton.loadingText,
1351
+ form: resolvedSecondaryButton.form,
1352
+ className: cn("w-auto", resolvedSecondaryButton.className),
1353
+ children: resolvedSecondaryButton.text
1354
+ }
1355
+ ),
1356
+ /* @__PURE__ */ jsx(
1357
+ Button,
1358
+ {
1359
+ type: resolvedPrimaryButton.form ? "submit" : "button",
1360
+ "data-button-type": "primary",
1361
+ onClick: resolvedPrimaryButton.form ? void 0 : handlePrimary,
1362
+ disabled: resolvedPrimaryButton.disabled || isPrimaryLoading || false,
1363
+ variant: resolvedPrimaryButton.variant || "default",
1364
+ leftIcon: resolvedPrimaryButton.icon,
1365
+ loading: isPrimaryLoading,
1366
+ loadingText: resolvedPrimaryButton.loadingText,
1367
+ form: resolvedPrimaryButton.form,
1368
+ className: cn("w-auto", resolvedPrimaryButton.className),
1369
+ children: resolvedPrimaryButton.text
1370
+ }
1371
+ )
1372
+ ] }) })
1373
+ ]
1374
+ }
1375
+ )
1376
+ ] });
1377
+ }
1378
+ function NDialog(props) {
1379
+ if (isDirectDialogProps(props)) {
1380
+ return /* @__PURE__ */ jsx(NDirectDialog, { ...props });
1381
+ }
1382
+ return /* @__PURE__ */ jsx(NMultiDialog, { ...props });
1383
+ }
1384
+ function NConfirmDialog({
1385
+ open,
1386
+ onOpenChange,
1387
+ title = "Are you sure?",
1388
+ description,
1389
+ confirmLabel = "Confirm",
1390
+ cancelLabel = "Cancel",
1391
+ onConfirm,
1392
+ variant = "default",
1393
+ loading = false,
1394
+ children,
1395
+ icon
1396
+ }) {
1397
+ const isDestructive = variant === "destructive";
1398
+ const resolvedIcon = icon ?? (isDestructive ? "alert-triangle" : "circle-help");
1399
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (value) => {
1400
+ if (!value && loading) return;
1401
+ onOpenChange(value);
1402
+ }, children: /* @__PURE__ */ jsxs(DialogContent, { children: [
1403
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3 text-center pt-1", children: [
1404
+ /* @__PURE__ */ jsx(
1405
+ "div",
1406
+ {
1407
+ className: cn(
1408
+ "flex size-16 items-center justify-center rounded-full",
1409
+ isDestructive ? "bg-destructive/10 text-destructive" : "bg-primary/10 text-primary"
1410
+ ),
1411
+ children: /* @__PURE__ */ jsx(NIcon, { icon: resolvedIcon, className: "size-8" })
1412
+ }
1413
+ ),
1414
+ /* @__PURE__ */ jsxs(DialogHeader, { className: "text-center sm:text-center", children: [
1415
+ /* @__PURE__ */ jsx(DialogTitle, { className: isDestructive ? "text-destructive" : "", children: title }),
1416
+ description && /* @__PURE__ */ jsx(DialogDescription, { children: description })
1417
+ ] })
1418
+ ] }),
1419
+ children,
1420
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
1421
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: loading, children: cancelLabel }),
1422
+ /* @__PURE__ */ jsx(Button, { variant: isDestructive ? "destructive" : "default", onClick: onConfirm, disabled: loading, children: loading ? "Processing..." : confirmLabel })
1423
+ ] })
1424
+ ] }) });
1425
+ }
1426
+ function NDeleteDialogContent({
1427
+ title = "Delete",
1428
+ itemName,
1429
+ itemType,
1430
+ icon: Icon = "trash-2",
1431
+ warningText = "Are you sure you want to delete?",
1432
+ className
1433
+ }) {
1434
+ return /* @__PURE__ */ jsx("form", { id: "najm-delete-form", onSubmit: (e) => e.preventDefault(), children: /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center text-center", className), children: [
1435
+ /* @__PURE__ */ jsx("div", { className: "mb-6 flex size-16 items-center justify-center rounded-[20px] bg-destructive/10 text-destructive", children: /* @__PURE__ */ jsx(NIcon, { icon: Icon, className: "size-7" }) }),
1436
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1437
+ /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold leading-none text-card-foreground", children: title }),
1438
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1439
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium leading-5 text-muted-foreground", children: warningText }),
1440
+ /* @__PURE__ */ jsxs("p", { className: "mx-auto max-w-[280px] break-words text-center text-sm font-bold leading-5 text-card-foreground", children: [
1441
+ '"',
1442
+ itemName,
1443
+ '"',
1444
+ itemType ? /* @__PURE__ */ jsxs(Fragment, { children: [
1445
+ " (",
1446
+ itemType,
1447
+ ")"
1448
+ ] }) : null
1449
+ ] })
1450
+ ] })
1451
+ ] })
1452
+ ] }) });
1453
+ }
1454
+ function NDeleteDialog({
1455
+ open = true,
1456
+ title = "Delete",
1457
+ description,
1458
+ itemName,
1459
+ itemType,
1460
+ icon,
1461
+ warningText,
1462
+ confirmText = "Confirm",
1463
+ cancelText = "Cancel",
1464
+ loading,
1465
+ zIndex,
1466
+ className,
1467
+ contentClassName,
1468
+ onOpenChange,
1469
+ onConfirm,
1470
+ onCancel
1471
+ }) {
1472
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(
1473
+ DialogContent,
1474
+ {
1475
+ className: cn(
1476
+ "h-auto max-w-[380px] gap-0 rounded-[18px] border border-border bg-card p-6 pt-8 shadow-2xl",
1477
+ "[&>button]:end-4 [&>button]:top-4 [&>button]:flex [&>button]:size-6 [&>button]:items-center [&>button]:justify-center",
1478
+ "[&>button]:rounded-full [&>button]:bg-muted [&>button]:text-muted-foreground [&>button]:opacity-100 [&>button]:ring-offset-0",
1479
+ "[&>button]:hover:bg-secondary [&>button]:hover:text-foreground",
1480
+ className
1481
+ ),
1482
+ style: { zIndex },
1483
+ children: [
1484
+ /* @__PURE__ */ jsxs(DialogHeader, { className: "sr-only", children: [
1485
+ /* @__PURE__ */ jsx(DialogTitle, { children: title }),
1486
+ /* @__PURE__ */ jsx(DialogDescription, { children: description })
1487
+ ] }),
1488
+ /* @__PURE__ */ jsx(
1489
+ NDeleteDialogContent,
1490
+ {
1491
+ title,
1492
+ itemName,
1493
+ itemType,
1494
+ icon,
1495
+ warningText,
1496
+ className: contentClassName
1497
+ }
1498
+ ),
1499
+ /* @__PURE__ */ jsxs(DialogFooter, { className: "mt-6 grid grid-cols-2 gap-3 sm:grid-cols-2", children: [
1500
+ /* @__PURE__ */ jsx(
1501
+ Button,
1502
+ {
1503
+ type: "button",
1504
+ "data-button-type": "secondary",
1505
+ variant: "outline",
1506
+ onClick: onCancel,
1507
+ disabled: loading,
1508
+ className: "h-10 w-full rounded-[10px] border border-destructive bg-transparent text-sm font-bold text-card-foreground shadow-none hover:bg-transparent hover:text-card-foreground",
1509
+ children: cancelText
1510
+ }
1511
+ ),
1512
+ /* @__PURE__ */ jsx(
1513
+ Button,
1514
+ {
1515
+ type: "button",
1516
+ "data-button-type": "primary",
1517
+ variant: "destructive",
1518
+ onClick: onConfirm,
1519
+ loading,
1520
+ className: "h-10 w-full rounded-[10px] bg-destructive text-sm font-bold text-destructive-foreground shadow-none hover:bg-destructive/90 focus-visible:ring-destructive/40",
1521
+ children: confirmText
1522
+ }
1523
+ )
1524
+ ] })
1525
+ ]
1526
+ }
1527
+ ) });
1528
+ }
1529
+ function useDialog(store) {
1530
+ const resolvedStore = store ?? useDialogStore();
1531
+ const custom = async (options) => {
1532
+ try {
1533
+ return await resolvedStore.getState().pushDialog(options);
1534
+ } catch (error) {
1535
+ console.error("[useDialog] Failed to push dialog:", error);
1536
+ return null;
1537
+ }
1538
+ };
1539
+ const confirmDelete = async (options) => {
1540
+ try {
1541
+ return await resolvedStore.getState().pushDialog({
1542
+ title: options.title || "Delete",
1543
+ description: options.description,
1544
+ size: options.size || "sm",
1545
+ className: options.className,
1546
+ render: ({ dialog, zIndex, confirm, cancel, onOpenChange }) => React12__default.createElement(NDeleteDialog, {
1547
+ itemName: options.itemName,
1548
+ itemType: options.itemType,
1549
+ icon: options.icon,
1550
+ warningText: options.warningText,
1551
+ title: dialog.title,
1552
+ description: dialog.description,
1553
+ confirmText: dialog.primaryButton?.text,
1554
+ cancelText: dialog.secondaryButton?.text,
1555
+ loading: dialog.primaryButton?.loading,
1556
+ className: dialog.className,
1557
+ zIndex,
1558
+ onOpenChange,
1559
+ onConfirm: () => confirm(),
1560
+ onCancel: () => cancel()
1561
+ }),
1562
+ primaryButton: {
1563
+ text: options.confirmText || "Confirm",
1564
+ variant: "destructive",
1565
+ loading: options.loading,
1566
+ onClick: options.onConfirm
1567
+ },
1568
+ secondaryButton: {
1569
+ text: options.cancelText || "Cancel",
1570
+ variant: "outline"
1571
+ },
1572
+ showButtons: true
1573
+ });
1574
+ } catch (error) {
1575
+ console.error("[useDialog] Failed to show confirmDelete:", error);
1576
+ return null;
1577
+ }
1578
+ };
1579
+ return {
1580
+ custom,
1581
+ openDialog: custom,
1582
+ confirmDelete,
1583
+ closeAll: () => resolvedStore.getState().closeAllDialogs(),
1584
+ push: (opts) => resolvedStore.getState().pushDialog(opts),
1585
+ pop: (result) => resolvedStore.getState().popDialog(result)
1586
+ };
1587
+ }
1588
+
1589
+ // src/location/labels.ts
1590
+ var DEFAULT_LOCATION_LABELS = {
1591
+ dialogTitle: "Pick an address",
1592
+ dialogDescription: "Search or select a location on the map.",
1593
+ openMap: "Select location on map",
1594
+ close: "Close",
1595
+ cancel: "Cancel",
1596
+ confirm: "Confirm address",
1597
+ clearPin: "Clear pin",
1598
+ searchPlaceholder: "Search by street, city, or postal code",
1599
+ search: "Search",
1600
+ searching: "Searching\u2026",
1601
+ searchEmpty: "No locations found.",
1602
+ searchError: "Location search is unavailable.",
1603
+ findingAddress: "Finding the address...",
1604
+ selected: "Delivery location selected",
1605
+ notSelected: "Delivery location not selected",
1606
+ changedAfterPin: "Address changed after the pin was selected",
1607
+ loading: "Loading map\u2026",
1608
+ unavailable: "Map selection is unavailable",
1609
+ retry: "Retry",
1610
+ currentLocation: "Use current location",
1611
+ zoomIn: "Zoom in",
1612
+ zoomOut: "Zoom out",
1613
+ mapInstructions: "Map. Use arrow keys to move the selected pin. Hold Shift for a larger step.",
1614
+ readyAnnouncement: "Map is ready.",
1615
+ selectedAnnouncement: "Location selected.",
1616
+ addressUpdatedAnnouncement: "Address updated for the selected location.",
1617
+ clearedAnnouncement: "Location pin cleared.",
1618
+ geolocationUnsupported: "Current location is not supported by this browser.",
1619
+ geolocationDenied: "Location permission was denied.",
1620
+ geolocationTimeout: "Current location timed out.",
1621
+ geolocationUnavailable: "Current location is unavailable.",
1622
+ providerError: "The map could not be loaded.",
1623
+ resultsAnnouncement: (count) => `${count} location result${count === 1 ? "" : "s"} available.`
1624
+ };
1625
+ var DEFAULT_CENTER = { latitude: 33.5731, longitude: -7.5898 };
1626
+ var NLocationContext = React12__default.createContext(null);
1627
+ function NLocationProvider({
1628
+ children,
1629
+ adapter = null,
1630
+ geocoder = null,
1631
+ defaultCenter = DEFAULT_CENTER,
1632
+ defaultZoom = 12,
1633
+ labels,
1634
+ searchMode = "submit",
1635
+ unavailableReason
1636
+ }) {
1637
+ const value = React12__default.useMemo(() => ({
1638
+ adapter,
1639
+ geocoder,
1640
+ defaultCenter,
1641
+ defaultZoom,
1642
+ labels: { ...DEFAULT_LOCATION_LABELS, ...labels },
1643
+ searchMode,
1644
+ unavailableReason
1645
+ }), [adapter, defaultCenter, defaultZoom, geocoder, labels, searchMode, unavailableReason]);
1646
+ return /* @__PURE__ */ jsx(NLocationContext.Provider, { value, children });
1647
+ }
1648
+ function useNLocationProvider() {
1649
+ const value = React12__default.useContext(NLocationContext);
1650
+ if (!value) {
1651
+ return {
1652
+ adapter: null,
1653
+ geocoder: null,
1654
+ defaultCenter: DEFAULT_CENTER,
1655
+ defaultZoom: 12,
1656
+ labels: DEFAULT_LOCATION_LABELS,
1657
+ searchMode: "submit",
1658
+ unavailableReason: DEFAULT_LOCATION_LABELS.unavailable
1659
+ };
1660
+ }
1661
+ return value;
1662
+ }
1663
+ function useLocationDraft(committed, open) {
1664
+ const [draft, setDraft] = React12__default.useState(() => normalizeLocationValue(committed));
1665
+ React12__default.useEffect(() => {
1666
+ if (open) setDraft(normalizeLocationValue(committed));
1667
+ }, [committed, open]);
1668
+ const discard = React12__default.useCallback(() => setDraft(normalizeLocationValue(committed)), [committed]);
1669
+ return { draft, setDraft, discard };
1670
+ }
1671
+ var SEARCH_DELAY_MS = 350;
1672
+ function geolocationMessage(error, labels) {
1673
+ if (error.code === error.PERMISSION_DENIED) return labels.geolocationDenied;
1674
+ if (error.code === error.TIMEOUT) return labels.geolocationTimeout;
1675
+ return labels.geolocationUnavailable;
1676
+ }
1677
+ function NLocationDialog({ open, value, onOpenChange, onConfirm, labels: labelOverrides, classNames }) {
1678
+ const provider = useNLocationProvider();
1679
+ const labels = React12__default.useMemo(() => ({ ...provider.labels, ...labelOverrides }), [labelOverrides, provider.labels]);
1680
+ const { draft, setDraft, discard } = useLocationDraft(value, open);
1681
+ const [announcement, setAnnouncement] = React12__default.useState("");
1682
+ const [providerLoading, setProviderLoading] = React12__default.useState(false);
1683
+ const [providerError, setProviderError] = React12__default.useState(null);
1684
+ const [providerAttempt, setProviderAttempt] = React12__default.useState(0);
1685
+ const [mapControls, setMapControls] = React12__default.useState(null);
1686
+ const [query, setQuery] = React12__default.useState("");
1687
+ const [results, setResults] = React12__default.useState([]);
1688
+ const [searchPerformed, setSearchPerformed] = React12__default.useState(false);
1689
+ const [searchLoading, setSearchLoading] = React12__default.useState(false);
1690
+ const [searchError, setSearchError] = React12__default.useState(false);
1691
+ const [reverseLoading, setReverseLoading] = React12__default.useState(false);
1692
+ const searchAbort = React12__default.useRef(null);
1693
+ const reverseAbort = React12__default.useRef(null);
1694
+ const reverseRequest = React12__default.useRef(0);
1695
+ const geoRequest = React12__default.useRef(0);
1696
+ const searchInputRef = React12__default.useRef(null);
1697
+ const mapInstructionRef = React12__default.useRef(null);
1698
+ const coordinates = locationCoordinates(draft);
1699
+ const coordinatesRef = React12__default.useRef(coordinates);
1700
+ coordinatesRef.current = coordinates;
1701
+ const AdapterMap = provider.adapter?.Map;
1702
+ React12__default.useEffect(() => {
1703
+ if (!open) {
1704
+ searchAbort.current?.abort();
1705
+ reverseAbort.current?.abort();
1706
+ reverseRequest.current += 1;
1707
+ provider.geocoder?.resetSession?.();
1708
+ geoRequest.current += 1;
1709
+ setProviderLoading(false);
1710
+ setSearchLoading(false);
1711
+ setReverseLoading(false);
1712
+ return;
1713
+ }
1714
+ setAnnouncement("");
1715
+ setProviderError(null);
1716
+ setResults([]);
1717
+ setQuery("");
1718
+ setSearchError(false);
1719
+ setSearchPerformed(false);
1720
+ setSearchLoading(false);
1721
+ setReverseLoading(false);
1722
+ }, [open, provider.geocoder]);
1723
+ const close = React12__default.useCallback(() => {
1724
+ discard();
1725
+ onOpenChange(false);
1726
+ }, [discard, onOpenChange]);
1727
+ const updateCoordinates = React12__default.useCallback((next) => {
1728
+ setDraft((current) => ({ ...current, ...next }));
1729
+ setAnnouncement(labels.selectedAnnouncement);
1730
+ if (!provider.geocoder?.reverse) return;
1731
+ reverseAbort.current?.abort();
1732
+ const controller = new AbortController();
1733
+ const request = ++reverseRequest.current;
1734
+ reverseAbort.current = controller;
1735
+ setReverseLoading(true);
1736
+ void provider.geocoder.reverse(next, controller.signal).then((candidate) => {
1737
+ if (controller.signal.aborted || request !== reverseRequest.current || !candidate?.label) return;
1738
+ setDraft((current) => ({ ...current, address: candidate.label }));
1739
+ setAnnouncement(labels.addressUpdatedAnnouncement);
1740
+ }).catch(() => {
1741
+ }).finally(() => {
1742
+ if (request === reverseRequest.current) setReverseLoading(false);
1743
+ });
1744
+ }, [labels.addressUpdatedAnnouncement, labels.selectedAnnouncement, provider.geocoder, setDraft]);
1745
+ const performSearch = React12__default.useCallback(async (rawQuery) => {
1746
+ const nextQuery = rawQuery.trim();
1747
+ if (!provider.geocoder || nextQuery.length < 2) {
1748
+ setResults([]);
1749
+ return;
1750
+ }
1751
+ searchAbort.current?.abort();
1752
+ const controller = new AbortController();
1753
+ searchAbort.current = controller;
1754
+ setSearchLoading(true);
1755
+ setSearchError(false);
1756
+ setSearchPerformed(false);
1757
+ try {
1758
+ const found = await provider.geocoder.search(nextQuery, {
1759
+ signal: controller.signal,
1760
+ center: coordinatesRef.current ?? provider.defaultCenter,
1761
+ language: document.documentElement.lang || void 0
1762
+ });
1763
+ if (controller.signal.aborted) return;
1764
+ setResults(found);
1765
+ setSearchPerformed(true);
1766
+ setAnnouncement(labels.resultsAnnouncement(found.length));
1767
+ } catch {
1768
+ if (controller.signal.aborted) return;
1769
+ setResults([]);
1770
+ setSearchError(true);
1771
+ setSearchPerformed(true);
1772
+ setAnnouncement(labels.searchError);
1773
+ } finally {
1774
+ if (!controller.signal.aborted) setSearchLoading(false);
1775
+ }
1776
+ }, [labels, provider.defaultCenter, provider.geocoder]);
1777
+ React12__default.useEffect(() => {
1778
+ if (!open || provider.searchMode !== "autocomplete" || !provider.geocoder || query.trim().length < 2) return;
1779
+ const timeout = setTimeout(() => void performSearch(query), SEARCH_DELAY_MS);
1780
+ return () => clearTimeout(timeout);
1781
+ }, [open, performSearch, provider.geocoder, provider.searchMode, query]);
1782
+ const selectCandidate = async (candidate) => {
1783
+ if (!provider.geocoder) return;
1784
+ reverseAbort.current?.abort();
1785
+ reverseRequest.current += 1;
1786
+ setReverseLoading(false);
1787
+ searchAbort.current?.abort();
1788
+ const controller = new AbortController();
1789
+ searchAbort.current = controller;
1790
+ setSearchLoading(true);
1791
+ try {
1792
+ const resolved = candidate.coordinates ? candidate : provider.geocoder.resolve ? await provider.geocoder.resolve(candidate, controller.signal) : candidate;
1793
+ if (controller.signal.aborted || !resolved.coordinates) return;
1794
+ setDraft((current) => ({
1795
+ ...current,
1796
+ address: resolved.label || current.address,
1797
+ latitude: resolved.coordinates.latitude,
1798
+ longitude: resolved.coordinates.longitude
1799
+ }));
1800
+ mapControls?.recenter(resolved.coordinates);
1801
+ setQuery("");
1802
+ setResults([]);
1803
+ setSearchPerformed(false);
1804
+ setAnnouncement(labels.selectedAnnouncement);
1805
+ } catch {
1806
+ if (!controller.signal.aborted) setSearchError(true);
1807
+ } finally {
1808
+ if (!controller.signal.aborted) setSearchLoading(false);
1809
+ }
1810
+ };
1811
+ const useCurrentLocation = () => {
1812
+ if (!navigator.geolocation) {
1813
+ setAnnouncement(labels.geolocationUnsupported);
1814
+ return;
1815
+ }
1816
+ const request = ++geoRequest.current;
1817
+ setProviderLoading(true);
1818
+ navigator.geolocation.getCurrentPosition(
1819
+ (position) => {
1820
+ if (request !== geoRequest.current || !open) return;
1821
+ const next = { latitude: position.coords.latitude, longitude: position.coords.longitude };
1822
+ updateCoordinates(next);
1823
+ mapControls?.recenter(next);
1824
+ setProviderLoading(false);
1825
+ },
1826
+ (error) => {
1827
+ if (request !== geoRequest.current || !open) return;
1828
+ setProviderLoading(false);
1829
+ setAnnouncement(geolocationMessage(error, labels));
1830
+ },
1831
+ { enableHighAccuracy: false, timeout: 1e4, maximumAge: 6e4 }
1832
+ );
1833
+ };
1834
+ const handleMapKeyDown = (event) => {
1835
+ if (!coordinates || !["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.key)) return;
1836
+ event.preventDefault();
1837
+ const step = event.shiftKey ? 1e-3 : 1e-4;
1838
+ const latitudeDelta = event.key === "ArrowUp" ? step : event.key === "ArrowDown" ? -step : 0;
1839
+ const longitudeDelta = event.key === "ArrowRight" ? step : event.key === "ArrowLeft" ? -step : 0;
1840
+ updateCoordinates(moveCoordinates(coordinates, latitudeDelta, longitudeDelta));
1841
+ };
1842
+ const clearPin = () => {
1843
+ reverseAbort.current?.abort();
1844
+ reverseRequest.current += 1;
1845
+ setReverseLoading(false);
1846
+ setDraft((current) => ({ ...current, latitude: null, longitude: null }));
1847
+ setAnnouncement(labels.clearedAnnouncement);
1848
+ };
1849
+ const confirm = () => {
1850
+ const normalized = normalizeLocationValue(draft);
1851
+ if ((draft.latitude !== null || draft.longitude !== null) && !isCompleteCoordinatePair(draft)) return;
1852
+ onConfirm(normalized);
1853
+ onOpenChange(false);
1854
+ };
1855
+ const pending = providerLoading || searchLoading || reverseLoading;
1856
+ const invalidPair = (draft.latitude !== null || draft.longitude !== null) && !isCompleteCoordinatePair(draft);
1857
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (next) => next ? onOpenChange(true) : close(), children: /* @__PURE__ */ jsxs(
1858
+ DialogContent,
1859
+ {
1860
+ padding: "none",
1861
+ hideClose: true,
1862
+ className: cn(
1863
+ "nlocation-dialog !flex flex-col gap-0 inset-0 top-0 left-0 h-dvh max-h-dvh max-w-none translate-x-0 translate-y-0 rounded-none sm:inset-auto sm:top-1/2 sm:left-1/2 sm:h-[min(46rem,calc(100dvh-2rem))] sm:max-h-[calc(100dvh-2rem)] sm:max-w-4xl sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-lg",
1864
+ classNames?.dialog
1865
+ ),
1866
+ onOpenAutoFocus: (event) => {
1867
+ event.preventDefault();
1868
+ queueMicrotask(() => (provider.geocoder ? searchInputRef.current : mapInstructionRef.current)?.focus());
1869
+ },
1870
+ children: [
1871
+ /* @__PURE__ */ jsxs(DialogHeader, { className: "shrink-0 flex-row items-start justify-between gap-4 border-b border-border px-4 py-4 text-start sm:px-6", children: [
1872
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-1", children: [
1873
+ /* @__PURE__ */ jsx(DialogTitle, { children: labels.dialogTitle }),
1874
+ /* @__PURE__ */ jsx(DialogDescription, { children: labels.dialogDescription })
1875
+ ] }),
1876
+ /* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { "aria-label": labels.close, variant: "ghost", onClick: discard, children: /* @__PURE__ */ jsx(X, {}) }) })
1877
+ ] }),
1878
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto px-4 py-3 sm:px-6", children: [
1879
+ provider.geocoder && /* @__PURE__ */ jsxs("form", { className: cn("relative shrink-0", classNames?.search), onSubmit: (event) => {
1880
+ event.preventDefault();
1881
+ void performSearch(query);
1882
+ }, children: [
1883
+ /* @__PURE__ */ jsx(
1884
+ Input,
1885
+ {
1886
+ ref: searchInputRef,
1887
+ value: query,
1888
+ onChange: (event) => {
1889
+ searchAbort.current?.abort();
1890
+ searchAbort.current = null;
1891
+ setSearchLoading(false);
1892
+ setQuery(event.target.value);
1893
+ setResults([]);
1894
+ setSearchError(false);
1895
+ setSearchPerformed(false);
1896
+ },
1897
+ placeholder: labels.searchPlaceholder,
1898
+ className: "pe-24",
1899
+ autoComplete: "off"
1900
+ }
1901
+ ),
1902
+ /* @__PURE__ */ jsx(Button, { type: "submit", size: "sm", variant: "ghost", className: "absolute end-1 top-1 h-7", disabled: searchLoading || query.trim().length < 2, leftIcon: Search, children: searchLoading ? labels.searching : labels.search }),
1903
+ (results.length > 0 || searchError || searchPerformed) && /* @__PURE__ */ jsx("div", { className: "absolute inset-x-0 top-full z-[60] mt-1 overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md", children: searchError ? /* @__PURE__ */ jsx("p", { className: "p-3 text-sm text-destructive", children: labels.searchError }) : results.length === 0 ? /* @__PURE__ */ jsx("p", { className: "p-3 text-sm text-muted-foreground", children: labels.searchEmpty }) : /* @__PURE__ */ jsx("div", { role: "listbox", "aria-label": labels.searchPlaceholder, className: "max-h-52 overflow-y-auto p-1", children: results.map((candidate) => /* @__PURE__ */ jsxs("button", { type: "button", role: "option", "aria-selected": "false", className: "flex w-full flex-col rounded-sm px-3 py-2 text-start text-sm hover:bg-accent focus-visible:bg-accent focus-visible:outline-none", onClick: () => void selectCandidate(candidate), children: [
1904
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: candidate.label }),
1905
+ candidate.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: candidate.description })
1906
+ ] }, candidate.id)) }) })
1907
+ ] }),
1908
+ /* @__PURE__ */ jsxs(
1909
+ "div",
1910
+ {
1911
+ ref: mapInstructionRef,
1912
+ tabIndex: 0,
1913
+ role: "application",
1914
+ "aria-label": labels.mapInstructions,
1915
+ onKeyDown: handleMapKeyDown,
1916
+ className: cn("relative min-h-72 flex-1 overflow-hidden rounded-lg border border-border bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", classNames?.map),
1917
+ children: [
1918
+ AdapterMap ? /* @__PURE__ */ jsx(React12__default.Suspense, { fallback: /* @__PURE__ */ jsx(NLoadingState, { surface: "panel", label: labels.loading, className: "h-full min-h-72" }), children: /* @__PURE__ */ jsx(
1919
+ AdapterMap,
1920
+ {
1921
+ value: coordinates,
1922
+ initialCenter: provider.defaultCenter,
1923
+ initialZoom: provider.defaultZoom,
1924
+ onChange: updateCoordinates,
1925
+ onReady: () => {
1926
+ setProviderLoading(false);
1927
+ setProviderError(null);
1928
+ setAnnouncement(labels.readyAnnouncement);
1929
+ },
1930
+ onLoadingChange: setProviderLoading,
1931
+ onError: () => {
1932
+ setProviderLoading(false);
1933
+ setProviderError(labels.providerError);
1934
+ setAnnouncement(labels.providerError);
1935
+ },
1936
+ onControlsReady: setMapControls,
1937
+ reducedMotion: typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches,
1938
+ className: "h-full min-h-72 w-full"
1939
+ },
1940
+ providerAttempt
1941
+ ) }) : /* @__PURE__ */ jsx("div", { className: "flex h-full min-h-72 items-center justify-center p-6 text-center text-sm text-muted-foreground", children: provider.unavailableReason || labels.unavailable }),
1942
+ providerError && /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 z-20 flex flex-col items-center justify-center gap-3 bg-background/90 p-6 text-center", children: [
1943
+ /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm text-destructive", children: providerError }),
1944
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => {
1945
+ setProviderError(null);
1946
+ setProviderAttempt((n) => n + 1);
1947
+ }, children: labels.retry })
1948
+ ] }),
1949
+ /* @__PURE__ */ jsxs("div", { className: "absolute bottom-3 end-3 z-10 flex flex-col gap-1 rounded-md border border-border bg-card/95 p-1 shadow-sm", children: [
1950
+ /* @__PURE__ */ jsx(IconButton, { "aria-label": labels.zoomIn, onClick: () => mapControls?.zoomIn(), disabled: !mapControls, children: /* @__PURE__ */ jsx(Plus, {}) }),
1951
+ /* @__PURE__ */ jsx(IconButton, { "aria-label": labels.zoomOut, onClick: () => mapControls?.zoomOut(), disabled: !mapControls, children: /* @__PURE__ */ jsx(Minus, {}) }),
1952
+ /* @__PURE__ */ jsx(IconButton, { "aria-label": labels.currentLocation, onClick: useCurrentLocation, disabled: !AdapterMap || pending, children: /* @__PURE__ */ jsx(Crosshair, {}) })
1953
+ ] })
1954
+ ]
1955
+ }
1956
+ ),
1957
+ /* @__PURE__ */ jsxs("label", { className: "grid shrink-0 gap-1 text-sm font-medium", children: [
1958
+ labels.dialogTitle,
1959
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1960
+ /* @__PURE__ */ jsx(
1961
+ Input,
1962
+ {
1963
+ value: draft.address,
1964
+ "aria-busy": reverseLoading,
1965
+ className: reverseLoading ? "pe-10" : void 0,
1966
+ onChange: (event) => {
1967
+ reverseAbort.current?.abort();
1968
+ reverseRequest.current += 1;
1969
+ setReverseLoading(false);
1970
+ setDraft((current) => ({ ...current, address: event.target.value }));
1971
+ }
1972
+ }
1973
+ ),
1974
+ reverseLoading && /* @__PURE__ */ jsx("span", { role: "status", "aria-label": labels.findingAddress, className: "pointer-events-none absolute inset-y-0 end-3 flex items-center text-muted-foreground", children: /* @__PURE__ */ jsx(LoaderCircle, { "aria-hidden": "true", className: "h-4 w-4 animate-spin motion-reduce:animate-none" }) })
1975
+ ] })
1976
+ ] })
1977
+ ] }),
1978
+ /* @__PURE__ */ jsx("div", { className: "sr-only", "aria-live": "polite", "aria-atomic": "true", children: announcement }),
1979
+ /* @__PURE__ */ jsxs(DialogFooter, { className: cn("shrink-0 border-t border-border bg-card px-4 py-3 pb-[max(.75rem,env(safe-area-inset-bottom))] sm:px-6", classNames?.footer), children: [
1980
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", className: "sm:me-auto", onClick: clearPin, disabled: !coordinates || pending, leftIcon: Trash2, children: labels.clearPin }),
1981
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: close, children: labels.cancel }),
1982
+ /* @__PURE__ */ jsx(Button, { type: "button", onClick: confirm, disabled: pending || invalidPair, leftIcon: Check, children: labels.confirm })
1983
+ ] })
1984
+ ]
1985
+ }
1986
+ ) });
1987
+ }
1988
+ var NLocationInput = React12__default.forwardRef(function NLocationInput2({
1989
+ value,
1990
+ onChange,
1991
+ labels: labelOverrides,
1992
+ classNames,
1993
+ status = "default",
1994
+ bordered,
1995
+ className,
1996
+ disabled = false,
1997
+ readOnly = false,
1998
+ ...inputProps
1999
+ }, ref) {
2000
+ const provider = useNLocationProvider();
2001
+ const labels = React12__default.useMemo(() => ({ ...provider.labels, ...labelOverrides }), [labelOverrides, provider.labels]);
2002
+ const normalized = normalizeLocationValue(value);
2003
+ const hasPin = isCompleteCoordinatePair(normalized);
2004
+ const [open, setOpen] = React12__default.useState(false);
2005
+ const [pinnedAddress, setPinnedAddress] = React12__default.useState(() => hasPin ? normalized.address : "");
2006
+ const coordinateKey = hasPin ? `${normalized.latitude}:${normalized.longitude}` : "";
2007
+ const pinnedCoordinateKey = React12__default.useRef(coordinateKey);
2008
+ React12__default.useEffect(() => {
2009
+ if (coordinateKey === pinnedCoordinateKey.current) return;
2010
+ pinnedCoordinateKey.current = coordinateKey;
2011
+ setPinnedAddress(hasPin ? normalized.address : "");
2012
+ }, [coordinateKey, hasPin, normalized.address]);
2013
+ const changedAfterPin = hasPin && pinnedAddress !== "" && pinnedAddress !== normalized.address;
2014
+ const mapAvailable = Boolean(provider.adapter);
2015
+ const stateLabel = !mapAvailable ? provider.unavailableReason || labels.unavailable : changedAfterPin ? labels.changedAfterPin : hasPin ? labels.selected : labels.notSelected;
2016
+ const StateIcon = !mapAvailable || changedAfterPin ? CircleAlert : hasPin ? MapPinCheck : MapPin;
2017
+ return /* @__PURE__ */ jsxs("div", { className: cn("grid gap-1.5", classNames?.root), children: [
2018
+ /* @__PURE__ */ jsxs(BaseInput, { status, bordered, disabled, className: cn("gap-2", classNames?.input, className), children: [
2019
+ /* @__PURE__ */ jsx(
2020
+ Input,
2021
+ {
2022
+ ...inputProps,
2023
+ ref,
2024
+ value: normalized.address,
2025
+ onChange: (event) => onChange({ ...normalized, address: event.target.value }),
2026
+ disabled,
2027
+ readOnly,
2028
+ className: "h-auto flex-1 border-0 bg-transparent p-0 shadow-none focus-visible:ring-0"
2029
+ }
2030
+ ),
2031
+ /* @__PURE__ */ jsx(
2032
+ IconButton,
2033
+ {
2034
+ "aria-label": labels.openMap,
2035
+ variant: "ghost",
2036
+ size: "md",
2037
+ disabled: disabled || readOnly || !mapAvailable,
2038
+ onClick: () => setOpen(true),
2039
+ children: /* @__PURE__ */ jsx(MapPin, {})
2040
+ }
2041
+ )
2042
+ ] }),
2043
+ /* @__PURE__ */ jsxs("p", { className: cn("flex items-center gap-1.5 text-xs text-muted-foreground", changedAfterPin && "text-warning", classNames?.status), children: [
2044
+ /* @__PURE__ */ jsx(StateIcon, { "aria-hidden": "true", className: "h-3.5 w-3.5 shrink-0" }),
2045
+ stateLabel
2046
+ ] }),
2047
+ /* @__PURE__ */ jsx(
2048
+ NLocationDialog,
2049
+ {
2050
+ open,
2051
+ value: normalized,
2052
+ onOpenChange: setOpen,
2053
+ onConfirm: (next) => {
2054
+ setPinnedAddress(next.latitude === null ? "" : next.address);
2055
+ onChange(next);
2056
+ },
2057
+ labels: labelOverrides,
2058
+ classNames
2059
+ }
2060
+ )
2061
+ ] });
2062
+ });
2063
+ function Label({ className, ...props }) {
2064
+ return /* @__PURE__ */ jsx(
2065
+ LabelPrimitive.Root,
2066
+ {
2067
+ "data-slot": "label",
2068
+ className: cn("flex items-center gap-2 text-sm leading-none font-medium text-foreground select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", className),
2069
+ ...props
2070
+ }
2071
+ );
2072
+ }
2073
+ var Form = FormProvider;
2074
+ var FormFieldContext = React12.createContext({});
2075
+ var FormField = ({ ...props }) => /* @__PURE__ */ jsx(FormFieldContext.Provider, { value: { name: props.name }, children: /* @__PURE__ */ jsx(Controller, { ...props }) });
2076
+ var useFormField = () => {
2077
+ const fieldContext = React12.useContext(FormFieldContext);
2078
+ const itemContext = React12.useContext(FormItemContext);
2079
+ const { getFieldState, formState } = useFormContext();
2080
+ const fieldState = getFieldState(fieldContext.name, formState);
2081
+ if (!fieldContext) throw new Error("useFormField should be used within <FormField>");
2082
+ const { id } = itemContext;
2083
+ return { id, name: fieldContext.name, formItemId: `${id}-form-item`, formDescriptionId: `${id}-form-item-description`, formMessageId: `${id}-form-item-message`, ...fieldState };
2084
+ };
2085
+ var FormItemContext = React12.createContext({});
2086
+ function FormItem({ className, ...props }) {
2087
+ const id = React12.useId();
2088
+ return /* @__PURE__ */ jsx(FormItemContext.Provider, { value: { id }, children: /* @__PURE__ */ jsx("div", { "data-slot": "form-item", className: cn("grid gap-2", className), ...props }) });
2089
+ }
2090
+ function FormLabel({ className, ...props }) {
2091
+ const { error, formItemId } = useFormField();
2092
+ return /* @__PURE__ */ jsx(Label, { "data-slot": "form-label", "data-error": !!error, className: cn("data-[error=true]:text-destructive", className), htmlFor: formItemId, ...props });
2093
+ }
2094
+ function FormControl({ ...props }) {
2095
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
2096
+ return /* @__PURE__ */ jsx(Slot, { "data-slot": "form-control", id: formItemId, "aria-describedby": !error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`, "aria-invalid": !!error, ...props });
2097
+ }
2098
+ function FormDescription({ className, ...props }) {
2099
+ const { formDescriptionId } = useFormField();
2100
+ return /* @__PURE__ */ jsx("p", { "data-slot": "form-description", id: formDescriptionId, className: cn("text-muted-foreground text-[0.8rem]", className), ...props });
2101
+ }
2102
+ function FormMessage({ className, children, ...props }) {
2103
+ const { error, formMessageId } = useFormField();
2104
+ const findMessage = (value) => {
2105
+ if (!value || typeof value !== "object") return void 0;
2106
+ if ("message" in value && typeof value.message === "string") return value.message;
2107
+ for (const child of Object.values(value)) {
2108
+ const nested = findMessage(child);
2109
+ if (nested) return nested;
2110
+ }
2111
+ };
2112
+ const body = error ? findMessage(error) : children;
2113
+ if (!body) return null;
2114
+ return /* @__PURE__ */ jsx("p", { "data-slot": "form-message", id: formMessageId, className: cn("text-destructive text-[0.8rem] font-medium", className), ...props, children: body });
2115
+ }
2116
+ var VARIANT_PRESETS = {
2117
+ default: {
2118
+ item: "flex flex-col w-full",
2119
+ label: "text-foreground flex items-center gap-2",
2120
+ input: "",
2121
+ description: "",
2122
+ error: ""
2123
+ },
2124
+ studio: {
2125
+ item: "space-y-1.5 w-full",
2126
+ label: "text-[11px] uppercase tracking-[0.16em] text-muted-foreground flex items-center gap-2",
2127
+ input: "h-10 bg-card",
2128
+ description: "text-xs text-muted-foreground",
2129
+ error: "text-xs text-destructive"
2130
+ },
2131
+ compact: {
2132
+ item: "flex flex-col w-full gap-1",
2133
+ label: "text-xs text-foreground flex items-center gap-2",
2134
+ input: "h-8 text-sm",
2135
+ description: "text-xs",
2136
+ error: "text-xs"
2137
+ }
2138
+ };
2139
+ var VariantContext = createContext({ variant: "default" });
2140
+ var VariantProvider = ({ variant = "default", bordered, children }) => /* @__PURE__ */ jsx(VariantContext.Provider, { value: { variant, bordered }, children });
2141
+ var useVariant = () => useContext(VariantContext).variant;
2142
+ var useBordered = () => useContext(VariantContext).bordered;
2143
+ var useVariantPreset = () => VARIANT_PRESETS[useContext(VariantContext).variant];
2144
+ var PrefixContext = createContext(void 0);
2145
+ var PrefixProvider = ({ prefix, children }) => /* @__PURE__ */ jsx(PrefixContext.Provider, { value: prefix, children });
2146
+ var usePrefix = () => useContext(PrefixContext);
2147
+ var backgrounds = {
2148
+ card: "!bg-card",
2149
+ background: "!bg-background",
2150
+ secondary: "!bg-secondary",
2151
+ muted: "!bg-muted",
2152
+ transparent: "!bg-transparent"
2153
+ };
2154
+ function nestedMessage(error) {
2155
+ if (!error || typeof error !== "object") return void 0;
2156
+ if ("message" in error && typeof error.message === "string") return error.message;
2157
+ for (const child of Object.values(error)) {
2158
+ const message = nestedMessage(child);
2159
+ if (message) return message;
2160
+ }
2161
+ }
2162
+ function FormLocationInput({ name, formLabel, formDescription, required = false, disabled = false, readOnly = false, hidden = false, background, classNames, onChange: consumerOnChange, ...props }) {
2163
+ const { control } = useFormContext();
2164
+ const prefix = usePrefix();
2165
+ const preset = useVariantPreset();
2166
+ const bordered = useBordered();
2167
+ const fieldName = prefix ? `${prefix}.${name}` : name;
2168
+ return /* @__PURE__ */ jsx(FormField, { control, name: fieldName, render: ({ field, fieldState }) => {
2169
+ const message = nestedMessage(fieldState.error);
2170
+ const handleChange = (next) => {
2171
+ field.onChange(next);
2172
+ consumerOnChange?.(next);
2173
+ };
2174
+ return /* @__PURE__ */ jsxs(FormItem, { className: cn(preset.item, classNames?.item, hidden && "hidden"), children: [
2175
+ formLabel && /* @__PURE__ */ jsxs(FormLabel, { className: cn(preset.label, classNames?.label), children: [
2176
+ formLabel,
2177
+ required && !disabled && !readOnly && /* @__PURE__ */ jsx("span", { className: "ms-1 text-destructive", children: "*" })
2178
+ ] }),
2179
+ /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(
2180
+ NLocationInput,
2181
+ {
2182
+ ...props,
2183
+ name: field.name,
2184
+ ref: field.ref,
2185
+ value: field.value ?? DEFAULT_LOCATION_VALUE,
2186
+ onChange: handleChange,
2187
+ onBlur: field.onBlur,
2188
+ disabled,
2189
+ readOnly,
2190
+ bordered,
2191
+ status: fieldState.error ? "error" : "default",
2192
+ className: cn(background && backgrounds[background], classNames?.input),
2193
+ classNames
2194
+ }
2195
+ ) }),
2196
+ !fieldState.error && !disabled && !readOnly && formDescription && /* @__PURE__ */ jsx(FormDescription, { className: cn(preset.description, classNames?.description), children: formDescription }),
2197
+ message ? /* @__PURE__ */ jsx(FormMessage, { className: cn(preset.error, classNames?.error), children: message }) : null
2198
+ ] });
2199
+ } });
2200
+ }
2201
+
2202
+ export { BaseInput, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormLocationInput, FormMessage, IconButton, Input, Label, NConfirmDialog, NDeleteDialog, NDeleteDialogContent, NDialog, NDialogDescription, NDialogHeader, NDialogPrimaryButton, NDialogSecondaryButton, NLocationDialog, NLocationInput, NLocationProvider, NMultiDialog, NPageHeader, NPageHeaderActions, NPageHeaderCompactActions, NPageHeaderFilters, NPageHeaderTop, NPortalScopeProvider, NSheet, NSidebarProvider, PrefixProvider, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, VariantProvider, createDialogStore, dialogVariants, focusRingClasses, focusRingWithinClasses, isCompleteCoordinatePair, normalizeLocationValue, useBordered, useDialog, useDialogStore, useFormField, useNLocationProvider, useNPortalScope, useNSidebar, usePrefix, useVariant, useVariantPreset };