@avenue-ticketing/ui 0.2.0 → 0.4.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.
@@ -0,0 +1,55 @@
1
+ import * as React from 'react';
2
+
3
+ /** Matches `button.tsx` primary / secondary — no separate outline variants. */
4
+ declare const variantClass: {
5
+ readonly primary: "border border-transparent bg-primary text-background";
6
+ readonly secondary: "border border-primary/10 bg-transparent text-primary";
7
+ };
8
+ /** Same keys as `button.tsx` `roundedClass`. */
9
+ declare const roundedClass: {
10
+ readonly full: "rounded-full";
11
+ readonly lg: "rounded-lg";
12
+ readonly md: "rounded-md";
13
+ };
14
+ declare const sizeClass: {
15
+ readonly md: "min-h-6 min-w-6 px-2 text-xs";
16
+ readonly lg: "min-h-7 min-w-7 px-2.5 text-sm";
17
+ };
18
+ type BadgeProps = React.HTMLAttributes<HTMLSpanElement> & {
19
+ /** @default secondary — same as Button default. */
20
+ variant?: keyof typeof variantClass;
21
+ /** @default md */
22
+ size?: keyof typeof sizeClass;
23
+ /**
24
+ * Corner radius — same options as `Button` (`full` | `lg` | `md`).
25
+ * @default full (pill)
26
+ */
27
+ rounded?: keyof typeof roundedClass;
28
+ /**
29
+ * When set, the label is the number capped at `max` with a "+" suffix
30
+ * (e.g. `max={99}` → `99+`). Ignores `children` for the visible text.
31
+ */
32
+ count?: number;
33
+ /** Upper bound before showing `{max}+`. Default `99`. */
34
+ max?: number;
35
+ };
36
+ declare const Badge: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & {
37
+ /** @default secondary — same as Button default. */
38
+ variant?: keyof typeof variantClass;
39
+ /** @default md */
40
+ size?: keyof typeof sizeClass;
41
+ /**
42
+ * Corner radius — same options as `Button` (`full` | `lg` | `md`).
43
+ * @default full (pill)
44
+ */
45
+ rounded?: keyof typeof roundedClass;
46
+ /**
47
+ * When set, the label is the number capped at `max` with a "+" suffix
48
+ * (e.g. `max={99}` → `99+`). Ignores `children` for the visible text.
49
+ */
50
+ count?: number;
51
+ /** Upper bound before showing `{max}+`. Default `99`. */
52
+ max?: number;
53
+ } & React.RefAttributes<HTMLSpanElement>>;
54
+
55
+ export { Badge, type BadgeProps };
@@ -0,0 +1,64 @@
1
+ import * as React from 'react';
2
+ import { clsx } from 'clsx';
3
+ import { twMerge } from 'tailwind-merge';
4
+ import { jsx } from 'react/jsx-runtime';
5
+
6
+ function cn(...inputs) {
7
+ return twMerge(clsx(inputs));
8
+ }
9
+ var variantClass = {
10
+ primary: "border border-transparent bg-primary text-background",
11
+ secondary: "border border-primary/10 bg-transparent text-primary"
12
+ };
13
+ var roundedClass = {
14
+ full: "rounded-full",
15
+ lg: "rounded-lg",
16
+ md: "rounded-md"
17
+ };
18
+ var sizeClass = {
19
+ md: "min-h-6 min-w-6 px-2 text-xs",
20
+ lg: "min-h-7 min-w-7 px-2.5 text-sm"
21
+ };
22
+ function formatCount(count, max) {
23
+ if (count > max) return `${max}+`;
24
+ return String(count);
25
+ }
26
+ var Badge = React.forwardRef(
27
+ ({
28
+ className,
29
+ variant = "secondary",
30
+ size = "md",
31
+ rounded = "full",
32
+ count,
33
+ max = 99,
34
+ children,
35
+ "aria-label": ariaLabelProp,
36
+ ...props
37
+ }, ref) => {
38
+ const content = count !== void 0 ? formatCount(count, max) : children;
39
+ return /* @__PURE__ */ jsx(
40
+ "span",
41
+ {
42
+ ref,
43
+ "data-slot": "badge",
44
+ "data-size": size,
45
+ "data-rounded": rounded,
46
+ "aria-label": ariaLabelProp,
47
+ className: cn(
48
+ "inline-flex shrink-0 items-center justify-center font-semibold leading-none tabular-nums",
49
+ roundedClass[rounded],
50
+ sizeClass[size],
51
+ variantClass[variant],
52
+ className
53
+ ),
54
+ ...props,
55
+ children: content
56
+ }
57
+ );
58
+ }
59
+ );
60
+ Badge.displayName = "Badge";
61
+
62
+ export { Badge };
63
+ //# sourceMappingURL=badge.js.map
64
+ //# sourceMappingURL=badge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/utils.ts","../../src/react/badge.tsx"],"names":[],"mappings":";;;;;AAGO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACEA,IAAM,YAAA,GAAe;AAAA,EACnB,OAAA,EAAS,sDAAA;AAAA,EACT,SAAA,EAAW;AACb,CAAA;AAGA,IAAM,YAAA,GAAe;AAAA,EACnB,IAAA,EAAM,cAAA;AAAA,EACN,EAAA,EAAI,YAAA;AAAA,EACJ,EAAA,EAAI;AACN,CAAA;AAEA,IAAM,SAAA,GAAY;AAAA,EAChB,EAAA,EAAI,8BAAA;AAAA,EACJ,EAAA,EAAI;AACN,CAAA;AAqBA,SAAS,WAAA,CAAY,OAAe,GAAA,EAAqB;AACvD,EAAA,IAAI,KAAA,GAAQ,GAAA,EAAK,OAAO,CAAA,EAAG,GAAG,CAAA,CAAA,CAAA;AAC9B,EAAA,OAAO,OAAO,KAAK,CAAA;AACrB;AAEA,IAAM,KAAA,GAAc,KAAA,CAAA,UAAA;AAAA,EAClB,CACE;AAAA,IACE,SAAA;AAAA,IACA,OAAA,GAAU,WAAA;AAAA,IACV,IAAA,GAAO,IAAA;AAAA,IACP,OAAA,GAAU,MAAA;AAAA,IACV,KAAA;AAAA,IACA,GAAA,GAAM,EAAA;AAAA,IACN,QAAA;AAAA,IACA,YAAA,EAAc,aAAA;AAAA,IACd,GAAG;AAAA,KAEL,GAAA,KACG;AACH,IAAA,MAAM,UAAU,KAAA,KAAU,MAAA,GAAY,WAAA,CAAY,KAAA,EAAO,GAAG,CAAA,GAAI,QAAA;AAEhE,IAAA,uBACE,GAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,WAAA,EAAU,OAAA;AAAA,QACV,WAAA,EAAW,IAAA;AAAA,QACX,cAAA,EAAc,OAAA;AAAA,QACd,YAAA,EAAY,aAAA;AAAA,QACZ,SAAA,EAAW,EAAA;AAAA,UACT,0FAAA;AAAA,UACA,aAAa,OAAO,CAAA;AAAA,UACpB,UAAU,IAAI,CAAA;AAAA,UACd,aAAa,OAAO,CAAA;AAAA,UACpB;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAEH,QAAA,EAAA;AAAA;AAAA,KACH;AAAA,EAEJ;AACF;AAEA,KAAA,CAAM,WAAA,GAAc,OAAA","file":"badge.js","sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/** Matches `button.tsx` primary / secondary — no separate outline variants. */\nconst variantClass = {\n primary: \"border border-transparent bg-primary text-background\",\n secondary: \"border border-primary/10 bg-transparent text-primary\",\n} as const;\n\n/** Same keys as `button.tsx` `roundedClass`. */\nconst roundedClass = {\n full: \"rounded-full\",\n lg: \"rounded-lg\",\n md: \"rounded-md\",\n} as const;\n\nconst sizeClass = {\n md: \"min-h-6 min-w-6 px-2 text-xs\",\n lg: \"min-h-7 min-w-7 px-2.5 text-sm\",\n} as const;\n\nexport type BadgeProps = React.HTMLAttributes<HTMLSpanElement> & {\n /** @default secondary — same as Button default. */\n variant?: keyof typeof variantClass;\n /** @default md */\n size?: keyof typeof sizeClass;\n /**\n * Corner radius — same options as `Button` (`full` | `lg` | `md`).\n * @default full (pill)\n */\n rounded?: keyof typeof roundedClass;\n /**\n * When set, the label is the number capped at `max` with a \"+\" suffix\n * (e.g. `max={99}` → `99+`). Ignores `children` for the visible text.\n */\n count?: number;\n /** Upper bound before showing `{max}+`. Default `99`. */\n max?: number;\n};\n\nfunction formatCount(count: number, max: number): string {\n if (count > max) return `${max}+`;\n return String(count);\n}\n\nconst Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(\n (\n {\n className,\n variant = \"secondary\",\n size = \"md\",\n rounded = \"full\",\n count,\n max = 99,\n children,\n \"aria-label\": ariaLabelProp,\n ...props\n },\n ref,\n ) => {\n const content = count !== undefined ? formatCount(count, max) : children;\n\n return (\n <span\n ref={ref}\n data-slot=\"badge\"\n data-size={size}\n data-rounded={rounded}\n aria-label={ariaLabelProp}\n className={cn(\n \"inline-flex shrink-0 items-center justify-center font-semibold leading-none tabular-nums\",\n roundedClass[rounded],\n sizeClass[size],\n variantClass[variant],\n className,\n )}\n {...props}\n >\n {content}\n </span>\n );\n },\n);\n\nBadge.displayName = \"Badge\";\n\nexport { Badge };\n"]}
@@ -14,14 +14,29 @@ declare const DialogClose: React__default.FC<{
14
14
  children: React__default.ReactNode;
15
15
  asChild?: boolean;
16
16
  }>;
