@avenue-ticketing/ui 0.3.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.
@@ -27,9 +27,16 @@ interface DialogContentProps extends React__default.HTMLAttributes<HTMLDivElemen
27
27
  duration?: number;
28
28
  closeOnOverlayClick?: boolean;
29
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;
30
36
  /**
31
37
  * When `true`, panel uses translateY + opacity with fixed timing and separate
32
38
  * entry/exit curves (works with any `size`). Default `false` uses scale + fade.
39
+ * Ignored when `mobileBottomSheet` applies on a narrow viewport.
33
40
  */
34
41
  slideEntrance?: boolean;
35
42
  /** Override slide distance (px); default follows `size` via `SLIDE_ENTRANCE_OFFSET_PX`. */
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useState, 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';
@@ -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();
@@ -135,108 +193,171 @@ var DialogContent = ({
135
193
  duration = 200,
136
194
  closeOnOverlayClick = true,
137
195
  showClose = true,
196
+ mobileBottomSheet = true,
138
197
  slideEntrance = false,
139
198
  slideEntranceOffsetPx: slideEntranceOffsetPxProp,
140
- ...props
199
+ ...rest
141
200
  }) => {
142
- const { open, setOpen } = useDialog();
143
- const slideOffsetPx = slideEntranceOffsetPxProp ?? SLIDE_ENTRANCE_OFFSET_PX[size];
144
- 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
+ );
145
221
  const { shouldRender, isAnimating } = useDialogRenderLifecycle(
146
222
  open,
147
223
  panelCloseMs
148
224
  );
149
- 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]);
150
236
  useEffect(() => {
151
- const handleEsc = (e) => {
152
- if (e.key === "Escape") setOpen(false);
153
- };
154
- if (open) {
155
- if (typeof document !== "undefined") {
156
- window.addEventListener("keydown", handleEsc);
157
- document.body.style.overflow = "hidden";
158
- }
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";
159
243
  }
160
244
  return () => {
161
- if (typeof document !== "undefined") {
162
- window.removeEventListener("keydown", handleEsc);
245
+ dialogPopEscapeCloser(closer);
246
+ dialogBodyScrollLockCount -= 1;
247
+ if (dialogBodyScrollLockCount <= 0) {
248
+ dialogBodyScrollLockCount = 0;
163
249
  document.body.style.overflow = "";
164
250
  }
165
251
  };
166
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
+ ]);
167
340
  if (!shouldRender) return null;
168
- const sizeClasses = {
169
- sm: "sm:max-w-sm",
170
- md: "sm:max-w-md",
171
- lg: "sm:max-w-lg",
172
- xl: "sm:max-w-xl"
173
- };
174
341
  return createPortal(
175
- /* @__PURE__ */ jsxs(
176
- "div",
177
- {
178
- className: cn(
179
- "fixed inset-0 z-50 flex",
180
- size === "full" ? "h-dvh w-full flex-col p-0" : "items-center justify-center p-4"
181
- ),
182
- children: [
183
- /* @__PURE__ */ jsx(
184
- "div",
185
- {
186
- className: cn(
187
- "fixed inset-0 bg-black/40 dark:bg-black/60",
188
- !slideEntrance && "transition-opacity ease-in-out",
189
- isAnimating ? "opacity-100" : "opacity-0"
190
- ),
191
- style: {
192
- transitionDuration: `${panelCloseMs}ms`,
193
- ...slideEntrance ? {
194
- transitionProperty: "opacity",
195
- transitionTimingFunction: slideMotionEasing
196
- } : {}
197
- },
198
- onClick: () => closeOnOverlayClick && setOpen(false)
199
- }
200
- ),
201
- /* @__PURE__ */ jsxs(
202
- "div",
203
- {
204
- ...props,
205
- className: cn(
206
- "bg-background relative z-50 w-full overflow-hidden shadow-2xl",
207
- size === "full" ? "flex min-h-0 flex-1 flex-col max-w-none rounded-none" : cn("border-primary/10 rounded-2xl border", sizeClasses[size]),
208
- !slideEntrance && cn(
209
- "transition-all ease-in-out",
210
- isAnimating ? "scale-100 opacity-100" : "scale-95 opacity-0"
211
- ),
212
- className
213
- ),
214
- style: {
215
- ...props.style,
216
- ...slideEntrance ? {
217
- transform: isAnimating ? "translateY(0)" : `translateY(${slideOffsetPx}px)`,
218
- opacity: isAnimating ? 1 : 0,
219
- transitionProperty: "transform, opacity",
220
- transitionDuration: `${DIALOG_MOTION_MS}ms`,
221
- transitionTimingFunction: slideMotionEasing
222
- } : {
223
- transitionDuration: `${duration}ms`
224
- }
225
- },
226
- children: [
227
- children,
228
- showClose && /* @__PURE__ */ jsx(
229
- DialogCloseButton,
230
- {
231
- className: cn("absolute top-4 right-4 md:top-4 md:right-4")
232
- }
233
- )
234
- ]
235
- }
236
- )
237
- ]
238
- }
239
- ),
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
+ ] }),
240
361
  document.body
241
362
  );
