@avenue-ticketing/ui 0.2.0 → 0.3.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.
- package/dist/react/badge.d.ts +55 -0
- package/dist/react/badge.js +64 -0
- package/dist/react/badge.js.map +1 -0
- package/dist/react/dialog.d.ts +9 -1
- package/dist/react/dialog.js +36 -16
- package/dist/react/dialog.js.map +1 -1
- package/dist/react/scroll-header.d.ts +19 -1
- package/dist/react/scroll-header.js +129 -40
- package/dist/react/scroll-header.js.map +1 -1
- package/dist/react/sheet.d.ts +15 -1
- package/dist/react/sheet.js +44 -16
- package/dist/react/sheet.js.map +1 -1
- package/dist/react/tabs.d.ts +28 -0
- package/dist/react/tabs.js +263 -0
- package/dist/react/tabs.js.map +1 -0
- package/package.json +1 -1
|
@@ -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"]}
|
package/dist/react/dialog.d.ts
CHANGED
|
@@ -14,6 +14,14 @@ 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;
|
|
@@ -37,4 +45,4 @@ declare const DialogFooter: React__default.FC<React__default.HTMLAttributes<HTML
|
|
|
37
45
|
declare const DialogTitle: React__default.FC<React__default.HTMLAttributes<HTMLHeadingElement>>;
|
|
38
46
|
declare const DialogDescription: React__default.FC<React__default.HTMLAttributes<HTMLParagraphElement>>;
|
|
39
47
|
|
|
40
|
-
export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger };
|
|
48
|
+
export { Dialog, DialogClose, DialogCloseButton, type DialogCloseButtonProps, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger };
|
package/dist/react/dialog.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useCallback, useState, useEffect } 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 {
|
|
6
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
7
7
|
|
|
8
8
|
function cn(...inputs) {
|
|
9
9
|
return twMerge(clsx(inputs));
|
|
@@ -98,6 +98,36 @@ var DialogClose = ({ children, asChild }) => {
|
|
|
98
98
|
}
|
|
99
99
|
return /* @__PURE__ */ jsx("button", { type: "button", onClick: handleClick, children });
|
|
100
100
|
};
|
|
101
|
+
var DialogCloseButton = React.forwardRef(({ className, type = "button", onClick, ...props }, ref) => {
|
|
102
|
+
const { setOpen } = useDialog();
|
|
103
|
+
const handleClick = useCallback(
|
|
104
|
+
(e) => {
|
|
105
|
+
onClick?.(e);
|
|
106
|
+
if (onClick == null) {
|
|
107
|
+
setOpen(false);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
[onClick, setOpen]
|
|
111
|
+
);
|
|
112
|
+
return /* @__PURE__ */ jsxs(
|
|
113
|
+
"button",
|
|
114
|
+
{
|
|
115
|
+
ref,
|
|
116
|
+
type,
|
|
117
|
+
className: cn(
|
|
118
|
+
"z-100 flex size-12 cursor-pointer items-center justify-center rounded-full transition-all hover:bg-secondary-background active:scale-[0.96]",
|
|
119
|
+
className
|
|
120
|
+
),
|
|
121
|
+
onClick: handleClick,
|
|
122
|
+
...props,
|
|
123
|
+
children: [
|
|
124
|
+
/* @__PURE__ */ jsx(X, { className: "size-5.5" }),
|
|
125
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
DialogCloseButton.displayName = "DialogCloseButton";
|
|
101
131
|
var DialogContent = ({
|
|
102
132
|
children,
|
|
103
133
|
size = "md",
|
|
@@ -110,7 +140,6 @@ var DialogContent = ({
|
|
|
110
140
|
...props
|
|
111
141
|
}) => {
|
|
112
142
|
const { open, setOpen } = useDialog();
|
|
113
|
-
const closeDialog = useCallback(() => setOpen(false), [setOpen]);
|
|
114
143
|
const slideOffsetPx = slideEntranceOffsetPxProp ?? SLIDE_ENTRANCE_OFFSET_PX[size];
|
|
115
144
|
const panelCloseMs = slideEntrance ? DIALOG_MOTION_MS : duration;
|
|
116
145
|
const { shouldRender, isAnimating } = useDialogRenderLifecycle(
|
|
@@ -196,19 +225,10 @@ var DialogContent = ({
|
|
|
196
225
|
},
|
|
197
226
|
children: [
|
|
198
227
|
children,
|
|
199
|
-
showClose && /* @__PURE__ */
|
|
200
|
-
|
|
228
|
+
showClose && /* @__PURE__ */ jsx(
|
|
229
|
+
DialogCloseButton,
|
|
201
230
|
{
|
|
202
|
-
|
|
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
|
-
]
|
|
231
|
+
className: cn("absolute top-4 right-4 md:top-4 md:right-4")
|
|
212
232
|
}
|
|
213
233
|
)
|
|
214
234
|
]
|
|
@@ -262,6 +282,6 @@ var DialogDescription = ({ className, ...props }) => {
|
|
|
262
282
|
return /* @__PURE__ */ jsx("p", { className: cn("text-muted-foreground text-sm", className), ...props });
|
|
263
283
|
};
|
|
264
284
|
|
|
265
|
-
export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger };
|
|
285
|
+
export { Dialog, DialogClose, DialogCloseButton, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger };
|
|
266
286
|
//# sourceMappingURL=dialog.js.map
|
|
267
287
|
//# sourceMappingURL=dialog.js.map
|
package/dist/react/dialog.js.map
CHANGED
|
@@ -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//////////////////////////////////////////////// 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 const closeDialog = useCallback(() => setOpen(false), [setOpen]);\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 <button\n type=\"button\"\n onClick={closeDialog}\n className={cn(\n // Above in-panel sticky layers (e.g. ScrollHeaderSticky z-40)\n \"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\",\n )}\n >\n <X className=\"size-5.5\" />\n <span className=\"sr-only\">Close</span>\n </button>\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":";;;;;;;AAGO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACEA,IAAM,gBAAA,GAAmB,GAAA;AACzB,IAAM,0BAAA,GAA6B,gCAAA;AACnC,IAAM,yBAAA,GAA4B,gCAAA;AAQlC,IAAM,wBAAA,GAAuD;AAAA,EAC3D,EAAA,EAAI,EAAA;AAAA,EACJ,EAAA,EAAI,EAAA;AAAA,EACJ,EAAA,EAAI,EAAA;AAAA,EACJ,EAAA,EAAI,EAAA;AAAA,EACJ,IAAA,EAAM;AACR,CAAA;AAGA,SAAS,wBAAA,CAAyB,MAAe,YAAA,EAAsB;AACrE,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAI,SAAS,IAAI,CAAA;AACrD,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAAS,KAAK,CAAA;AAEpD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,eAAA,CAAgB,IAAI,CAAA;AAAA,IACtB,CAAA,MAAO;AACL,MAAA,cAAA,CAAe,KAAK,CAAA;AACpB,MAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,eAAA,CAAgB,KAAK,GAAG,YAAY,CAAA;AACnE,MAAA,OAAO,MAAM,aAAa,KAAK,CAAA;AAAA,IACjC;AAAA,EACF,CAAA,EAAG,CAAC,IAAA,EAAM,YAAY,CAAC,CAAA;AAEvB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,YAAA,IAAgB,CAAC,IAAA,EAAM;AAE5B,IAAA,IAAI,IAAA,GAAO,CAAA;AACX,IAAA,MAAM,IAAA,GAAO,sBAAsB,MAAM;AACvC,MAAA,IAAA,GAAO,qBAAA,CAAsB,MAAM,cAAA,CAAe,IAAI,CAAC,CAAA;AAAA,IACzD,CAAC,CAAA;AACD,IAAA,OAAO,MAAM;AACX,MAAA,oBAAA,CAAqB,IAAI,CAAA;AACzB,MAAA,IAAI,IAAA,uBAA2B,IAAI,CAAA;AAAA,IACrC,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,YAAA,EAAc,IAAI,CAAC,CAAA;AAEvB,EAAA,OAAO,EAAE,cAAc,WAAA,EAAY;AACrC;AAIA,IAAM,aAAA,GAAgB,KAAA,CAAM,aAAA,CAM1B,MAAS,CAAA;AAEX,SAAS,SAAA,GAAY;AACnB,EAAA,MAAM,OAAA,GAAU,KAAA,CAAM,UAAA,CAAW,aAAa,CAAA;AAC9C,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAI,MAAM,oDAAoD,CAAA;AAAA,EACtE;AACA,EAAA,OAAO,OAAA;AACT;AAUO,IAAM,SAAgC,CAAC;AAAA,EAC5C,QAAA;AAAA,EACA,IAAA,EAAM,cAAA;AAAA,EACN;AACF,CAAA,KAAM;AACJ,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAI,SAAS,KAAK,CAAA;AACtD,EAAA,MAAM,eAAe,cAAA,KAAmB,MAAA;AACxC,EAAA,MAAM,IAAA,GAAO,eAAe,cAAA,GAAiB,YAAA;AAE7C,EAAA,MAAM,UAAU,KAAA,CAAM,WAAA;AAAA,IACpB,CAAC,KAAA,KAAmB;AAClB,MAAA,IAAI,CAAC,YAAA,EAAc;AACjB,QAAA,eAAA,CAAgB,KAAK,CAAA;AAAA,MACvB;AACA,MAAA,YAAA,GAAe,KAAK,CAAA;AAAA,IACtB,CAAA;AAAA,IACA,CAAC,cAAc,YAAY;AAAA,GAC7B;AAEA,EAAA,uBACE,GAAA,CAAC,cAAc,QAAA,EAAd,EAAuB,OAAO,EAAE,IAAA,EAAM,OAAA,EAAQ,EAC5C,QAAA,EACH,CAAA;AAEJ;AAEO,IAAM,aAAA,GAGR,CAAC,EAAE,QAAA,EAAU,SAAQ,KAAM;AAC9B,EAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,SAAA,EAAU;AAE9B,EAAA,MAAM,WAAA,GAAc,MAAM,OAAA,CAAQ,IAAI,CAAA;AAEtC,EAAA,IAAI,OAAA,IAAW,KAAA,CAAM,cAAA,CAAe,QAAQ,CAAA,EAAG;AAC7C,IAAA,MAAM,KAAA,GAAQ,QAAA;AAGd,IAAA,OAAO,KAAA,CAAM,aAAa,KAAA,EAAO;AAAA,MAC/B,OAAA,EAAS,CAAC,CAAA,KAAwB;AAChC,QAAA,KAAA,CAAM,KAAA,CAAM,UAAU,CAAC,CAAA;AACvB,QAAA,WAAA,EAAY;AAAA,MACd;AAAA,KACD,CAAA;AAAA,EACH;AAEA,EAAA,2BACG,QAAA,EAAA,EAAO,IAAA,EAAK,QAAA,EAAS,OAAA,EAAS,aAC5B,QAAA,EACH,CAAA;AAEJ;AAEO,IAAM,WAAA,GAGR,CAAC,EAAE,QAAA,EAAU,SAAQ,KAAM;AAC9B,EAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,SAAA,EAAU;AAE9B,EAAA,MAAM,WAAA,GAAc,MAAM,OAAA,CAAQ,KAAK,CAAA;AAEvC,EAAA,IAAI,OAAA,IAAW,KAAA,CAAM,cAAA,CAAe,QAAQ,CAAA,EAAG;AAC7C,IAAA,MAAM,KAAA,GAAQ,QAAA;AAGd,IAAA,OAAO,KAAA,CAAM,aAAa,KAAA,EAAO;AAAA,MAC/B,OAAA,EAAS,CAAC,CAAA,KAAwB;AAChC,QAAA,KAAA,CAAM,KAAA,CAAM,UAAU,CAAC,CAAA;AACvB,QAAA,WAAA,EAAY;AAAA,MACd;AAAA,KACD,CAAA;AAAA,EACH;AAEA,EAAA,2BACG,QAAA,EAAA,EAAO,IAAA,EAAK,QAAA,EAAS,OAAA,EAAS,aAC5B,QAAA,EACH,CAAA;AAEJ;AAWO,IAAM,iBAAA,GAAoB,KAAA,CAAM,UAAA,CAGrC,CAAC,EAAE,SAAA,EAAW,IAAA,GAAO,QAAA,EAAU,OAAA,EAAS,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AAC5D,EAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,SAAA,EAAU;AAC9B,EAAA,MAAM,WAAA,GAAc,WAAA;AAAA,IAClB,CAAC,CAAA,KAA2C;AAC1C,MAAA,OAAA,GAAU,CAAC,CAAA;AACX,MAAA,IAAI,WAAW,IAAA,EAAM;AACnB,QAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,MACf;AAAA,IACF,CAAA;AAAA,IACA,CAAC,SAAS,OAAO;AAAA,GACnB;AAEA,EAAA,uBACE,IAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,IAAA;AAAA,MACA,SAAA,EAAW,EAAA;AAAA,QACT,6IAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,OAAA,EAAS,WAAA;AAAA,MACR,GAAG,KAAA;AAAA,MAEJ,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,CAAA,EAAA,EAAE,WAAU,UAAA,EAAW,CAAA;AAAA,wBACxB,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,SAAA,EAAU,QAAA,EAAA,OAAA,EAAK;AAAA;AAAA;AAAA,GACjC;AAEJ,CAAC;AACD,iBAAA,CAAkB,WAAA,GAAc,mBAAA;AAkBzB,IAAM,gBAA8C,CAAC;AAAA,EAC1D,QAAA;AAAA,EACA,IAAA,GAAO,IAAA;AAAA,EACP,SAAA;AAAA,EACA,QAAA,GAAW,GAAA;AAAA,EACX,mBAAA,GAAsB,IAAA;AAAA,EACtB,SAAA,GAAY,IAAA;AAAA,EACZ,aAAA,GAAgB,KAAA;AAAA,EAChB,qBAAA,EAAuB,yBAAA;AAAA,EACvB,GAAG;AACL,CAAA,KAAM;AACJ,EAAA,MAAM,EAAE,IAAA,EAAM,OAAA,EAAQ,GAAI,SAAA,EAAU;AAEpC,EAAA,MAAM,aAAA,GACJ,yBAAA,IAA6B,wBAAA,CAAyB,IAAI,CAAA;AAE5D,EAAA,MAAM,YAAA,GAAe,gBAAgB,gBAAA,GAAmB,QAAA;AACxD,EAAA,MAAM,EAAE,YAAA,EAAc,WAAA,EAAY,GAAI,wBAAA;AAAA,IACpC,IAAA;AAAA,IACA;AAAA,GACF;AAGA,EAAA,MAAM,iBAAA,GAAoB,OACtB,0BAAA,GACA,yBAAA;AAEJ,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,SAAA,GAAY,CAAC,CAAA,KAAqB;AACtC,MAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,QAAA,EAAU,OAAA,CAAQ,KAAK,CAAA;AAAA,IACvC,CAAA;AACA,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,IAAI,OAAO,aAAa,WAAA,EAAa;AACnC,QAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,SAAS,CAAA;AAC5C,QAAA,QAAA,CAAS,IAAA,CAAK,MAAM,QAAA,GAAW,QAAA;AAAA,MACjC;AAAA,IACF;AACA,IAAA,OAAO,MAAM;AACX,MAAA,IAAI,OAAO,aAAa,WAAA,EAAa;AACnC,QAAA,MAAA,CAAO,mBAAA,CAAoB,WAAW,SAAS,CAAA;AAC/C,QAAA,QAAA,CAAS,IAAA,CAAK,MAAM,QAAA,GAAW,EAAA;AAAA,MACjC;AAAA,IACF,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,IAAA,EAAM,OAAO,CAAC,CAAA;AAElB,EAAA,IAAI,CAAC,cAAc,OAAO,IAAA;AAE1B,EAAA,MAAM,WAAA,GAAyD;AAAA,IAC7D,EAAA,EAAI,aAAA;AAAA,IACJ,EAAA,EAAI,aAAA;AAAA,IACJ,EAAA,EAAI,aAAA;AAAA,IACJ,EAAA,EAAI;AAAA,GACN;AAEA,EAAA,OAAO,YAAA;AAAA,oBACL,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAW,EAAA;AAAA,UACT,yBAAA;AAAA,UACA,IAAA,KAAS,SACL,2BAAA,GACA;AAAA,SACN;AAAA,QAGA,QAAA,EAAA;AAAA,0BAAA,GAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cACC,SAAA,EAAW,EAAA;AAAA,gBACT,4CAAA;AAAA,gBACA,CAAC,aAAA,IAAiB,gCAAA;AAAA,gBAClB,cAAc,aAAA,GAAgB;AAAA,eAChC;AAAA,cACA,KAAA,EAAO;AAAA,gBACL,kBAAA,EAAoB,GAAG,YAAY,CAAA,EAAA,CAAA;AAAA,gBACnC,GAAI,aAAA,GACA;AAAA,kBACE,kBAAA,EAAoB,SAAA;AAAA,kBACpB,wBAAA,EAA0B;AAAA,oBAE5B;AAAC,eACP;AAAA,cACA,OAAA,EAAS,MAAM,mBAAA,IAAuB,OAAA,CAAQ,KAAK;AAAA;AAAA,WACrD;AAAA,0BAGA,IAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cACE,GAAG,KAAA;AAAA,cACJ,SAAA,EAAW,EAAA;AAAA,gBACT,+DAAA;AAAA,gBACA,SAAS,MAAA,GACL,sDAAA,GACA,GAAG,sCAAA,EAAwC,WAAA,CAAY,IAAI,CAAC,CAAA;AAAA,gBAChE,CAAC,aAAA,IACC,EAAA;AAAA,kBACE,4BAAA;AAAA,kBACA,cAAc,uBAAA,GAA0B;AAAA,iBAC1C;AAAA,gBACF;AAAA,eACF;AAAA,cACA,KAAA,EAAO;AAAA,gBACL,GAAG,KAAA,CAAM,KAAA;AAAA,gBACT,GAAI,aAAA,GACA;AAAA,kBACE,SAAA,EAAW,WAAA,GACP,eAAA,GACA,CAAA,WAAA,EAAc,aAAa,CAAA,GAAA,CAAA;AAAA,kBAC/B,OAAA,EAAS,cAAc,CAAA,GAAI,CAAA;AAAA,kBAC3B,kBAAA,EAAoB,oBAAA;AAAA,kBACpB,kBAAA,EAAoB,GAAG,gBAAgB,CAAA,EAAA,CAAA;AAAA,kBACvC,wBAAA,EAA0B;AAAA,iBAC5B,GACA;AAAA,kBACE,kBAAA,EAAoB,GAAG,QAAQ,CAAA,EAAA;AAAA;AACjC,eACN;AAAA,cAEC,QAAA,EAAA;AAAA,gBAAA,QAAA;AAAA,gBAEA,SAAA,oBACC,GAAA;AAAA,kBAAC,iBAAA;AAAA,kBAAA;AAAA,oBACC,SAAA,EAAW,GAAG,4CAA4C;AAAA;AAAA;AAC5D;AAAA;AAAA;AAEJ;AAAA;AAAA,KACF;AAAA,IACA,QAAA,CAAS;AAAA,GACX;AACF;AAEO,IAAM,eAET,CAAC,EAAE,WAAW,KAAA,EAAO,GAAG,OAAM,KAAM;AACtC,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,EAAA;AAAA,QACT,wDAAA;AAAA,QACA,KAAA,IAAS,4DAAA;AAAA,QACT;AAAA,OACF;AAAA,MACC,GAAG;AAAA;AAAA,GACN;AAEJ;AAEO,IAAM,eAET,CAAC,EAAE,WAAW,KAAA,EAAO,GAAG,OAAM,KAAM;AACtC,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,EAAA;AAAA,QACT,mEAAA;AAAA,QACA,KAAA,IACE,+DAAA;AAAA,QACF;AAAA,OACF;AAAA,MACC,GAAG;AAAA;AAAA,GACN;AAEJ;AAEO,IAAM,cAET,CAAC,EAAE,SAAA,EAAW,GAAG,OAAM,KAAM;AAC/B,EAAA,uBACE,GAAA;AAAA,IAAC,IAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,EAAA;AAAA,QACT,iDAAA;AAAA,QACA;AAAA,OACF;AAAA,MACC,GAAG;AAAA;AAAA,GACN;AAEJ;AAEO,IAAM,oBAET,CAAC,EAAE,SAAA,EAAW,GAAG,OAAM,KAAM;AAC/B,EAAA,uBACE,GAAA,CAAC,OAAE,SAAA,EAAW,EAAA,CAAG,iCAAiC,SAAS,CAAA,EAAI,GAAG,KAAA,EAAO,CAAA;AAE7E","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,10 +1,28 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
+
type ScrollHeaderRevealAt = "start" | "center" | "end";
|
|
3
4
|
interface ScrollHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
5
|
children: ReactNode;
|
|
6
|
+
/**
|
|
7
|
+
* Which part of `ScrollHeaderTop` must leave the scrollport before the sticky bar shows.
|
|
8
|
+
* `start` — top edge (earliest). `center` — vertical midpoint. `end` — bottom edge (latest).
|
|
9
|
+
* If omitted: defaults to `center` when `slideIn` is true, and `start` when `slideIn` is false (in-flow bar).
|
|
10
|
+
*/
|
|
11
|
+
revealAt?: ScrollHeaderRevealAt;
|
|
12
|
+
/**
|
|
13
|
+
* When `true` (default), `ScrollHeaderSticky` overlays without layout height and slides in with opacity.
|
|
14
|
+
* When `false`, the sticky bar stays in document flow (takes vertical space); use `onRevealChange` and
|
|
15
|
+
* your own styles for any fade or toolbar behavior.
|
|
16
|
+
*/
|
|
17
|
+
slideIn?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Called when the sticky header reveal state changes after scroll.
|
|
20
|
+
* `true` once the top section has crossed the reveal threshold (same moment as `data-scrolled="true"`).
|
|
21
|
+
*/
|
|
22
|
+
onRevealChange?: (revealed: boolean) => void;
|
|
5
23
|
}
|
|
6
24
|
declare const ScrollHeader: React.FC<ScrollHeaderProps>;
|
|
7
25
|
declare const ScrollHeaderTop: React.FC<React.HTMLAttributes<HTMLDivElement>>;
|
|
8
26
|
declare const ScrollHeaderSticky: React.FC<React.HTMLAttributes<HTMLDivElement>>;
|
|
9
27
|
|
|
10
|
-
export { ScrollHeader, ScrollHeaderSticky, ScrollHeaderTop };
|
|
28
|
+
export { ScrollHeader, type ScrollHeaderRevealAt, ScrollHeaderSticky, ScrollHeaderTop };
|
|
@@ -1,79 +1,168 @@
|
|
|
1
1
|
import { clsx } from 'clsx';
|
|
2
2
|
import { twMerge } from 'tailwind-merge';
|
|
3
|
-
import { useRef, useEffect } from 'react';
|
|
4
|
-
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { createContext, useRef, useContext, useEffect, Children, isValidElement } from 'react';
|
|
4
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
6
|
function cn(...inputs) {
|
|
7
7
|
return twMerge(clsx(inputs));
|
|
8
8
|
}
|
|
9
9
|
var DATA_ATTR = "data-scrolled";
|
|
10
|
+
var ScrollHeaderContext = createContext({
|
|
11
|
+
revealAt: "center",
|
|
12
|
+
slideIn: true,
|
|
13
|
+
onRevealChange: void 0
|
|
14
|
+
});
|
|
15
|
+
function isScrollHeaderStickyEl(child) {
|
|
16
|
+
return child.type.displayName === "ScrollHeaderSticky";
|
|
17
|
+
}
|
|
18
|
+
function isScrollHeaderTopEl(child) {
|
|
19
|
+
return child.type.displayName === "ScrollHeaderTop";
|
|
20
|
+
}
|
|
21
|
+
function reorderScrollHeaderChildren(children) {
|
|
22
|
+
const sticky = [];
|
|
23
|
+
const top = [];
|
|
24
|
+
const rest = [];
|
|
25
|
+
Children.forEach(children, (child) => {
|
|
26
|
+
if (!isValidElement(child)) {
|
|
27
|
+
if (child != null && child !== false) rest.push(child);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (isScrollHeaderStickyEl(child)) sticky.push(child);
|
|
31
|
+
else if (isScrollHeaderTopEl(child)) top.push(child);
|
|
32
|
+
else rest.push(child);
|
|
33
|
+
});
|
|
34
|
+
return [...sticky, ...top, ...rest];
|
|
35
|
+
}
|
|
10
36
|
var ScrollHeader = ({
|
|
37
|
+
revealAt,
|
|
38
|
+
slideIn = true,
|
|
39
|
+
onRevealChange,
|
|
11
40
|
children,
|
|
12
41
|
className,
|
|
13
42
|
...props
|
|
14
43
|
}) => {
|
|
15
44
|
const ref = useRef(null);
|
|
45
|
+
const resolvedRevealAt = revealAt ?? (slideIn ? "center" : "start");
|
|
16
46
|
return /* @__PURE__ */ jsx(
|
|
17
|
-
|
|
47
|
+
ScrollHeaderContext.Provider,
|
|
18
48
|
{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
49
|
+
value: {
|
|
50
|
+
revealAt: resolvedRevealAt,
|
|
51
|
+
slideIn,
|
|
52
|
+
onRevealChange
|
|
53
|
+
},
|
|
54
|
+
children: /* @__PURE__ */ jsx(
|
|
55
|
+
"div",
|
|
56
|
+
{
|
|
57
|
+
ref,
|
|
58
|
+
"data-scrolled": "false",
|
|
59
|
+
className: cn(
|
|
60
|
+
"scroll-header bg-background flex h-full flex-col overflow-y-auto",
|
|
61
|
+
className
|
|
62
|
+
),
|
|
63
|
+
...props,
|
|
64
|
+
children: reorderScrollHeaderChildren(children)
|
|
65
|
+
}
|
|
66
|
+
)
|
|
27
67
|
}
|
|
28
68
|
);
|
|
29
69
|
};
|
|
70
|
+
var sentinelClass = "pointer-events-none h-px w-full shrink-0";
|
|
30
71
|
var ScrollHeaderTop = ({
|
|
31
72
|
children,
|
|
32
73
|
className,
|
|
33
74
|
...props
|
|
34
75
|
}) => {
|
|
35
|
-
const
|
|
76
|
+
const { revealAt, onRevealChange } = useContext(ScrollHeaderContext);
|
|
77
|
+
const sentinelRef = useRef(null);
|
|
78
|
+
const onRevealChangeRef = useRef(onRevealChange);
|
|
79
|
+
onRevealChangeRef.current = onRevealChange;
|
|
80
|
+
const lastRevealedRef = useRef(void 0);
|
|
36
81
|
useEffect(() => {
|
|
37
|
-
const
|
|
38
|
-
if (!
|
|
39
|
-
const container =
|
|
82
|
+
const sentinel = sentinelRef.current;
|
|
83
|
+
if (!sentinel) return;
|
|
84
|
+
const container = sentinel.closest("[data-scrolled]");
|
|
40
85
|
if (!container) return;
|
|
86
|
+
lastRevealedRef.current = void 0;
|
|
41
87
|
const observer = new IntersectionObserver(
|
|
42
88
|
([entry]) => {
|
|
43
89
|
if (!entry) return;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
90
|
+
const revealed = !entry.isIntersecting;
|
|
91
|
+
container.setAttribute(DATA_ATTR, revealed ? "true" : "false");
|
|
92
|
+
if (lastRevealedRef.current !== revealed) {
|
|
93
|
+
lastRevealedRef.current = revealed;
|
|
94
|
+
onRevealChangeRef.current?.(revealed);
|
|
95
|
+
}
|
|
48
96
|
},
|
|
49
|
-
{
|
|
50
|
-
root: container,
|
|
51
|
-
threshold: 0
|
|
52
|
-
}
|
|
97
|
+
{ root: container, threshold: 0 }
|
|
53
98
|
);
|
|
54
|
-
observer.observe(
|
|
99
|
+
observer.observe(sentinel);
|
|
55
100
|
return () => observer.disconnect();
|
|
56
|
-
}, []);
|
|
57
|
-
|
|
101
|
+
}, [revealAt]);
|
|
102
|
+
if (revealAt === "end") {
|
|
103
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("shrink-0", className), ...props, children: [
|
|
104
|
+
children,
|
|
105
|
+
/* @__PURE__ */ jsx("div", { ref: sentinelRef, className: sentinelClass, "aria-hidden": true })
|
|
106
|
+
] });
|
|
107
|
+
}
|
|
108
|
+
if (revealAt === "center") {
|
|
109
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("relative shrink-0", className), ...props, children: [
|
|
110
|
+
/* @__PURE__ */ jsx(
|
|
111
|
+
"div",
|
|
112
|
+
{
|
|
113
|
+
ref: sentinelRef,
|
|
114
|
+
className: cn(
|
|
115
|
+
sentinelClass,
|
|
116
|
+
"absolute left-0 right-0 top-1/2 z-0 -translate-y-1/2"
|
|
117
|
+
),
|
|
118
|
+
"aria-hidden": true
|
|
119
|
+
}
|
|
120
|
+
),
|
|
121
|
+
children
|
|
122
|
+
] });
|
|
123
|
+
}
|
|
124
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("shrink-0", className), ...props, children: [
|
|
125
|
+
/* @__PURE__ */ jsx("div", { ref: sentinelRef, className: sentinelClass, "aria-hidden": true }),
|
|
126
|
+
children
|
|
127
|
+
] });
|
|
58
128
|
};
|
|
129
|
+
ScrollHeaderTop.displayName = "ScrollHeaderTop";
|
|
130
|
+
var stickyChromeBgClass = "bg-background supports-backdrop-filter:bg-background";
|
|
131
|
+
var stickyChromeBorderClass = "border-primary/10 border-b";
|
|
132
|
+
var stickyChromeClass = cn(stickyChromeBgClass, stickyChromeBorderClass);
|
|
133
|
+
var revealEase = "duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:duration-0 motion-reduce:transition-none";
|
|
59
134
|
var ScrollHeaderSticky = ({
|
|
60
135
|
children,
|
|
61
136
|
className,
|
|
62
137
|
...props
|
|
63
|
-
}) =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
138
|
+
}) => {
|
|
139
|
+
const { slideIn } = useContext(ScrollHeaderContext);
|
|
140
|
+
return /* @__PURE__ */ jsx(
|
|
141
|
+
"div",
|
|
142
|
+
{
|
|
143
|
+
className: cn(
|
|
144
|
+
"sticky top-0 z-40",
|
|
145
|
+
slideIn ? "h-0 overflow-visible" : "shrink-0"
|
|
146
|
+
),
|
|
147
|
+
children: slideIn ? /* @__PURE__ */ jsx(
|
|
148
|
+
"div",
|
|
149
|
+
{
|
|
150
|
+
className: cn(
|
|
151
|
+
stickyChromeClass,
|
|
152
|
+
"transition-[opacity, transform]",
|
|
153
|
+
revealEase,
|
|
154
|
+
'[.scroll-header:not([data-scrolled="true"])_&]:pointer-events-none [.scroll-header:not([data-scrolled="true"])_&]:-translate-y-1 [.scroll-header:not([data-scrolled="true"])_&]:opacity-0',
|
|
155
|
+
'[.scroll-header[data-scrolled="true"]_&]:translate-y-0 [.scroll-header[data-scrolled="true"]_&]:opacity-100',
|
|
156
|
+
className
|
|
157
|
+
),
|
|
158
|
+
...props,
|
|
159
|
+
children
|
|
160
|
+
}
|
|
161
|
+
) : /* @__PURE__ */ jsx("div", { className: stickyChromeBgClass, children: /* @__PURE__ */ jsx("div", { className: cn(stickyChromeBorderClass, className), ...props, children }) })
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
ScrollHeaderSticky.displayName = "ScrollHeaderSticky";
|
|
77
166
|
|
|
78
167
|
export { ScrollHeader, ScrollHeaderSticky, ScrollHeaderTop };
|
|
79
168
|
//# sourceMappingURL=scroll-header.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/utils.ts","../../src/react/scroll-header.tsx"],"names":[],"mappings":";;;;;AAGO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACAA,IAAM,SAAA,GAAY,eAAA;AAMlB,IAAM,eAA4C,CAAC;AAAA,EACjD,QAAA;AAAA,EACA,SAAA;AAAA,EACA,GAAG;AACL,CAAA,KAAM;AACJ,EAAA,MAAM,GAAA,GAAM,OAAuB,IAAI,CAAA;AAEvC,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,eAAA,EAAc,OAAA;AAAA,MACd,SAAA,EAAW,EAAA;AAAA,QACT,kEAAA;AAAA,QACA;AAAA,OACF;AAAA,MACC,GAAG,KAAA;AAAA,MAEH;AAAA;AAAA,GACH;AAEJ;AAEA,IAAM,kBAAkE,CAAC;AAAA,EACvE,QAAA;AAAA,EACA,SAAA;AAAA,EACA,GAAG;AACL,CAAA,KAAM;AACJ,EAAA,MAAM,GAAA,GAAM,OAAuB,IAAI,CAAA;AAEvC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,KAAK,GAAA,CAAI,OAAA;AACf,IAAA,IAAI,CAAC,EAAA,EAAI;AAET,IAAA,MAAM,SAAA,GAAY,EAAA,CAAG,OAAA,CAAqB,iBAAiB,CAAA;AAC3D,IAAA,IAAI,CAAC,SAAA,EAAW;AAEhB,IAAA,MAAM,WAAW,IAAI,oBAAA;AAAA,MACnB,CAAC,CAAC,KAAK,CAAA,KAAM;AACX,QAAA,IAAI,CAAC,KAAA,EAAO;AACZ,QAAA,SAAA,CAAU,YAAA;AAAA,UACR,SAAA;AAAA,UACA,KAAA,CAAM,iBAAiB,OAAA,GAAU;AAAA,SACnC;AAAA,MACF,CAAA;AAAA,MACA;AAAA,QACE,IAAA,EAAM,SAAA;AAAA,QACN,SAAA,EAAW;AAAA;AACb,KACF;AAEA,IAAA,QAAA,CAAS,QAAQ,EAAE,CAAA;AACnB,IAAA,OAAO,MAAM,SAAS,UAAA,EAAW;AAAA,EACnC,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAU,SAAA,EAAW,EAAA,CAAG,YAAY,SAAS,CAAA,EAAI,GAAG,KAAA,EACtD,QAAA,EACH,CAAA;AAEJ;AAEA,IAAM,qBAAqE,CAAC;AAAA,EAC1E,QAAA;AAAA,EACA,SAAA;AAAA,EACA,GAAG;AACL,CAAA,qBACE,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,wCAAA,EACb,QAAA,kBAAA,GAAA;AAAA,EAAC,KAAA;AAAA,EAAA;AAAA,IACC,SAAA,EAAW,EAAA;AAAA,MACT,gFAAA;AAAA,MACA,uIAAA;AAAA,MACA,2LAAA;AAAA,MACA,6GAAA;AAAA,MACA;AAAA,KACF;AAAA,IACC,GAAG,KAAA;AAAA,IAEH;AAAA;AACH,CAAA,EACF","file":"scroll-header.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 { cn } from \"@/lib/utils\";\nimport { ReactNode, useEffect, useRef } from \"react\";\n\nconst DATA_ATTR = \"data-scrolled\";\n\ninterface ScrollHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n}\n\nconst ScrollHeader: React.FC<ScrollHeaderProps> = ({\n children,\n className,\n ...props\n}) => {\n const ref = useRef<HTMLDivElement>(null);\n\n return (\n <div\n ref={ref}\n data-scrolled=\"false\"\n className={cn(\n \"scroll-header bg-background flex h-full flex-col overflow-y-auto\",\n className,\n )}\n {...props}\n >\n {children}\n </div>\n );\n};\n\nconst ScrollHeaderTop: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({\n children,\n className,\n ...props\n}) => {\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const container = el.closest<HTMLElement>(\"[data-scrolled]\");\n if (!container) return;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry) return;\n container.setAttribute(\n DATA_ATTR,\n entry.isIntersecting ? \"false\" : \"true\",\n );\n },\n {\n root: container,\n threshold: 0,\n },\n );\n\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n return (\n <div ref={ref} className={cn(\"shrink-0\", className)} {...props}>\n {children}\n </div>\n );\n};\n\nconst ScrollHeaderSticky: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({\n children,\n className,\n ...props\n}) => (\n <div className=\"sticky top-0 z-40 h-0 overflow-visible\">\n <div\n className={cn(\n \"border-border/20 bg-background supports-backdrop-filter:bg-background border-b\",\n \"transition-[opacity,transform] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:duration-0 motion-reduce:transition-none\",\n '[.scroll-header:not([data-scrolled=\"true\"])_&]:pointer-events-none [.scroll-header:not([data-scrolled=\"true\"])_&]:-translate-y-2 [.scroll-header:not([data-scrolled=\"true\"])_&]:opacity-0',\n '[.scroll-header[data-scrolled=\"true\"]_&]:translate-y-0 [.scroll-header[data-scrolled=\"true\"]_&]:opacity-100',\n className,\n )}\n {...props}\n >\n {children}\n </div>\n </div>\n);\n\nexport { ScrollHeader, ScrollHeaderSticky, ScrollHeaderTop };\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/lib/utils.ts","../../src/react/scroll-header.tsx"],"names":[],"mappings":"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","file":"scroll-header.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 { cn } from \"@/lib/utils\";\nimport {\n Children,\n createContext,\n isValidElement,\n ReactElement,\n ReactNode,\n useContext,\n useEffect,\n useRef,\n} from \"react\";\n\nconst DATA_ATTR = \"data-scrolled\";\n\nexport type ScrollHeaderRevealAt = \"start\" | \"center\" | \"end\";\n\ninterface ScrollHeaderContextValue {\n revealAt: ScrollHeaderRevealAt;\n slideIn: boolean;\n onRevealChange?: (revealed: boolean) => void;\n}\n\nconst ScrollHeaderContext = createContext<ScrollHeaderContextValue>({\n revealAt: \"center\",\n slideIn: true,\n onRevealChange: undefined,\n});\n\nfunction isScrollHeaderStickyEl(child: ReactElement): boolean {\n return (\n (child.type as { displayName?: string }).displayName ===\n \"ScrollHeaderSticky\"\n );\n}\n\nfunction isScrollHeaderTopEl(child: ReactElement): boolean {\n return (\n (child.type as { displayName?: string }).displayName === \"ScrollHeaderTop\"\n );\n}\n\n/** Sticky first so `position: sticky; top: 0` pins to the scrollport, not below the hero. */\nfunction reorderScrollHeaderChildren(children: ReactNode): ReactNode[] {\n const sticky: ReactNode[] = [];\n const top: ReactNode[] = [];\n const rest: ReactNode[] = [];\n\n Children.forEach(children, (child) => {\n if (!isValidElement(child)) {\n if (child != null && child !== false) rest.push(child);\n return;\n }\n if (isScrollHeaderStickyEl(child)) sticky.push(child);\n else if (isScrollHeaderTopEl(child)) top.push(child);\n else rest.push(child);\n });\n\n return [...sticky, ...top, ...rest];\n}\n\ninterface ScrollHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n /**\n * Which part of `ScrollHeaderTop` must leave the scrollport before the sticky bar shows.\n * `start` — top edge (earliest). `center` — vertical midpoint. `end` — bottom edge (latest).\n * If omitted: defaults to `center` when `slideIn` is true, and `start` when `slideIn` is false (in-flow bar).\n */\n revealAt?: ScrollHeaderRevealAt;\n /**\n * When `true` (default), `ScrollHeaderSticky` overlays without layout height and slides in with opacity.\n * When `false`, the sticky bar stays in document flow (takes vertical space); use `onRevealChange` and\n * your own styles for any fade or toolbar behavior.\n */\n slideIn?: boolean;\n /**\n * Called when the sticky header reveal state changes after scroll.\n * `true` once the top section has crossed the reveal threshold (same moment as `data-scrolled=\"true\"`).\n */\n onRevealChange?: (revealed: boolean) => void;\n}\n\nconst ScrollHeader: React.FC<ScrollHeaderProps> = ({\n revealAt,\n slideIn = true,\n onRevealChange,\n children,\n className,\n ...props\n}) => {\n const ref = useRef<HTMLDivElement>(null);\n const resolvedRevealAt = revealAt ?? (slideIn ? \"center\" : \"start\");\n\n return (\n <ScrollHeaderContext.Provider\n value={{\n revealAt: resolvedRevealAt,\n slideIn,\n onRevealChange,\n }}\n >\n <div\n ref={ref}\n data-scrolled=\"false\"\n className={cn(\n \"scroll-header bg-background flex h-full flex-col overflow-y-auto\",\n className,\n )}\n {...props}\n >\n {reorderScrollHeaderChildren(children)}\n </div>\n </ScrollHeaderContext.Provider>\n );\n};\n\nconst sentinelClass = \"pointer-events-none h-px w-full shrink-0\";\n\nconst ScrollHeaderTop: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({\n children,\n className,\n ...props\n}) => {\n const { revealAt, onRevealChange } = useContext(ScrollHeaderContext);\n const sentinelRef = useRef<HTMLDivElement>(null);\n const onRevealChangeRef = useRef(onRevealChange);\n onRevealChangeRef.current = onRevealChange;\n const lastRevealedRef = useRef<boolean | undefined>(undefined);\n\n useEffect(() => {\n const sentinel = sentinelRef.current;\n if (!sentinel) return;\n\n const container = sentinel.closest<HTMLElement>(\"[data-scrolled]\");\n if (!container) return;\n\n lastRevealedRef.current = undefined;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry) return;\n const revealed = !entry.isIntersecting;\n container.setAttribute(DATA_ATTR, revealed ? \"true\" : \"false\");\n if (lastRevealedRef.current !== revealed) {\n lastRevealedRef.current = revealed;\n onRevealChangeRef.current?.(revealed);\n }\n },\n { root: container, threshold: 0 },\n );\n\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [revealAt]);\n\n if (revealAt === \"end\") {\n return (\n <div className={cn(\"shrink-0\", className)} {...props}>\n {children}\n <div ref={sentinelRef} className={sentinelClass} aria-hidden />\n </div>\n );\n }\n\n if (revealAt === \"center\") {\n return (\n <div className={cn(\"relative shrink-0\", className)} {...props}>\n <div\n ref={sentinelRef}\n className={cn(\n sentinelClass,\n \"absolute left-0 right-0 top-1/2 z-0 -translate-y-1/2\",\n )}\n aria-hidden\n />\n {children}\n </div>\n );\n }\n\n return (\n <div className={cn(\"shrink-0\", className)} {...props}>\n <div ref={sentinelRef} className={sentinelClass} aria-hidden />\n {children}\n </div>\n );\n};\n\nScrollHeaderTop.displayName = \"ScrollHeaderTop\";\n\nconst stickyChromeBgClass =\n \"bg-background supports-backdrop-filter:bg-background\";\n\nconst stickyChromeBorderClass = \"border-primary/10 border-b\";\n\nconst stickyChromeClass = cn(stickyChromeBgClass, stickyChromeBorderClass);\n\nconst revealEase =\n \"duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:duration-0 motion-reduce:transition-none\";\n\nconst ScrollHeaderSticky: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({\n children,\n className,\n ...props\n}) => {\n const { slideIn } = useContext(ScrollHeaderContext);\n\n return (\n <div\n className={cn(\n \"sticky top-0 z-40\",\n slideIn ? \"h-0 overflow-visible\" : \"shrink-0\",\n )}\n >\n {slideIn ? (\n <div\n className={cn(\n stickyChromeClass,\n \"transition-[opacity, transform]\",\n revealEase,\n '[.scroll-header:not([data-scrolled=\"true\"])_&]:pointer-events-none [.scroll-header:not([data-scrolled=\"true\"])_&]:-translate-y-1 [.scroll-header:not([data-scrolled=\"true\"])_&]:opacity-0',\n '[.scroll-header[data-scrolled=\"true\"]_&]:translate-y-0 [.scroll-header[data-scrolled=\"true\"]_&]:opacity-100',\n className,\n )}\n {...props}\n >\n {children}\n </div>\n ) : (\n <div className={stickyChromeBgClass}>\n <div className={cn(stickyChromeBorderClass, className)} {...props}>\n {children}\n </div>\n </div>\n )}\n </div>\n );\n};\n\nScrollHeaderSticky.displayName = \"ScrollHeaderSticky\";\n\nexport { ScrollHeader, ScrollHeaderSticky, ScrollHeaderTop };\n"]}
|
package/dist/react/sheet.d.ts
CHANGED
|
@@ -22,6 +22,20 @@ declare const SheetClose: React__default.FC<{
|
|
|
22
22
|
asChild?: boolean;
|
|
23
23
|
afterClose?: () => void;
|
|
24
24
|
}>;
|
|
25
|
+
type SheetCloseButtonSide = "top" | "right" | "bottom" | "left";
|
|
26
|
+
interface SheetCloseButtonProps extends React__default.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
27
|
+
/**
|
|
28
|
+
* Desktop offset matches {@link SheetContent} for the same `side`; default
|
|
29
|
+
* `right` matches `SheetContent` default.
|
|
30
|
+
*/
|
|
31
|
+
side?: SheetCloseButtonSide;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Default dismiss control for {@link SheetContent}. Includes absolute placement
|
|
35
|
+
* for the given `side`; pass `className` to adjust or replace positioning.
|
|
36
|
+
* When `onClick` is omitted, uses sheet context to call `setOpen(false)`.
|
|
37
|
+
*/
|
|
38
|
+
declare const SheetCloseButton: React__default.ForwardRefExoticComponent<SheetCloseButtonProps & React__default.RefAttributes<HTMLButtonElement>>;
|
|
25
39
|
interface SheetContentProps extends React__default.HTMLAttributes<HTMLDivElement> {
|
|
26
40
|
side?: "top" | "right" | "bottom" | "left";
|
|
27
41
|
size?: "sm" | "md" | "lg" | "xl" | "full";
|
|
@@ -58,4 +72,4 @@ declare const SheetNestedClose: React__default.FC<React__default.ComponentPropsW
|
|
|
58
72
|
afterClose?: () => void;
|
|
59
73
|
}>;
|
|
60
74
|
|
|
61
|
-
export { Sheet, SheetBoundary, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetNestedClose, SheetTitle, SheetTrigger };
|
|
75
|
+
export { Sheet, SheetBoundary, SheetClose, SheetCloseButton, type SheetCloseButtonProps, type SheetCloseButtonSide, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetNestedClose, SheetTitle, SheetTrigger };
|