17
+ interface DialogCloseButtonProps extends React__default.ButtonHTMLAttributes<HTMLButtonElement> {
18
+ }
19
+ /**
20
+ * Default dismiss control for {@link DialogContent}. Includes absolute top-right
21
+ * placement; pass `className` to adjust or replace positioning.
22
+ * When `onClick` is omitted, closes via the surrounding `Dialog` context (`setOpen(false)`).
23
+ */
24
+ declare const DialogCloseButton: React__default.ForwardRefExoticComponent<DialogCloseButtonProps & React__default.RefAttributes<HTMLButtonElement>>;
17
25
  interface DialogContentProps extends React__default.HTMLAttributes<HTMLDivElement> {
18
26
  size?: "sm" | "md" | "lg" | "xl" | "full";
19
27
  duration?: number;
20
28
  closeOnOverlayClick?: boolean;
21
29
  showClose?: boolean;
30
+ /**
31
+ * When `true` (default), viewports ≤1024px use a bottom-anchored panel and
32
+ * slide-up motion (same breakpoint as {@link SheetContent}). Wider viewports
33
+ * keep the centered dialog. Set `false` to always use the centered dialog.
34
+ */
35
+ mobileBottomSheet?: boolean;
22
36
  /**
23
37
  * When `true`, panel uses translateY + opacity with fixed timing and separate
24
38
  * entry/exit curves (works with any `size`). Default `false` uses scale + fade.
39
+ * Ignored when `mobileBottomSheet` applies on a narrow viewport.
25
40
  */
26
41
  slideEntrance?: boolean;
27
42
  /** Override slide distance (px); default follows `size` via `SLIDE_ENTRANCE_OFFSET_PX`. */
@@ -37,4 +52,4 @@ declare const DialogFooter: React__default.FC<React__default.HTMLAttributes<HTML
37
52
  declare const DialogTitle: React__default.FC<React__default.HTMLAttributes<HTMLHeadingElement>>;
38
53
  declare const DialogDescription: React__default.FC<React__default.HTMLAttributes<HTMLParagraphElement>>;
39
54
 
40
- export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger };
55
+ export { Dialog, DialogClose, DialogCloseButton, type DialogCloseButtonProps, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger };
@@ -1,9 +1,9 @@
1
- import React, { useState, useCallback, useEffect } from 'react';
1
+ import React, { useCallback, useState, useMemo, useEffect, useSyncExternalStore } from 'react';
2
2
  import { clsx } from 'clsx';