242
363
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/utils.ts","../../src/react/dialog.tsx"],"names":[],"mappings":"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","file":"dialog.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\";\nimport React, { useCallback, useEffect, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { createPortal } from \"react-dom\";\nimport { X } from \"lucide-react\";\n\n/** Used when `slideEntrance` is set — duration + entry/exit easing (see `slideMotionEasing`). */\nconst DIALOG_MOTION_MS = 200;\nconst DIALOG_ENTRY_MOTION_EASING = \"cubic-bezier(0.85, 0, 0.15, 1)\";\nconst DIALOG_EXIT_MOTION_EASING = \"cubic-bezier(0.85, 0, 1, 0.15)\";\n\ntype DialogSize = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\";\n\n/**\n * Slide distance for `slideEntrance`, scaled to `size` (full keeps 120px like a\n * mobile sheet; smaller presets use shorter travel so the motion matches width).\n */\nconst SLIDE_ENTRANCE_OFFSET_PX: Record<DialogSize, number> = {\n sm: 16,\n md: 16,\n lg: 16,\n xl: 16,\n full: 120,\n};\n\n/** Same pattern as `useSheetRenderLifecycle` in sheet.tsx (portal + enter/exit timing). */\nfunction useDialogRenderLifecycle(open: boolean, panelCloseMs: number) {\n const [shouldRender, setShouldRender] = useState(open);\n const [isAnimating, setIsAnimating] = useState(false);\n\n useEffect(() => {\n if (open) {\n setShouldRender(true);\n } else {\n setIsAnimating(false);\n const timer = setTimeout(() => setShouldRender(false), panelCloseMs);\n return () => clearTimeout(timer);\n }\n }, [open, panelCloseMs]);\n\n useEffect(() => {\n if (!shouldRender || !open) return;\n\n let raf2 = 0;\n const raf1 = requestAnimationFrame(() => {\n raf2 = requestAnimationFrame(() => setIsAnimating(true));\n });\n return () => {\n cancelAnimationFrame(raf1);\n if (raf2) cancelAnimationFrame(raf2);\n };\n }, [shouldRender, open]);\n\n return { shouldRender, isAnimating };\n}\n\n//////////////////////////////////////////////// Context\n\nconst DialogContext = React.createContext<\n | {\n open: boolean;\n setOpen: (open: boolean) => void;\n }\n | undefined\n>(undefined);\n\nfunction useDialog() {\n const context = React.useContext(DialogContext);\n if (!context) {\n throw new Error(\"Dialog components must be used within a <Dialog />\");\n }\n return context;\n}\n\n//////////////////////////////////////////////// Main\n\ninterface DialogProps {\n children: React.ReactNode;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n}\n\nexport const Dialog: React.FC<DialogProps> = ({\n children,\n open: controlledOpen,\n onOpenChange,\n}) => {\n const [internalOpen, setInternalOpen] = useState(false);\n const isControlled = controlledOpen !== undefined;\n const open = isControlled ? controlledOpen : internalOpen;\n\n const setOpen = React.useCallback(\n (value: boolean) => {\n if (!isControlled) {\n setInternalOpen(value);\n }\n onOpenChange?.(value);\n },\n [isControlled, onOpenChange],\n );\n\n return (\n <DialogContext.Provider value={{ open, setOpen }}>\n {children}\n </DialogContext.Provider>\n );\n};\n\nexport const DialogTrigger: React.FC<{\n children: React.ReactNode;\n asChild?: boolean;\n}> = ({ children, asChild }) => {\n const { setOpen } = useDialog();\n\n const handleClick = () => setOpen(true);\n\n if (asChild && React.isValidElement(children)) {\n const child = children as React.ReactElement<{\n onClick?: React.MouseEventHandler;\n }>;\n return React.cloneElement(child, {\n onClick: (e: React.MouseEvent) => {\n child.props.onClick?.(e);\n handleClick();\n },\n });\n }\n\n return (\n <button type=\"button\" onClick={handleClick}>\n {children}\n </button>\n );\n};\n\nexport const DialogClose: React.FC<{\n children: React.ReactNode;\n asChild?: boolean;\n}> = ({ children, asChild }) => {\n const { setOpen } = useDialog();\n\n const handleClick = () => setOpen(false);\n\n if (asChild && React.isValidElement(children)) {\n const child = children as React.ReactElement<{\n onClick?: React.MouseEventHandler;\n }>;\n return React.cloneElement(child, {\n onClick: (e: React.MouseEvent) => {\n child.props.onClick?.(e);\n handleClick();\n },\n });\n }\n\n return (\n <button type=\"button\" onClick={handleClick}>\n {children}\n </button>\n );\n};\n\n//////////////////////////////////////////////// Close\n\nexport interface DialogCloseButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {}\n\n/**\n * Default dismiss control for {@link DialogContent}. Includes absolute top-right\n * placement; pass `className` to adjust or replace positioning.\n * When `onClick` is omitted, closes via the surrounding `Dialog` context (`setOpen(false)`).\n */\nexport const DialogCloseButton = React.forwardRef<\n HTMLButtonElement,\n DialogCloseButtonProps\n>(({ className, type = \"button\", onClick, ...props }, ref) => {\n const { setOpen } = useDialog();\n const handleClick = useCallback(\n (e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n if (onClick == null) {\n setOpen(false);\n }\n },\n [onClick, setOpen],\n );\n\n return (\n <button\n ref={ref}\n type={type}\n className={cn(\n \"z-100 flex size-12 cursor-pointer items-center justify-center rounded-full transition-all hover:bg-secondary-background active:scale-[0.96]\",\n className,\n )}\n onClick={handleClick}\n {...props}\n >\n <X className=\"size-5.5\" />\n <span className=\"sr-only\">Close</span>\n </button>\n );\n});\nDialogCloseButton.displayName = \"DialogCloseButton\";\n\n//////////////////////////////////////////////// Content\n\ninterface DialogContentProps extends React.HTMLAttributes<HTMLDivElement> {\n size?: \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\";\n duration?: number;\n closeOnOverlayClick?: boolean;\n showClose?: boolean;\n /**\n * When `true`, panel uses translateY + opacity with fixed timing and separate\n * entry/exit curves (works with any `size`). Default `false` uses scale + fade.\n */\n slideEntrance?: boolean;\n /** Override slide distance (px); default follows `size` via `SLIDE_ENTRANCE_OFFSET_PX`. */\n slideEntranceOffsetPx?: number;\n}\n\nexport const DialogContent: React.FC<DialogContentProps> = ({\n children,\n size = \"md\",\n className,\n duration = 200,\n closeOnOverlayClick = true,\n showClose = true,\n slideEntrance = false,\n slideEntranceOffsetPx: slideEntranceOffsetPxProp,\n ...props\n}) => {\n const { open, setOpen } = useDialog();\n\n const slideOffsetPx =\n slideEntranceOffsetPxProp ?? SLIDE_ENTRANCE_OFFSET_PX[size];\n\n const panelCloseMs = slideEntrance ? DIALOG_MOTION_MS : duration;\n const { shouldRender, isAnimating } = useDialogRenderLifecycle(\n open,\n panelCloseMs,\n );\n\n /** Slide entrance: entry easing while opening, exit easing while closing. */\n const slideMotionEasing = open\n ? DIALOG_ENTRY_MOTION_EASING\n : DIALOG_EXIT_MOTION_EASING;\n\n useEffect(() => {\n const handleEsc = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n if (open) {\n if (typeof document !== \"undefined\") {\n window.addEventListener(\"keydown\", handleEsc);\n document.body.style.overflow = \"hidden\";\n }\n }\n return () => {\n if (typeof document !== \"undefined\") {\n window.removeEventListener(\"keydown\", handleEsc);\n document.body.style.overflow = \"\";\n }\n };\n }, [open, setOpen]);\n\n if (!shouldRender) return null;\n\n const sizeClasses: Record<\"sm\" | \"md\" | \"lg\" | \"xl\", string> = {\n sm: \"sm:max-w-sm\",\n md: \"sm:max-w-md\",\n lg: \"sm:max-w-lg\",\n xl: \"sm:max-w-xl\",\n };\n\n return createPortal(\n <div\n className={cn(\n \"fixed inset-0 z-50 flex\",\n size === \"full\"\n ? \"h-dvh w-full flex-col p-0\"\n : \"items-center justify-center p-4\",\n )}\n >\n {/* Overlay */}\n <div\n className={cn(\n \"fixed inset-0 bg-black/40 dark:bg-black/60\",\n !slideEntrance && \"transition-opacity ease-in-out\",\n isAnimating ? \"opacity-100\" : \"opacity-0\",\n )}\n style={{\n transitionDuration: `${panelCloseMs}ms`,\n ...(slideEntrance\n ? {\n transitionProperty: \"opacity\",\n transitionTimingFunction: slideMotionEasing,\n }\n : {}),\n }}\n onClick={() => closeOnOverlayClick && setOpen(false)}\n />\n\n {/* Panel */}\n <div\n {...props}\n className={cn(\n \"bg-background relative z-50 w-full overflow-hidden shadow-2xl\",\n size === \"full\"\n ? \"flex min-h-0 flex-1 flex-col max-w-none rounded-none\"\n : cn(\"border-primary/10 rounded-2xl border\", sizeClasses[size]),\n !slideEntrance &&\n cn(\n \"transition-all ease-in-out\",\n isAnimating ? \"scale-100 opacity-100\" : \"scale-95 opacity-0\",\n ),\n className,\n )}\n style={{\n ...props.style,\n ...(slideEntrance\n ? {\n transform: isAnimating\n ? \"translateY(0)\"\n : `translateY(${slideOffsetPx}px)`,\n opacity: isAnimating ? 1 : 0,\n transitionProperty: \"transform, opacity\",\n transitionDuration: `${DIALOG_MOTION_MS}ms`,\n transitionTimingFunction: slideMotionEasing,\n }\n : {\n transitionDuration: `${duration}ms`,\n }),\n }}\n >\n {children}\n\n {showClose && (\n <DialogCloseButton\n className={cn(\"absolute top-4 right-4 md:top-4 md:right-4\")}\n />\n )}\n </div>\n </div>,\n document.body,\n );\n};\n\nexport const DialogHeader: React.FC<\n React.HTMLAttributes<HTMLDivElement> & { fixed?: boolean }\n> = ({ className, fixed, ...props }) => {\n return (\n <div\n className={cn(\n \"flex flex-col space-y-1.5 p-6 text-center sm:text-left\",\n fixed && \"bg-background border-primary/10 sticky top-0 z-10 border-b\",\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const DialogFooter: React.FC<\n React.HTMLAttributes<HTMLDivElement> & { fixed?: boolean }\n> = ({ className, fixed, ...props }) => {\n return (\n <div\n className={cn(\n \"flex flex-col-reverse p-6 sm:flex-row sm:justify-end sm:space-x-2\",\n fixed &&\n \"bg-background border-primary/10 sticky bottom-0 z-10 border-t\",\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const DialogTitle: React.FC<\n React.HTMLAttributes<HTMLHeadingElement>\n> = ({ className, ...props }) => {\n return (\n <h3\n className={cn(\n \"text-primary text-xl leading-none font-semibold\",\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const DialogDescription: React.FC<\n React.HTMLAttributes<HTMLParagraphElement>\n> = ({ className, ...props }) => {\n return (\n <p className={cn(\"text-muted-foreground text-sm\", className)} {...props} />\n );\n};\n"]}
1
+ {"version":3,"sources":["../../src/lib/utils.ts","../../src/react/dialog.tsx"],"names":[],"mappings":"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","file":"dialog.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\";\nimport React, {\n useCallback,\n useEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { createPortal } from \"react-dom\";\nimport { X } from \"lucide-react\";\n\n/** Used for slide / bottom-sheet motion — duration + entry/exit easing (see `slideMotionEasing`). */\nconst DIALOG_MOTION_MS = 200;\nconst DIALOG_ENTRY_MOTION_EASING = \"cubic-bezier(0.85, 0, 0.15, 1)\";\nconst DIALOG_EXIT_MOTION_EASING = \"cubic-bezier(0.85, 0, 1, 0.15)\";\n\n/** matches `SHEET_MOBILE_MAX_WIDTH_PX` in sheet.tsx */\nconst DIALOG_MOBILE_BOTTOM_SHEET_MAX_PX = 1024;\n\nconst DIALOG_Z_BASE = 50;\nconst DIALOG_Z_STEP = 10;\n\nlet dialogBodyScrollLockCount = 0;\nconst dialogEscapeClosers: Array<() => void> = [];\n\nfunction dialogEscapeOnKeydown(e: KeyboardEvent) {\n if (e.key !== \"Escape\") return;\n const top = dialogEscapeClosers[dialogEscapeClosers.length - 1];\n if (!top) return;\n e.preventDefault();\n top();\n}\n\nfunction dialogPushEscapeCloser(closer: () => void) {\n dialogEscapeClosers.push(closer);\n if (dialogEscapeClosers.length === 1) {\n window.addEventListener(\"keydown\", dialogEscapeOnKeydown, true);\n }\n}\n\nfunction dialogPopEscapeCloser(closer: () => void) {\n const i = dialogEscapeClosers.lastIndexOf(closer);\n if (i >= 0) dialogEscapeClosers.splice(i, 1);\n if (dialogEscapeClosers.length === 0) {\n window.removeEventListener(\"keydown\", dialogEscapeOnKeydown, true);\n }\n}\n\ntype DialogSize = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\";\n\nfunction useDialogMobileBottomSheetViewport() {\n const query = `(max-width: ${DIALOG_MOBILE_BOTTOM_SHEET_MAX_PX}px)`;\n return useSyncExternalStore(\n (onChange) => {\n const mq = window.matchMedia(query);\n mq.addEventListener(\"change\", onChange);\n return () => mq.removeEventListener(\"change\", onChange);\n },\n () => window.matchMedia(query).matches,\n () => false,\n );\n}\n\n/**\n * Slide distance for `slideEntrance`, scaled to `size` (full keeps 120px like a\n * mobile sheet; smaller presets use shorter travel so the motion matches width).\n */\nconst SLIDE_ENTRANCE_OFFSET_PX: Record<DialogSize, number> = {\n sm: 16,\n md: 16,\n lg: 16,\n xl: 16,\n full: 120,\n};\n\nconst SIZE_CLASSES: Record<Exclude<DialogSize, \"full\">, string> = {\n sm: \"sm:max-w-sm\",\n md: \"sm:max-w-md\",\n lg: \"sm:max-w-lg\",\n xl: \"sm:max-w-xl\",\n};\n\nconst SIZE_CLASSES_LG_ONLY: Record<Exclude<DialogSize, \"full\">, string> = {\n sm: \"lg:max-w-sm\",\n md: \"lg:max-w-md\",\n lg: \"lg:max-w-lg\",\n xl: \"lg:max-w-xl\",\n};\n\n/** Same pattern as `useSheetRenderLifecycle` in sheet.tsx (portal + enter/exit timing). */\nfunction useDialogRenderLifecycle(open: boolean, panelCloseMs: number) {\n const [shouldRender, setShouldRender] = useState(open);\n const [isAnimating, setIsAnimating] = useState(false);\n\n useEffect(() => {\n if (open) {\n setShouldRender(true);\n } else {\n setIsAnimating(false);\n const timer = setTimeout(() => setShouldRender(false), panelCloseMs);\n return () => clearTimeout(timer);\n }\n }, [open, panelCloseMs]);\n\n useEffect(() => {\n if (!shouldRender || !open) return;\n\n let raf2 = 0;\n const raf1 = requestAnimationFrame(() => {\n raf2 = requestAnimationFrame(() => setIsAnimating(true));\n });\n return () => {\n cancelAnimationFrame(raf1);\n if (raf2) cancelAnimationFrame(raf2);\n };\n }, [shouldRender, open]);\n\n return { shouldRender, isAnimating };\n}\n\n//////////////////////////////////////////////// Context\n\ntype DialogContextValue = {\n open: boolean;\n setOpen: (open: boolean) => void;\n /** nesting depth for z-index (1 = outermost dialog in tree) */\n depth: number;\n};\n\nconst DialogContext = React.createContext<DialogContextValue | undefined>(\n undefined,\n);\n\nfunction useDialog() {\n const context = React.useContext(DialogContext);\n if (!context) {\n throw new Error(\"Dialog components must be used within a <Dialog />\");\n }\n return context;\n}\n\n//////////////////////////////////////////////// Main\n\ninterface DialogProps {\n children: React.ReactNode;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n}\n\nexport const Dialog: React.FC<DialogProps> = ({\n children,\n open: controlledOpen,\n onOpenChange,\n}) => {\n const parentCtx = React.useContext(DialogContext);\n const parentDepth = parentCtx?.depth ?? 0;\n const depth = parentDepth + 1;\n\n const [internalOpen, setInternalOpen] = useState(false);\n const isControlled = controlledOpen !== undefined;\n const open = isControlled ? controlledOpen : internalOpen;\n\n const setOpen = React.useCallback(\n (value: boolean) => {\n if (!isControlled) {\n setInternalOpen(value);\n }\n onOpenChange?.(value);\n },\n [isControlled, onOpenChange],\n );\n\n const contextValue = useMemo<DialogContextValue>(\n () => ({ open, setOpen, depth }),\n [open, setOpen, depth],\n );\n\n return (\n <DialogContext.Provider value={contextValue}>\n {children}\n </DialogContext.Provider>\n );\n};\n\nexport const DialogTrigger: React.FC<{\n children: React.ReactNode;\n asChild?: boolean;\n}> = ({ children, asChild }) => {\n const { setOpen } = useDialog();\n\n const handleClick = () => setOpen(true);\n\n if (asChild && React.isValidElement(children)) {\n const child = children as React.ReactElement<{\n onClick?: React.MouseEventHandler;\n }>;\n return React.cloneElement(child, {\n onClick: (e: React.MouseEvent) => {\n child.props.onClick?.(e);\n handleClick();\n },\n });\n }\n\n return (\n <button type=\"button\" onClick={handleClick}>\n {children}\n </button>\n );\n};\n\nexport const DialogClose: React.FC<{\n children: React.ReactNode;\n asChild?: boolean;\n}> = ({ children, asChild }) => {\n const { setOpen } = useDialog();\n\n const handleClick = () => setOpen(false);\n\n if (asChild && React.isValidElement(children)) {\n const child = children as React.ReactElement<{\n onClick?: React.MouseEventHandler;\n }>;\n return React.cloneElement(child, {\n onClick: (e: React.MouseEvent) => {\n child.props.onClick?.(e);\n handleClick();\n },\n });\n }\n\n return (\n <button type=\"button\" onClick={handleClick}>\n {children}\n </button>\n );\n};\n\n//////////////////////////////////////////////// Close\n\nexport interface DialogCloseButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {}\n\n/**\n * Default dismiss control for {@link DialogContent}. Includes absolute top-right\n * placement; pass `className` to adjust or replace positioning.\n * When `onClick` is omitted, closes via the surrounding `Dialog` context (`setOpen(false)`).\n */\nexport const DialogCloseButton = React.forwardRef<\n HTMLButtonElement,\n DialogCloseButtonProps\n>(({ className, type = \"button\", onClick, ...props }, ref) => {\n const { setOpen } = useDialog();\n const handleClick = useCallback(\n (e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n if (onClick == null) {\n setOpen(false);\n }\n },\n [onClick, setOpen],\n );\n\n return (\n <button\n ref={ref}\n type={type}\n className={cn(\n \"z-100 flex size-12 cursor-pointer items-center justify-center rounded-full transition-all hover:bg-secondary-background active:scale-[0.96]\",\n className,\n )}\n onClick={handleClick}\n {...props}\n >\n <X className=\"size-5.5\" />\n <span className=\"sr-only\">Close</span>\n </button>\n );\n});\nDialogCloseButton.displayName = \"DialogCloseButton\";\n\n//////////////////////////////////////////////// Content\n\ninterface DialogContentProps extends React.HTMLAttributes<HTMLDivElement> {\n size?: \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\";\n duration?: number;\n closeOnOverlayClick?: boolean;\n showClose?: boolean;\n /**\n * When `true` (default), viewports ≤1024px use a bottom-anchored panel and\n * slide-up motion (same breakpoint as {@link SheetContent}). Wider viewports\n * keep the centered dialog. Set `false` to always use the centered dialog.\n */\n mobileBottomSheet?: boolean;\n /**\n * When `true`, panel uses translateY + opacity with fixed timing and separate\n * entry/exit curves (works with any `size`). Default `false` uses scale + fade.\n * Ignored when `mobileBottomSheet` applies on a narrow viewport.\n */\n slideEntrance?: boolean;\n /** Override slide distance (px); default follows `size` via `SLIDE_ENTRANCE_OFFSET_PX`. */\n slideEntranceOffsetPx?: number;\n}\n\nexport const DialogContent: React.FC<DialogContentProps> = ({\n children,\n size = \"md\",\n className,\n duration = 200,\n closeOnOverlayClick = true,\n showClose = true,\n mobileBottomSheet = true,\n slideEntrance = false,\n slideEntranceOffsetPx: slideEntranceOffsetPxProp,\n ...rest\n}) => {\n const { open, setOpen, depth } = useDialog();\n\n const isNarrowViewport = useDialogMobileBottomSheetViewport();\n\n const useMobileBottomSheet = useMemo(\n () => mobileBottomSheet !== false && isNarrowViewport,\n [mobileBottomSheet, isNarrowViewport],\n );\n\n const useMobileBottomSheetChrome = useMemo(\n () => mobileBottomSheet !== false && size !== \"full\",\n [mobileBottomSheet, size],\n );\n\n const slideOffsetPx = useMemo(\n () => slideEntranceOffsetPxProp ?? SLIDE_ENTRANCE_OFFSET_PX[size],\n [slideEntranceOffsetPxProp, size],\n );\n\n const useBottomSheetMotion = useMobileBottomSheet;\n const useDesktopSlideEntrance = slideEntrance && !useBottomSheetMotion;\n\n const panelCloseMs = useMemo(\n () =>\n useBottomSheetMotion\n ? DIALOG_MOTION_MS\n : slideEntrance\n ? DIALOG_MOTION_MS\n : duration,\n [useBottomSheetMotion, slideEntrance, duration],\n );\n\n const { shouldRender, isAnimating } = useDialogRenderLifecycle(\n open,\n panelCloseMs,\n );\n\n const slideMotionEasing = useMemo(\n () => (open ? DIALOG_ENTRY_MOTION_EASING : DIALOG_EXIT_MOTION_EASING),\n [open],\n );\n\n const portalZIndex = useMemo(\n () => DIALOG_Z_BASE + (depth - 1) * DIALOG_Z_STEP,\n [depth],\n );\n\n const handleOverlayClick = useCallback(() => {\n if (closeOnOverlayClick) setOpen(false);\n }, [closeOnOverlayClick, setOpen]);\n\n useEffect(() => {\n if (!open || typeof document === \"undefined\") return;\n\n const closer = () => setOpen(false);\n dialogPushEscapeCloser(closer);\n\n dialogBodyScrollLockCount += 1;\n if (dialogBodyScrollLockCount === 1) {\n document.body.style.overflow = \"hidden\";\n }\n\n return () => {\n dialogPopEscapeCloser(closer);\n dialogBodyScrollLockCount -= 1;\n if (dialogBodyScrollLockCount <= 0) {\n dialogBodyScrollLockCount = 0;\n document.body.style.overflow = \"\";\n }\n };\n }, [open, setOpen]);\n\n const rootClassName = useMemo(\n () =>\n cn(\n \"fixed inset-0 flex\",\n size === \"full\"\n ? \"h-dvh w-full flex-col p-0\"\n : mobileBottomSheet !== false\n ? \"max-lg:items-end max-lg:justify-center max-lg:p-0 lg:items-center lg:justify-center lg:p-4\"\n : \"items-center justify-center p-4\",\n ),\n [size, mobileBottomSheet],\n );\n\n const rootStyle = useMemo(\n (): React.CSSProperties => ({ zIndex: portalZIndex }),\n [portalZIndex],\n );\n\n const overlayClassName = useMemo(\n () =>\n cn(\n \"fixed inset-0 bg-black/40 dark:bg-black/60\",\n !useBottomSheetMotion &&\n !slideEntrance &&\n \"transition-opacity ease-in-out\",\n isAnimating ? \"opacity-100\" : \"opacity-0\",\n ),\n [useBottomSheetMotion, slideEntrance, isAnimating],\n );\n\n const overlayStyle = useMemo((): React.CSSProperties => {\n const style: React.CSSProperties = {\n transitionDuration: `${panelCloseMs}ms`,\n };\n if (useBottomSheetMotion || slideEntrance) {\n style.transitionProperty = \"opacity\";\n style.transitionTimingFunction = slideMotionEasing;\n }\n return style;\n }, [panelCloseMs, useBottomSheetMotion, slideEntrance, slideMotionEasing]);\n\n const panelClassName = useMemo(() => {\n const sizeKey = size === \"full\" ? null : size;\n return cn(\n \"bg-background relative z-10 w-full overflow-hidden shadow-2xl\",\n size === \"full\"\n ? \"flex min-h-0 flex-1 flex-col max-w-none rounded-none\"\n : cn(\n \"border-primary/10 border\",\n useMobileBottomSheetChrome\n ? \"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\"\n : \"rounded-2xl\",\n sizeKey &&\n (useMobileBottomSheetChrome\n ? SIZE_CLASSES_LG_ONLY[sizeKey]\n : SIZE_CLASSES[sizeKey]),\n ),\n !useBottomSheetMotion &&\n !useDesktopSlideEntrance &&\n cn(\n \"transition-all ease-in-out\",\n isAnimating ? \"scale-100 opacity-100\" : \"scale-95 opacity-0\",\n ),\n className,\n );\n }, [\n size,\n useMobileBottomSheetChrome,\n useBottomSheetMotion,\n useDesktopSlideEntrance,\n isAnimating,\n className,\n ]);\n\n const panelStyle = useMemo((): React.CSSProperties => {\n const base = { ...rest.style };\n if (useBottomSheetMotion) {\n return {\n ...base,\n transform: isAnimating ? \"translateY(0)\" : \"translateY(100%)\",\n opacity: isAnimating ? 1 : 0,\n transitionProperty: \"transform, opacity\",\n transitionDuration: `${DIALOG_MOTION_MS}ms`,\n transitionTimingFunction: slideMotionEasing,\n };\n }\n if (useDesktopSlideEntrance) {\n return {\n ...base,\n transform: isAnimating\n ? \"translateY(0)\"\n : `translateY(${slideOffsetPx}px)`,\n opacity: isAnimating ? 1 : 0,\n transitionProperty: \"transform, opacity\",\n transitionDuration: `${DIALOG_MOTION_MS}ms`,\n transitionTimingFunction: slideMotionEasing,\n };\n }\n return {\n ...base,\n transitionDuration: `${duration}ms`,\n };\n }, [\n rest.style,\n useBottomSheetMotion,\n useDesktopSlideEntrance,\n isAnimating,\n slideOffsetPx,\n slideMotionEasing,\n duration,\n ]);\n\n if (!shouldRender) return null;\n\n return createPortal(\n <div className={rootClassName} style={rootStyle}>\n <div\n className={overlayClassName}\n style={overlayStyle}\n onClick={handleOverlayClick}\n />\n\n <div {...rest} className={panelClassName} style={panelStyle}>\n {children}\n\n {showClose && (\n <DialogCloseButton\n className={cn(\"absolute top-4 right-4 md:top-4 md:right-4\")}\n />\n )}\n </div>\n </div>,\n document.body,\n );\n};\n\nexport const DialogHeader: React.FC<\n React.HTMLAttributes<HTMLDivElement> & { fixed?: boolean }\n> = ({ className, fixed, ...props }) => {\n return (\n <div\n className={cn(\n \"flex flex-col space-y-1.5 p-6 text-center sm:text-left\",\n fixed && \"bg-background border-primary/10 sticky top-0 z-10 border-b\",\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const DialogFooter: React.FC<\n React.HTMLAttributes<HTMLDivElement> & { fixed?: boolean }\n> = ({ className, fixed, ...props }) => {\n return (\n <div\n className={cn(\n \"flex flex-col-reverse p-6 sm:flex-row sm:justify-end sm:space-x-2\",\n fixed &&\n \"bg-background border-primary/10 sticky bottom-0 z-10 border-t\",\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const DialogTitle: React.FC<\n React.HTMLAttributes<HTMLHeadingElement>\n> = ({ className, ...props }) => {\n return (\n <h3\n className={cn(\n \"text-primary text-xl leading-none font-semibold\",\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const DialogDescription: React.FC<\n React.HTMLAttributes<HTMLParagraphElement>\n> = ({ className, ...props }) => {\n return (\n <p className={cn(\"text-muted-foreground text-sm\", className)} {...props} />\n );\n};\n"]}
@@ -0,0 +1,109 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React__default from 'react';
3
+ import { ChevronDown } from 'lucide-react';
4
+ import { ButtonProps } from './button.js';
5
+
6
+ type Side = "top" | "bottom" | "left" | "right";
7
+ type Align = "start" | "center" | "end";
8
+ interface DropdownProps {
9
+ children: React__default.ReactNode;
10
+ open?: boolean;
11
+ onOpenChange?: (open: boolean) => void;
12
+ }
13
+ declare const Dropdown: React__default.FC<DropdownProps>;
14
+ type DropdownTriggerProps = Omit<ButtonProps, "type"> & {
15
+ /**
16
+ * Merge trigger behavior onto a single child (e.g. your own `Button`).
17
+ * Without `asChild`, the trigger renders the shared `Button` primitive.
18
+ */
19
+ asChild?: boolean;
20
+ };
21
+ declare const DropdownTrigger: React__default.ForwardRefExoticComponent<Omit<DropdownTriggerProps, "ref"> & React__default.RefAttributes<HTMLButtonElement>>;
22
+ /** Chevron for menu triggers; rotates 180° when the menu is open. */
23
+ declare function DropdownChevron({ className, ...props }: React__default.ComponentProps<typeof ChevronDown>): react_jsx_runtime.JSX.Element;
24
+ interface DropdownMobileCloseProps extends React__default.ButtonHTMLAttributes<HTMLButtonElement> {
25
+ }
26
+ /**
27
+ * Dismiss control for the mobile bottom-sheet menu. Styling matches
28
+ * {@link DialogCloseButton} (circular hit target, X icon).
29
+ */
30
+ declare const DropdownMobileClose: React__default.ForwardRefExoticComponent<DropdownMobileCloseProps & React__default.RefAttributes<HTMLButtonElement>>;
31
+ interface DropdownContentProps extends React__default.HTMLAttributes<HTMLDivElement> {
32
+ side?: Side;
33
+ align?: Align;
34
+ /** Gap in px between the trigger and the menu edge (default 10). */
35
+ offset?: number;
36
+ duration?: number;
37
+ viewportPadding?: number;
38
+ closeOnEscape?: boolean;
39
+ /** Minimum width in px, or `"trigger"` to match the trigger (never below 192px). */
40
+ minWidth?: number | "trigger";
41
+ /** Loop keyboard focus at list boundaries */
42
+ loop?: boolean;
43
+ /**
44
+ * On viewports ≤1024px (same as `Dialog` mobile bottom sheet), render as a
45
+ * bottom sheet instead of a floating menu. Defaults to true.
46
+ */
47
+ mobileSheet?: boolean;
48
+ /** Title shown in the mobile sheet header (optional) */
49
+ mobileTitle?: string;
50
+ /**
51
+ * Mobile bottom sheet only. When `true` (default), panel motion matches Dialog
52
+ * `slideEntrance`: `translateY(slideEntranceOffsetPx)` + opacity, with the same
53
+ * double-RAF open timing as `DialogContent`. When `false`, uses full
54
+ * `translateY(100%)` like Dialog’s default narrow-viewport bottom sheet.
55
+ */
56
+ slideEntrance?: boolean;
57
+ /** Mobile sheet only; defaults to 120px (same as Dialog `slideEntrance` for `size="full"`). */
58
+ slideEntranceOffsetPx?: number;
59
+ }
60
+ declare const DropdownContent: React__default.FC<DropdownContentProps>;
61
+ interface DropdownItemProps extends React__default.HTMLAttributes<HTMLDivElement> {
62
+ disabled?: boolean;
63
+ destructive?: boolean;
64
+ icon?: React__default.ReactNode;
65
+ shortcut?: string;
66
+ /** Close menu on click (default true) */
67
+ closeOnSelect?: boolean;
68
+ inset?: boolean;
69
+ }
70
+ declare const DropdownItem: React__default.FC<DropdownItemProps>;
71
+ declare const DropdownSeparator: React__default.FC<React__default.HTMLAttributes<HTMLDivElement>>;
72
+ declare const DropdownLabel: React__default.FC<React__default.HTMLAttributes<HTMLDivElement> & {
73
+ inset?: boolean;
74
+ }>;
75
+ interface DropdownCheckboxItemProps extends Omit<DropdownItemProps, "icon"> {
76
+ checked?: boolean;
77
+ onCheckedChange?: (checked: boolean) => void;
78
+ }
79
+ declare const DropdownCheckboxItem: React__default.FC<DropdownCheckboxItemProps>;
80
+ declare const DropdownRadioGroup: React__default.FC<{
81
+ children: React__default.ReactNode;
82
+ value?: string;
83
+ onValueChange?: (value: string) => void;
84
+ group: string;
85
+ }>;
86
+ interface DropdownRadioItemProps extends Omit<DropdownItemProps, "icon"> {
87
+ value: string;
88
+ }
89
+ declare const DropdownRadioItem: React__default.FC<DropdownRadioItemProps>;
90
+ declare const DropdownSub: React__default.FC<{
91
+ children: React__default.ReactNode;
92
+ }>;
93
+ declare const DropdownSubTrigger: React__default.FC<React__default.HTMLAttributes<HTMLDivElement> & {
94
+ icon?: React__default.ReactNode;
95
+ inset?: boolean;
96
+ disabled?: boolean;
97
+ }>;
98
+ declare const DropdownSubContent: React__default.FC<React__default.HTMLAttributes<HTMLDivElement> & {
99
+ duration?: number;
100
+ viewportPadding?: number;
101
+ /** Same breakpoint and sheet as {@link DropdownContent}; default true. */
102
+ mobileSheet?: boolean;
103
+ mobileTitle?: string;
104
+ /** Same as {@link DropdownContent} slideEntrance; default true. */
105
+ slideEntrance?: boolean;
106
+ slideEntranceOffsetPx?: number;
107
+ }>;
108
+
109
+ export { Dropdown, DropdownCheckboxItem, DropdownChevron, DropdownContent, DropdownItem, DropdownLabel, DropdownMobileClose, type DropdownMobileCloseProps, DropdownRadioGroup, DropdownRadioItem, DropdownSeparator, DropdownSub, DropdownSubContent, DropdownSubTrigger, DropdownTrigger, type DropdownTriggerProps };