3
3
  import { twMerge } from 'tailwind-merge';
4
4
  import { createPortal } from 'react-dom';
5
5
  import { X } from 'lucide-react';
6
- import { jsx, jsxs } from 'react/jsx-runtime';
6
+ import { jsxs, jsx } from 'react/jsx-runtime';
7
7
 
8
8
  function cn(...inputs) {
9
9
  return twMerge(clsx(inputs));
@@ -11,6 +11,43 @@ function cn(...inputs) {
11
11
  var DIALOG_MOTION_MS = 200;
12
12
  var DIALOG_ENTRY_MOTION_EASING = "cubic-bezier(0.85, 0, 0.15, 1)";
13
13
  var DIALOG_EXIT_MOTION_EASING = "cubic-bezier(0.85, 0, 1, 0.15)";
14
+ var DIALOG_MOBILE_BOTTOM_SHEET_MAX_PX = 1024;
15
+ var DIALOG_Z_BASE = 50;
16
+ var DIALOG_Z_STEP = 10;
17
+ var dialogBodyScrollLockCount = 0;
18
+ var dialogEscapeClosers = [];
19
+ function dialogEscapeOnKeydown(e) {
20
+ if (e.key !== "Escape") return;
21
+ const top = dialogEscapeClosers[dialogEscapeClosers.length - 1];
22
+ if (!top) return;
23
+ e.preventDefault();
24
+ top();
25
+ }
26
+ function dialogPushEscapeCloser(closer) {
27
+ dialogEscapeClosers.push(closer);
28
+ if (dialogEscapeClosers.length === 1) {
29
+ window.addEventListener("keydown", dialogEscapeOnKeydown, true);
30
+ }
31
+ }
32
+ function dialogPopEscapeCloser(closer) {
33
+ const i = dialogEscapeClosers.lastIndexOf(closer);
34
+ if (i >= 0) dialogEscapeClosers.splice(i, 1);
35
+ if (dialogEscapeClosers.length === 0) {
36
+ window.removeEventListener("keydown", dialogEscapeOnKeydown, true);
37
+ }
38
+ }
39
+ function useDialogMobileBottomSheetViewport() {
40
+ const query = `(max-width: ${DIALOG_MOBILE_BOTTOM_SHEET_MAX_PX}px)`;
41
+ return useSyncExternalStore(
42
+ (onChange) => {
43
+ const mq = window.matchMedia(query);
44
+ mq.addEventListener("change", onChange);
45
+ return () => mq.removeEventListener("change", onChange);
46
+ },
47
+ () => window.matchMedia(query).matches,
48
+ () => false
49
+ );
50
+ }
14
51
  var SLIDE_ENTRANCE_OFFSET_PX = {
15
52
  sm: 16,
16
53
  md: 16,
@@ -18,6 +55,18 @@ var SLIDE_ENTRANCE_OFFSET_PX = {
18
55
  xl: 16,
19
56
  full: 120
20
57
  };
58
+ var SIZE_CLASSES = {
59
+ sm: "sm:max-w-sm",
60
+ md: "sm:max-w-md",
61
+ lg: "sm:max-w-lg",
62
+ xl: "sm:max-w-xl"
63
+ };
64
+ var SIZE_CLASSES_LG_ONLY = {
65
+ sm: "lg:max-w-sm",
66
+ md: "lg:max-w-md",
67
+ lg: "lg:max-w-lg",
68
+ xl: "lg:max-w-xl"
69
+ };
21
70
  function useDialogRenderLifecycle(open, panelCloseMs) {
22
71
  const [shouldRender, setShouldRender] = useState(open);
23
72
  const [isAnimating, setIsAnimating] = useState(false);
@@ -43,7 +92,9 @@ function useDialogRenderLifecycle(open, panelCloseMs) {
43
92
  }, [shouldRender, open]);
44
93
  return { shouldRender, isAnimating };
45
94
  }
46
- var DialogContext = React.createContext(void 0);
95
+ var DialogContext = React.createContext(
96
+ void 0
97
+ );
47
98
  function useDialog() {
48
99
  const context = React.useContext(DialogContext);
49
100
  if (!context) {
@@ -56,6 +107,9 @@ var Dialog = ({
56
107
  open: controlledOpen,
57
108
  onOpenChange
58
109
  }) => {
110
+ const parentCtx = React.useContext(DialogContext);
111
+ const parentDepth = parentCtx?.depth ?? 0;
112
+ const depth = parentDepth + 1;
59
113
  const [internalOpen, setInternalOpen] = useState(false);
60
114
  const isControlled = controlledOpen !== void 0;
61
115
  const open = isControlled ? controlledOpen : internalOpen;
@@ -68,7 +122,11 @@ var Dialog = ({
68
122
  },
69
123
  [isControlled, onOpenChange]
70
124
  );
71
- return /* @__PURE__ */ jsx(DialogContext.Provider, { value: { open, setOpen }, children });
125
+ const contextValue = useMemo(
126
+ () => ({ open, setOpen, depth }),
127
+ [open, setOpen, depth]
128
+ );
129
+ return /* @__PURE__ */ jsx(DialogContext.Provider, { value: contextValue, children });
72
130
  };
73
131
  var DialogTrigger = ({ children, asChild }) => {
74
132
  const { setOpen } = useDialog();
@@ -98,6 +156,36 @@ var DialogClose = ({ children, asChild }) => {
98
156
  }
99
157
  return /* @__PURE__ */ jsx("button", { type: "button", onClick: handleClick, children });
100
158
  };
159
+ var DialogCloseButton = React.forwardRef(({ className, type = "button", onClick, ...props }, ref) => {
160
+ const { setOpen } = useDialog();
161
+ const handleClick = useCallback(
162
+ (e) => {
163
+ onClick?.(e);
164
+ if (onClick == null) {
165
+ setOpen(false);
166
+ }
167
+ },
168
+ [onClick, setOpen]
169
+ );
170
+ return /* @__PURE__ */ jsxs(
171
+ "button",
172
+ {
173
+ ref,
174
+ type,
175
+ className: cn(
176
+ "z-100 flex size-12 cursor-pointer items-center justify-center rounded-full transition-all hover:bg-secondary-background active:scale-[0.96]",
177
+ className
178
+ ),
179
+ onClick: handleClick,
180
+ ...props,
181
+ children: [
182
+ /* @__PURE__ */ jsx(X, { className: "size-5.5" }),
183
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
184
+ ]
185
+ }
186
+ );
187
+ });
188
+ DialogCloseButton.displayName = "DialogCloseButton";
101
189
  var DialogContent = ({
102
190
  children,
103
191
  size = "md",
@@ -105,118 +193,171 @@ var DialogContent = ({
105
193
  duration = 200,
106
194
  closeOnOverlayClick = true,
107
195
  showClose = true,
196
+ mobileBottomSheet = true,
108
197
  slideEntrance = false,
109
198
  slideEntranceOffsetPx: slideEntranceOffsetPxProp,
110
- ...props
199
+ ...rest
111
200
  }) => {
112
- const { open, setOpen } = useDialog();
113
- const closeDialog = useCallback(() => setOpen(false), [setOpen]);
114
- const slideOffsetPx = slideEntranceOffsetPxProp ?? SLIDE_ENTRANCE_OFFSET_PX[size];
115
- const panelCloseMs = slideEntrance ? DIALOG_MOTION_MS : duration;
201
+ const { open, setOpen, depth } = useDialog();
202
+ const isNarrowViewport = useDialogMobileBottomSheetViewport();
203
+ const useMobileBottomSheet = useMemo(
204
+ () => mobileBottomSheet !== false && isNarrowViewport,
205
+ [mobileBottomSheet, isNarrowViewport]
206
+ );
207
+ const useMobileBottomSheetChrome = useMemo(
208
+ () => mobileBottomSheet !== false && size !== "full",
209
+ [mobileBottomSheet, size]
210
+ );
211
+ const slideOffsetPx = useMemo(
212
+ () => slideEntranceOffsetPxProp ?? SLIDE_ENTRANCE_OFFSET_PX[size],
213
+ [slideEntranceOffsetPxProp, size]
214
+ );
215
+ const useBottomSheetMotion = useMobileBottomSheet;
216
+ const useDesktopSlideEntrance = slideEntrance && !useBottomSheetMotion;
217
+ const panelCloseMs = useMemo(
218
+ () => useBottomSheetMotion ? DIALOG_MOTION_MS : slideEntrance ? DIALOG_MOTION_MS : duration,
219
+ [useBottomSheetMotion, slideEntrance, duration]
220
+ );
116
221
  const { shouldRender, isAnimating } = useDialogRenderLifecycle(
117
222
  open,
118
223
  panelCloseMs
119
224
  );
120
- const slideMotionEasing = open ? DIALOG_ENTRY_MOTION_EASING : DIALOG_EXIT_MOTION_EASING;
225
+ const slideMotionEasing = useMemo(
226
+ () => open ? DIALOG_ENTRY_MOTION_EASING : DIALOG_EXIT_MOTION_EASING,
227
+ [open]
228
+ );
229
+ const portalZIndex = useMemo(
230
+ () => DIALOG_Z_BASE + (depth - 1) * DIALOG_Z_STEP,
231
+ [depth]
232
+ );
233
+ const handleOverlayClick = useCallback(() => {
234
+ if (closeOnOverlayClick) setOpen(false);
235
+ }, [closeOnOverlayClick, setOpen]);
121
236
  useEffect(() => {
122
- const handleEsc = (e) => {
123
- if (e.key === "Escape") setOpen(false);
124
- };
125
- if (open) {
126
- if (typeof document !== "undefined") {
127
- window.addEventListener("keydown", handleEsc);
128
- document.body.style.overflow = "hidden";
129
- }
237
+ if (!open || typeof document === "undefined") return;
238
+ const closer = () => setOpen(false);
239
+ dialogPushEscapeCloser(closer);
240
+ dialogBodyScrollLockCount += 1;
241
+ if (dialogBodyScrollLockCount === 1) {
242
+ document.body.style.overflow = "hidden";
130
243
  }
131
244
  return () => {
132
- if (typeof document !== "undefined") {
133
- window.removeEventListener("keydown", handleEsc);
245
+ dialogPopEscapeCloser(closer);
246
+ dialogBodyScrollLockCount -= 1;
247
+ if (dialogBodyScrollLockCount <= 0) {
248
+ dialogBodyScrollLockCount = 0;
134
249
  document.body.style.overflow = "";
135
250
  }
136
251
  };
137
252
  }, [open, setOpen]);
253
+ const rootClassName = useMemo(
254
+ () => cn(
255
+ "fixed inset-0 flex",
256
+ size === "full" ? "h-dvh w-full flex-col p-0" : mobileBottomSheet !== false ? "max-lg:items-end max-lg:justify-center max-lg:p-0 lg:items-center lg:justify-center lg:p-4" : "items-center justify-center p-4"
257
+ ),
258
+ [size, mobileBottomSheet]
259
+ );
260
+ const rootStyle = useMemo(
261
+ () => ({ zIndex: portalZIndex }),
262
+ [portalZIndex]
263
+ );
264
+ const overlayClassName = useMemo(
265
+ () => cn(
266
+ "fixed inset-0 bg-black/40 dark:bg-black/60",
267
+ !useBottomSheetMotion && !slideEntrance && "transition-opacity ease-in-out",
268
+ isAnimating ? "opacity-100" : "opacity-0"
269
+ ),
270
+ [useBottomSheetMotion, slideEntrance, isAnimating]
271
+ );
272
+ const overlayStyle = useMemo(() => {
273
+ const style = {
274
+ transitionDuration: `${panelCloseMs}ms`
275
+ };
276
+ if (useBottomSheetMotion || slideEntrance) {
277
+ style.transitionProperty = "opacity";
278
+ style.transitionTimingFunction = slideMotionEasing;
279
+ }
280
+ return style;
281
+ }, [panelCloseMs, useBottomSheetMotion, slideEntrance, slideMotionEasing]);
282
+ const panelClassName = useMemo(() => {
283
+ const sizeKey = size === "full" ? null : size;
284
+ return cn(
285
+ "bg-background relative z-10 w-full overflow-hidden shadow-2xl",
286
+ size === "full" ? "flex min-h-0 flex-1 flex-col max-w-none rounded-none" : cn(
287
+ "border-primary/10 border",
288
+ useMobileBottomSheetChrome ? "max-lg:max-h-[min(90dvh,calc(100dvh-env(safe-area-inset-bottom,0px)))] max-lg:rounded-t-2xl max-lg:rounded-b-none max-lg:border-x-0 max-lg:border-b-0 max-lg:border-t max-lg:border-primary/10 lg:rounded-2xl" : "rounded-2xl",
289
+ sizeKey && (useMobileBottomSheetChrome ? SIZE_CLASSES_LG_ONLY[sizeKey] : SIZE_CLASSES[sizeKey])
290
+ ),
291
+ !useBottomSheetMotion && !useDesktopSlideEntrance && cn(
292
+ "transition-all ease-in-out",
293
+ isAnimating ? "scale-100 opacity-100" : "scale-95 opacity-0"
294
+ ),
295
+ className
296
+ );
297
+ }, [
298
+ size,
299
+ useMobileBottomSheetChrome,
300
+ useBottomSheetMotion,
301
+ useDesktopSlideEntrance,
302
+ isAnimating,
303
+ className
304
+ ]);
305
+ const panelStyle = useMemo(() => {
306
+ const base = { ...rest.style };
307
+ if (useBottomSheetMotion) {
308
+ return {
309
+ ...base,
310
+ transform: isAnimating ? "translateY(0)" : "translateY(100%)",
311
+ opacity: isAnimating ? 1 : 0,
312
+ transitionProperty: "transform, opacity",
313
+ transitionDuration: `${DIALOG_MOTION_MS}ms`,
314
+ transitionTimingFunction: slideMotionEasing
315
+ };
316
+ }
317
+ if (useDesktopSlideEntrance) {
318
+ return {
319
+ ...base,
320
+ transform: isAnimating ? "translateY(0)" : `translateY(${slideOffsetPx}px)`,
321
+ opacity: isAnimating ? 1 : 0,
322
+ transitionProperty: "transform, opacity",
323
+ transitionDuration: `${DIALOG_MOTION_MS}ms`,
324
+ transitionTimingFunction: slideMotionEasing
325
+ };
326
+ }
327
+ return {
328
+ ...base,
329
+ transitionDuration: `${duration}ms`
330
+ };
331
+ }, [
332
+ rest.style,
333
+ useBottomSheetMotion,
334
+ useDesktopSlideEntrance,
335
+ isAnimating,
336
+ slideOffsetPx,
337
+ slideMotionEasing,
338
+ duration
339
+ ]);
138
340
  if (!shouldRender) return null;
139
- const sizeClasses = {
140
- sm: "sm:max-w-sm",
141
- md: "sm:max-w-md",
142
- lg: "sm:max-w-lg",
143
- xl: "sm:max-w-xl"
144
- };
145
341
  return createPortal(
146
- /* @__PURE__ */ jsxs(
147
- "div",
148
- {
149
- className: cn(
150
- "fixed inset-0 z-50 flex",
151
- size === "full" ? "h-dvh w-full flex-col p-0" : "items-center justify-center p-4"
152
- ),
153
- children: [
154
- /* @__PURE__ */ jsx(
155
- "div",
156
- {
157
- className: cn(
158
- "fixed inset-0 bg-black/40 dark:bg-black/60",
159
- !slideEntrance && "transition-opacity ease-in-out",
160
- isAnimating ? "opacity-100" : "opacity-0"
161
- ),
162
- style: {
163
- transitionDuration: `${panelCloseMs}ms`,
164
- ...slideEntrance ? {
165
- transitionProperty: "opacity",
166
- transitionTimingFunction: slideMotionEasing
167
- } : {}
168
- },
169
- onClick: () => closeOnOverlayClick && setOpen(false)
170
- }
171
- ),
172
- /* @__PURE__ */ jsxs(
173
- "div",
174
- {
175
- ...props,
176
- className: cn(
177
- "bg-background relative z-50 w-full overflow-hidden shadow-2xl",
178
- size === "full" ? "flex min-h-0 flex-1 flex-col max-w-none rounded-none" : cn("border-primary/10 rounded-2xl border", sizeClasses[size]),
179
- !slideEntrance && cn(
180
- "transition-all ease-in-out",
181
- isAnimating ? "scale-100 opacity-100" : "scale-95 opacity-0"
182
- ),
183
- className
184
- ),
185
- style: {
186
- ...props.style,
187
- ...slideEntrance ? {
188
- transform: isAnimating ? "translateY(0)" : `translateY(${slideOffsetPx}px)`,
189
- opacity: isAnimating ? 1 : 0,
190
- transitionProperty: "transform, opacity",
191
- transitionDuration: `${DIALOG_MOTION_MS}ms`,
192
- transitionTimingFunction: slideMotionEasing
193
- } : {
194
- transitionDuration: `${duration}ms`
195
- }
196
- },
197
- children: [
198
- children,
199
- showClose && /* @__PURE__ */ jsxs(
200
- "button",
201
- {
202
- type: "button",
203
- onClick: closeDialog,
204
- className: cn(
205
- // Above in-panel sticky layers (e.g. ScrollHeaderSticky z-40)
206
- "absolute top-4 right-4 z-100 flex size-12 cursor-pointer items-center justify-center rounded-full bg-background transition-all hover:bg-secondary-background active:scale-[0.96] md:top-4 md:right-4"
207
- ),
208
- children: [
209
- /* @__PURE__ */ jsx(X, { className: "size-5.5" }),
210
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
211
- ]
212
- }
213
- )
214
- ]
215
- }
216
- )
217
- ]
218
- }
219
- ),
342
+ /* @__PURE__ */ jsxs("div", { className: rootClassName, style: rootStyle, children: [
343
+ /* @__PURE__ */ jsx(
344
+ "div",
345
+ {
346
+ className: overlayClassName,
347
+ style: overlayStyle,
348
+ onClick: handleOverlayClick
349
+ }
350
+ ),
351
+ /* @__PURE__ */ jsxs("div", { ...rest, className: panelClassName, style: panelStyle, children: [
352
+ children,
353
+ showClose && /* @__PURE__ */ jsx(
354
+ DialogCloseButton,
355
+ {
356
+ className: cn("absolute top-4 right-4 md:top-4 md:right-4")
357
+ }
358
+ )
359
+ ] })
360
+ ] }),
220
361
  document.body
221
362
  );
222
363
  };
@@ -262,6 +403,6 @@ var DialogDescription = ({ className, ...props }) => {
262
403
  return /* @__PURE__ */ jsx("p", { className: cn("text-muted-foreground text-sm", className), ...props });
263
404
  };
264
405
 
265
- export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger };
406
+ export { Dialog, DialogClose, DialogCloseButton, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger };
266
407
  //# sourceMappingURL=dialog.js.map
267
408
  //# sourceMappingURL=dialog.js.map