@fanvue/ui 2.15.0 → 2.15.1
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/cjs/components/Dialog/Dialog.cjs +3 -1
- package/dist/cjs/components/Dialog/Dialog.cjs.map +1 -1
- package/dist/cjs/components/Drawer/Drawer.cjs +15 -4
- package/dist/cjs/components/Drawer/Drawer.cjs.map +1 -1
- package/dist/cjs/components/DropdownMenu/DropdownMenu.cjs +72 -2
- package/dist/cjs/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
- package/dist/cjs/components/InfoBox/InfoBox.cjs +3 -1
- package/dist/cjs/components/InfoBox/InfoBox.cjs.map +1 -1
- package/dist/cjs/utils/useSuppressClickAfterDrag.cjs +71 -0
- package/dist/cjs/utils/useSuppressClickAfterDrag.cjs.map +1 -0
- package/dist/components/Dialog/Dialog.mjs +3 -1
- package/dist/components/Dialog/Dialog.mjs.map +1 -1
- package/dist/components/Drawer/Drawer.mjs +15 -4
- package/dist/components/Drawer/Drawer.mjs.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.mjs +72 -2
- package/dist/components/DropdownMenu/DropdownMenu.mjs.map +1 -1
- package/dist/components/InfoBox/InfoBox.mjs +3 -1
- package/dist/components/InfoBox/InfoBox.mjs.map +1 -1
- package/dist/index.d.ts +34 -9
- package/dist/utils/useSuppressClickAfterDrag.mjs +54 -0
- package/dist/utils/useSuppressClickAfterDrag.mjs.map +1 -0
- package/package.json +2 -1
|
@@ -5,6 +5,7 @@ const jsxRuntime = require("react/jsx-runtime");
|
|
|
5
5
|
const DialogPrimitive = require("@radix-ui/react-dialog");
|
|
6
6
|
const React = require("react");
|
|
7
7
|
const cn = require("../../utils/cn.cjs");
|
|
8
|
+
const useSuppressClickAfterDrag = require("../../utils/useSuppressClickAfterDrag.cjs");
|
|
8
9
|
const IconButton = require("../IconButton/IconButton.cjs");
|
|
9
10
|
const ArrowLeftIcon = require("../Icons/ArrowLeftIcon.cjs");
|
|
10
11
|
const CloseIcon = require("../Icons/CloseIcon.cjs");
|
|
@@ -27,7 +28,8 @@ function _interopNamespaceDefault(e) {
|
|
|
27
28
|
const DialogPrimitive__namespace = /* @__PURE__ */ _interopNamespaceDefault(DialogPrimitive);
|
|
28
29
|
const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
|
|
29
30
|
const Dialog = DialogPrimitive__namespace.Root;
|
|
30
|
-
const DialogTrigger = DialogPrimitive__namespace.Trigger;
|
|
31
|
+
const DialogTrigger = React__namespace.forwardRef((props, ref) => /* @__PURE__ */ jsxRuntime.jsx(DialogPrimitive__namespace.Trigger, { ref, ...useSuppressClickAfterDrag.useSuppressClickAfterDrag(props) }));
|
|
32
|
+
DialogTrigger.displayName = "DialogTrigger";
|
|
31
33
|
const DialogClose = DialogPrimitive__namespace.Close;
|
|
32
34
|
const DialogOverlay = React__namespace.forwardRef(({ className, style, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
33
35
|
DialogPrimitive__namespace.Overlay,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.cjs","sources":["../../../../src/components/Dialog/Dialog.tsx"],"sourcesContent":["import * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { ArrowLeftIcon } from \"../Icons/ArrowLeftIcon\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\n\n/** Props for the {@link Dialog} root component. */\nexport interface DialogProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Root> {\n /** Controlled open state. When provided, you must also supply `onOpenChange`. */\n open?: boolean;\n /** Called when the open state changes. Required when `open` is controlled. */\n onOpenChange?: (open: boolean) => void;\n /** The open state of the dialog when it is initially rendered (uncontrolled). */\n defaultOpen?: boolean;\n}\n\n/** Root component that manages open/close state for a dialog. */\nexport const Dialog = DialogPrimitive.Root;\n\n/** Props for the {@link DialogTrigger} component. */\nexport type DialogTriggerProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>;\n\n/** The element that opens the dialog when clicked. */\nexport const DialogTrigger = DialogPrimitive.Trigger;\n\n/** Convenience alias for Radix `Dialog.Close`. Closes the dialog when clicked. */\nexport const DialogClose = DialogPrimitive.Close;\n\n/** Props for the {@link DialogClose} component. */\nexport type DialogCloseProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>;\n\nexport interface DialogOverlayProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> {}\n\n/**\n * Semi-transparent backdrop rendered behind the dialog content.\n * Rendered inside a portal automatically by {@link DialogContent}.\n */\nexport const DialogOverlay = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Overlay>,\n DialogOverlayProps\n>(({ className, style, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n className={cn(\n \"data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 fixed inset-0 bg-bg-overlay data-[state=closed]:animate-out data-[state=open]:animate-in\",\n className,\n )}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n {...props}\n />\n));\nDialogOverlay.displayName = \"DialogOverlay\";\n\nexport interface DialogContentProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {\n /**\n * Width preset for the dialog.\n * - `\"sm\"` — 400px max-width (confirmations, simple forms)\n * - `\"md\"` — 440px max-width (default, standard dialogs)\n * - `\"lg\"` — 600px max-width (complex content, tables)\n *\n * @default \"md\"\n */\n size?: \"sm\" | \"md\" | \"lg\";\n /** When true, renders overlay automatically. @default true */\n overlay?: boolean;\n}\n\nconst SIZE_CLASSES: Record<NonNullable<DialogContentProps[\"size\"]>, string> = {\n sm: \"sm:max-w-[400px]\",\n md: \"sm:max-w-[440px]\",\n lg: \"sm:max-w-[600px]\",\n};\n\n/**\n * The dialog panel rendered inside a portal. Includes the overlay by default.\n *\n * On mobile viewports (<640px), the dialog slides up from the bottom as a sheet\n * with top-only border radius. On larger viewports it renders centered with\n * full border radius.\n *\n * @example\n * ```tsx\n * <Dialog>\n * <DialogTrigger asChild>\n * <Button>Open</Button>\n * </DialogTrigger>\n * <DialogContent>\n * <DialogHeader>\n * <DialogTitle>Title</DialogTitle>\n * </DialogHeader>\n * <DialogBody>Content here</DialogBody>\n * <DialogFooter>\n * <DialogClose asChild>\n * <Button variant=\"secondary\">Cancel</Button>\n * </DialogClose>\n * <Button>Accept</Button>\n * </DialogFooter>\n * </DialogContent>\n * </Dialog>\n * ```\n */\nexport const DialogContent = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Content>,\n DialogContentProps\n>(({ className, children, size = \"md\", overlay = true, style, onOpenAutoFocus, ...props }, ref) => (\n <DialogPrimitive.Portal>\n {overlay && <DialogOverlay />}\n <DialogPrimitive.Content\n ref={ref}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n onOpenAutoFocus={(e) => {\n if (onOpenAutoFocus) {\n onOpenAutoFocus(e);\n return;\n }\n e.preventDefault();\n (e.currentTarget as HTMLElement).focus();\n }}\n className={cn(\n // Base\n \"fixed flex flex-col overflow-hidden bg-bg-primary shadow-lg focus:outline-none dark:bg-surface-primary\",\n // Mobile: bottom sheet\n \"inset-x-0 bottom-0 max-h-[85vh] w-full rounded-t-lg\",\n // Animation (shared)\n \"data-[state=open]:fade-in-0 data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=closed]:animate-out\",\n // Mobile: slide up from bottom\n \"data-[state=open]:slide-in-from-bottom-full\",\n \"data-[state=closed]:slide-out-to-bottom-full\",\n // Desktop: centered dialog\n \"sm:inset-auto sm:top-1/2 sm:left-1/2 sm:max-h-[85vh] sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-lg\",\n // Desktop: scale in/out (cancel mobile slide)\n \"sm:data-[state=open]:slide-in-from-bottom-0 sm:data-[state=open]:zoom-in-95\",\n \"sm:data-[state=closed]:slide-out-to-bottom-0 sm:data-[state=closed]:zoom-out-95\",\n // Duration\n \"duration-200\",\n // Size\n SIZE_CLASSES[size],\n className,\n )}\n {...props}\n >\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n));\nDialogContent.displayName = \"DialogContent\";\n\nexport interface DialogHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Show the close (X) button in the header. @default true */\n showClose?: boolean;\n /** Show a back arrow button on the left side. Defaults to `true` when `onBack` is provided. */\n showBack?: boolean;\n /** Called when the back button is clicked. */\n onBack?: () => void;\n /** Accessible label for the back button. @default \"Go back\" */\n backLabel?: string;\n /** Accessible label for the close button. @default \"Close\" */\n closeLabel?: string;\n}\n\n/**\n * Header bar for the dialog. Renders the title with an optional back arrow\n * and close button.\n *\n * @example\n * ```tsx\n * <DialogHeader>\n * <DialogTitle>Settings</DialogTitle>\n * </DialogHeader>\n *\n * <DialogHeader showBack onBack={() => setStep(0)}>\n * <DialogTitle>Step 2</DialogTitle>\n * </DialogHeader>\n * ```\n */\nexport const DialogHeader = React.forwardRef<HTMLDivElement, DialogHeaderProps>(\n (\n {\n className,\n children,\n showClose = true,\n showBack,\n onBack,\n backLabel = \"Go back\",\n closeLabel = \"Close\",\n ...props\n },\n ref,\n ) => {\n const shouldShowBack = showBack ?? !!onBack;\n\n return (\n <div\n ref={ref}\n className={cn(\"flex h-16 shrink-0 items-center gap-2 px-6 py-4\", className)}\n {...props}\n >\n {shouldShowBack && (\n <IconButton\n variant=\"tertiary\"\n size=\"32\"\n icon={<ArrowLeftIcon />}\n onClick={onBack}\n disabled={!onBack}\n aria-label={backLabel}\n />\n )}\n <div className=\"min-w-0 flex-1\">{children}</div>\n {showClose && (\n <DialogPrimitive.Close asChild>\n <IconButton variant=\"tertiary\" size=\"32\" icon={<CloseIcon />} aria-label={closeLabel} />\n </DialogPrimitive.Close>\n )}\n </div>\n );\n },\n);\nDialogHeader.displayName = \"DialogHeader\";\n\n/** Props for the {@link DialogTitle} component. */\nexport type DialogTitleProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>;\n\n/**\n * Accessible title for the dialog. Must be rendered inside {@link DialogHeader}\n * or directly within {@link DialogContent}.\n */\nexport const DialogTitle = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Title>,\n DialogTitleProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\"typography-bold-heading-xs truncate text-content-primary\", className)}\n {...props}\n />\n));\nDialogTitle.displayName = \"DialogTitle\";\n\n/** Props for the {@link DialogDescription} component. */\nexport type DialogDescriptionProps = React.ComponentPropsWithoutRef<\n typeof DialogPrimitive.Description\n>;\n\n/** Accessible description for the dialog. Rendered as secondary text. */\nexport const DialogDescription = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Description>,\n DialogDescriptionProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn(\"typography-regular-body-lg text-content-secondary\", className)}\n {...props}\n />\n));\nDialogDescription.displayName = \"DialogDescription\";\n\nexport interface DialogBodyProps extends React.HTMLAttributes<HTMLDivElement> {}\n\n/**\n * Scrollable content area (slot) between the header and footer.\n * Grows to fill available space and scrolls when content overflows.\n */\nexport const DialogBody = React.forwardRef<HTMLDivElement, DialogBodyProps>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex-1 overflow-y-auto px-6 py-4\", className)} {...props} />\n ),\n);\nDialogBody.displayName = \"DialogBody\";\n\nexport interface DialogFooterProps extends React.HTMLAttributes<HTMLDivElement> {}\n\n/**\n * Footer bar for the dialog. Typically contains action buttons.\n * Children are laid out in a horizontal row with equal flex-basis.\n */\nexport const DialogFooter = React.forwardRef<HTMLDivElement, DialogFooterProps>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n \"flex shrink-0 items-center gap-3 px-6 pt-3 pb-6\",\n \"[&>*]:min-w-0 [&>*]:flex-1\",\n className,\n )}\n {...props}\n />\n ),\n);\nDialogFooter.displayName = \"DialogFooter\";\n"],"names":["DialogPrimitive","React","jsx","cn","jsxs","IconButton","ArrowLeftIcon","CloseIcon"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Dialog.cjs","sources":["../../../../src/components/Dialog/Dialog.tsx"],"sourcesContent":["import * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useSuppressClickAfterDrag } from \"../../utils/useSuppressClickAfterDrag\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { ArrowLeftIcon } from \"../Icons/ArrowLeftIcon\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\n\n/** Props for the {@link Dialog} root component. */\nexport interface DialogProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Root> {\n /** Controlled open state. When provided, you must also supply `onOpenChange`. */\n open?: boolean;\n /** Called when the open state changes. Required when `open` is controlled. */\n onOpenChange?: (open: boolean) => void;\n /** The open state of the dialog when it is initially rendered (uncontrolled). */\n defaultOpen?: boolean;\n}\n\n/** Root component that manages open/close state for a dialog. */\nexport const Dialog = DialogPrimitive.Root;\n\n/** Props for the {@link DialogTrigger} component. */\nexport type DialogTriggerProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>;\n\n/**\n * The element that opens the dialog when clicked.\n *\n * On touch / pen, a press-and-release that crosses a small movement threshold\n * is treated as a drag and the resulting synthetic click is suppressed —\n * defends against Android Chrome opening the dialog on a scroll-drag-end.\n */\nexport const DialogTrigger = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Trigger>,\n DialogTriggerProps\n>((props, ref) => <DialogPrimitive.Trigger ref={ref} {...useSuppressClickAfterDrag(props)} />);\nDialogTrigger.displayName = \"DialogTrigger\";\n\n/** Convenience alias for Radix `Dialog.Close`. Closes the dialog when clicked. */\nexport const DialogClose = DialogPrimitive.Close;\n\n/** Props for the {@link DialogClose} component. */\nexport type DialogCloseProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>;\n\nexport interface DialogOverlayProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> {}\n\n/**\n * Semi-transparent backdrop rendered behind the dialog content.\n * Rendered inside a portal automatically by {@link DialogContent}.\n */\nexport const DialogOverlay = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Overlay>,\n DialogOverlayProps\n>(({ className, style, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n className={cn(\n \"data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 fixed inset-0 bg-bg-overlay data-[state=closed]:animate-out data-[state=open]:animate-in\",\n className,\n )}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n {...props}\n />\n));\nDialogOverlay.displayName = \"DialogOverlay\";\n\nexport interface DialogContentProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {\n /**\n * Width preset for the dialog.\n * - `\"sm\"` — 400px max-width (confirmations, simple forms)\n * - `\"md\"` — 440px max-width (default, standard dialogs)\n * - `\"lg\"` — 600px max-width (complex content, tables)\n *\n * @default \"md\"\n */\n size?: \"sm\" | \"md\" | \"lg\";\n /** When true, renders overlay automatically. @default true */\n overlay?: boolean;\n}\n\nconst SIZE_CLASSES: Record<NonNullable<DialogContentProps[\"size\"]>, string> = {\n sm: \"sm:max-w-[400px]\",\n md: \"sm:max-w-[440px]\",\n lg: \"sm:max-w-[600px]\",\n};\n\n/**\n * The dialog panel rendered inside a portal. Includes the overlay by default.\n *\n * On mobile viewports (<640px), the dialog slides up from the bottom as a sheet\n * with top-only border radius. On larger viewports it renders centered with\n * full border radius.\n *\n * @example\n * ```tsx\n * <Dialog>\n * <DialogTrigger asChild>\n * <Button>Open</Button>\n * </DialogTrigger>\n * <DialogContent>\n * <DialogHeader>\n * <DialogTitle>Title</DialogTitle>\n * </DialogHeader>\n * <DialogBody>Content here</DialogBody>\n * <DialogFooter>\n * <DialogClose asChild>\n * <Button variant=\"secondary\">Cancel</Button>\n * </DialogClose>\n * <Button>Accept</Button>\n * </DialogFooter>\n * </DialogContent>\n * </Dialog>\n * ```\n */\nexport const DialogContent = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Content>,\n DialogContentProps\n>(({ className, children, size = \"md\", overlay = true, style, onOpenAutoFocus, ...props }, ref) => (\n <DialogPrimitive.Portal>\n {overlay && <DialogOverlay />}\n <DialogPrimitive.Content\n ref={ref}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n onOpenAutoFocus={(e) => {\n if (onOpenAutoFocus) {\n onOpenAutoFocus(e);\n return;\n }\n e.preventDefault();\n (e.currentTarget as HTMLElement).focus();\n }}\n className={cn(\n // Base\n \"fixed flex flex-col overflow-hidden bg-bg-primary shadow-lg focus:outline-none dark:bg-surface-primary\",\n // Mobile: bottom sheet\n \"inset-x-0 bottom-0 max-h-[85vh] w-full rounded-t-lg\",\n // Animation (shared)\n \"data-[state=open]:fade-in-0 data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=closed]:animate-out\",\n // Mobile: slide up from bottom\n \"data-[state=open]:slide-in-from-bottom-full\",\n \"data-[state=closed]:slide-out-to-bottom-full\",\n // Desktop: centered dialog\n \"sm:inset-auto sm:top-1/2 sm:left-1/2 sm:max-h-[85vh] sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-lg\",\n // Desktop: scale in/out (cancel mobile slide)\n \"sm:data-[state=open]:slide-in-from-bottom-0 sm:data-[state=open]:zoom-in-95\",\n \"sm:data-[state=closed]:slide-out-to-bottom-0 sm:data-[state=closed]:zoom-out-95\",\n // Duration\n \"duration-200\",\n // Size\n SIZE_CLASSES[size],\n className,\n )}\n {...props}\n >\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n));\nDialogContent.displayName = \"DialogContent\";\n\nexport interface DialogHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Show the close (X) button in the header. @default true */\n showClose?: boolean;\n /** Show a back arrow button on the left side. Defaults to `true` when `onBack` is provided. */\n showBack?: boolean;\n /** Called when the back button is clicked. */\n onBack?: () => void;\n /** Accessible label for the back button. @default \"Go back\" */\n backLabel?: string;\n /** Accessible label for the close button. @default \"Close\" */\n closeLabel?: string;\n}\n\n/**\n * Header bar for the dialog. Renders the title with an optional back arrow\n * and close button.\n *\n * @example\n * ```tsx\n * <DialogHeader>\n * <DialogTitle>Settings</DialogTitle>\n * </DialogHeader>\n *\n * <DialogHeader showBack onBack={() => setStep(0)}>\n * <DialogTitle>Step 2</DialogTitle>\n * </DialogHeader>\n * ```\n */\nexport const DialogHeader = React.forwardRef<HTMLDivElement, DialogHeaderProps>(\n (\n {\n className,\n children,\n showClose = true,\n showBack,\n onBack,\n backLabel = \"Go back\",\n closeLabel = \"Close\",\n ...props\n },\n ref,\n ) => {\n const shouldShowBack = showBack ?? !!onBack;\n\n return (\n <div\n ref={ref}\n className={cn(\"flex h-16 shrink-0 items-center gap-2 px-6 py-4\", className)}\n {...props}\n >\n {shouldShowBack && (\n <IconButton\n variant=\"tertiary\"\n size=\"32\"\n icon={<ArrowLeftIcon />}\n onClick={onBack}\n disabled={!onBack}\n aria-label={backLabel}\n />\n )}\n <div className=\"min-w-0 flex-1\">{children}</div>\n {showClose && (\n <DialogPrimitive.Close asChild>\n <IconButton variant=\"tertiary\" size=\"32\" icon={<CloseIcon />} aria-label={closeLabel} />\n </DialogPrimitive.Close>\n )}\n </div>\n );\n },\n);\nDialogHeader.displayName = \"DialogHeader\";\n\n/** Props for the {@link DialogTitle} component. */\nexport type DialogTitleProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>;\n\n/**\n * Accessible title for the dialog. Must be rendered inside {@link DialogHeader}\n * or directly within {@link DialogContent}.\n */\nexport const DialogTitle = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Title>,\n DialogTitleProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\"typography-bold-heading-xs truncate text-content-primary\", className)}\n {...props}\n />\n));\nDialogTitle.displayName = \"DialogTitle\";\n\n/** Props for the {@link DialogDescription} component. */\nexport type DialogDescriptionProps = React.ComponentPropsWithoutRef<\n typeof DialogPrimitive.Description\n>;\n\n/** Accessible description for the dialog. Rendered as secondary text. */\nexport const DialogDescription = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Description>,\n DialogDescriptionProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn(\"typography-regular-body-lg text-content-secondary\", className)}\n {...props}\n />\n));\nDialogDescription.displayName = \"DialogDescription\";\n\nexport interface DialogBodyProps extends React.HTMLAttributes<HTMLDivElement> {}\n\n/**\n * Scrollable content area (slot) between the header and footer.\n * Grows to fill available space and scrolls when content overflows.\n */\nexport const DialogBody = React.forwardRef<HTMLDivElement, DialogBodyProps>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex-1 overflow-y-auto px-6 py-4\", className)} {...props} />\n ),\n);\nDialogBody.displayName = \"DialogBody\";\n\nexport interface DialogFooterProps extends React.HTMLAttributes<HTMLDivElement> {}\n\n/**\n * Footer bar for the dialog. Typically contains action buttons.\n * Children are laid out in a horizontal row with equal flex-basis.\n */\nexport const DialogFooter = React.forwardRef<HTMLDivElement, DialogFooterProps>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n \"flex shrink-0 items-center gap-3 px-6 pt-3 pb-6\",\n \"[&>*]:min-w-0 [&>*]:flex-1\",\n className,\n )}\n {...props}\n />\n ),\n);\nDialogFooter.displayName = \"DialogFooter\";\n"],"names":["DialogPrimitive","React","jsx","useSuppressClickAfterDrag","cn","jsxs","IconButton","ArrowLeftIcon","CloseIcon"],"mappings":"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"}
|
|
@@ -5,6 +5,7 @@ const jsxRuntime = require("react/jsx-runtime");
|
|
|
5
5
|
const DialogPrimitive = require("@radix-ui/react-dialog");
|
|
6
6
|
const React = require("react");
|
|
7
7
|
const cn = require("../../utils/cn.cjs");
|
|
8
|
+
const useSuppressClickAfterDrag = require("../../utils/useSuppressClickAfterDrag.cjs");
|
|
8
9
|
const IconButton = require("../IconButton/IconButton.cjs");
|
|
9
10
|
const CloseIcon = require("../Icons/CloseIcon.cjs");
|
|
10
11
|
function _interopNamespaceDefault(e) {
|
|
@@ -25,13 +26,15 @@ function _interopNamespaceDefault(e) {
|
|
|
25
26
|
}
|
|
26
27
|
const DialogPrimitive__namespace = /* @__PURE__ */ _interopNamespaceDefault(DialogPrimitive);
|
|
27
28
|
const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
|
|
28
|
-
const DrawerContext = React__namespace.createContext({
|
|
29
|
+
const DrawerContext = React__namespace.createContext({
|
|
30
|
+
overlay: true
|
|
31
|
+
});
|
|
29
32
|
function Drawer({ overlay = true, modal, children, ...props }) {
|
|
30
33
|
const resolvedModal = modal ?? (overlay ? void 0 : false);
|
|
31
34
|
return /* @__PURE__ */ jsxRuntime.jsx(DrawerContext.Provider, { value: { overlay }, children: /* @__PURE__ */ jsxRuntime.jsx(DialogPrimitive__namespace.Root, { modal: resolvedModal, ...props, children }) });
|
|
32
35
|
}
|
|
33
36
|
Drawer.displayName = "Drawer";
|
|
34
|
-
const DrawerTrigger = DialogPrimitive__namespace.Trigger;
|
|
37
|
+
const DrawerTrigger = React__namespace.forwardRef((props, ref) => /* @__PURE__ */ jsxRuntime.jsx(DialogPrimitive__namespace.Trigger, { ref, ...useSuppressClickAfterDrag.useSuppressClickAfterDrag(props) }));
|
|
35
38
|
DrawerTrigger.displayName = "DrawerTrigger";
|
|
36
39
|
const DrawerClose = DialogPrimitive__namespace.Close;
|
|
37
40
|
DrawerClose.displayName = "DrawerClose";
|
|
@@ -71,7 +74,12 @@ const DrawerContent = React__namespace.forwardRef(
|
|
|
71
74
|
const ctx = React__namespace.useContext(DrawerContext);
|
|
72
75
|
const overlay = overlayProp ?? ctx.overlay;
|
|
73
76
|
const isHorizontal = position === "left" || position === "right";
|
|
74
|
-
const sizeClass = isHorizontal ? {
|
|
77
|
+
const sizeClass = isHorizontal ? {
|
|
78
|
+
sm: "max-w-sm",
|
|
79
|
+
md: "max-w-md",
|
|
80
|
+
lg: "max-w-lg",
|
|
81
|
+
full: "max-w-full"
|
|
82
|
+
}[size] : {
|
|
75
83
|
sm: "max-h-[24rem]",
|
|
76
84
|
md: "max-h-[28rem]",
|
|
77
85
|
lg: "max-h-[32rem]",
|
|
@@ -83,7 +91,10 @@ const DrawerContent = React__namespace.forwardRef(
|
|
|
83
91
|
DialogPrimitive__namespace.Content,
|
|
84
92
|
{
|
|
85
93
|
ref,
|
|
86
|
-
style: {
|
|
94
|
+
style: {
|
|
95
|
+
zIndex: "calc(var(--fanvue-ui-portal-z-index, 50) + 1)",
|
|
96
|
+
...style
|
|
97
|
+
},
|
|
87
98
|
className: cn.cn(
|
|
88
99
|
"fixed flex flex-col bg-surface-secondary shadow-lg outline-none backdrop-blur-lg",
|
|
89
100
|
"data-[state=closed]:animate-out data-[state=open]:animate-in",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Drawer.cjs","sources":["../../../../src/components/Drawer/Drawer.tsx"],"sourcesContent":["import * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\n\n/** The side from which the drawer slides in. */\nexport type DrawerPosition = \"left\" | \"right\" | \"top\" | \"bottom\";\n\n/** Size presets for the drawer panel. Maps to max-width (left/right) or max-height (top/bottom). */\nexport type DrawerSize = \"sm\" | \"md\" | \"lg\" | \"full\";\n\n/**\n * Props for the {@link Drawer} root component.\n *\n * Inherits `open`, `onOpenChange`, and `defaultOpen` from Radix Dialog.Root.\n *\n * The Radix `modal` prop (default `true`) can also be passed to create a\n * non-modal drawer — useful for persistent side navigation that does not\n * block interaction with the rest of the page.\n *\n * **`overlay` behaviour:** When `overlay` is `false`, the component\n * automatically sets `modal={false}` on the underlying Radix Dialog.Root.\n * A modal dialog without a visible overlay is confusing because focus is\n * still trapped and scroll is blocked even though the page appears\n * interactive. Passing `modal={true}` explicitly will override this.\n */\nexport interface DrawerProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Root> {\n /**\n * Whether the default {@link DrawerOverlay} is rendered.\n * When `false`, `modal` is automatically set to `false` as well\n * (unless explicitly overridden) so focus-trap and scroll-lock are disabled.\n * @default true\n */\n overlay?: boolean;\n}\n\nconst DrawerContext = React.createContext<{ overlay: boolean }>({ overlay: true });\n\n/**\n * Root component that manages open/close state for a drawer.\n * Wraps Radix Dialog.Root.\n *\n * @example\n * ```tsx\n * <Drawer>\n * <DrawerTrigger>Open</DrawerTrigger>\n * <DrawerContent position=\"right\">\n * <DrawerHeader>\n * <DrawerTitle>Settings</DrawerTitle>\n * <DrawerDescription>Adjust your preferences.</DrawerDescription>\n * </DrawerHeader>\n * <p>Content goes here.</p>\n * <DrawerFooter>\n * <DrawerClose>Done</DrawerClose>\n * </DrawerFooter>\n * </DrawerContent>\n * </Drawer>\n * ```\n */\nexport function Drawer({ overlay = true, modal, children, ...props }: DrawerProps) {\n const resolvedModal = modal ?? (overlay ? undefined : false);\n return (\n <DrawerContext.Provider value={{ overlay }}>\n <DialogPrimitive.Root modal={resolvedModal} {...props}>\n {children}\n </DialogPrimitive.Root>\n </DrawerContext.Provider>\n );\n}\nDrawer.displayName = \"Drawer\";\n\n/** Props for the {@link DrawerTrigger} component. */\nexport type DrawerTriggerProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>;\n\n/** The element that opens the drawer when clicked. */\nexport const DrawerTrigger = DialogPrimitive.Trigger;\nDrawerTrigger.displayName = \"DrawerTrigger\";\n\n/** Props for the {@link DrawerClose} component. */\nexport type DrawerCloseProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>;\n\n/** Closes the drawer when clicked. Can be placed anywhere inside the drawer. */\nexport const DrawerClose = DialogPrimitive.Close;\nDrawerClose.displayName = \"DrawerClose\";\n\n/** Props for the {@link DrawerOverlay} component. */\nexport interface DrawerOverlayProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> {}\n\n/**\n * A translucent backdrop rendered behind the drawer content.\n * Clicking the overlay closes the drawer by default.\n */\nexport const DrawerOverlay = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Overlay>,\n DrawerOverlayProps\n>(({ className, style, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n className={cn(\n \"fixed inset-0 bg-bg-overlay\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=closed]:duration-150 data-[state=closed]:ease-in\",\n \"data-[state=open]:fade-in-0 data-[state=open]:duration-200 data-[state=open]:ease-out\",\n className,\n )}\n {...props}\n />\n));\nDrawerOverlay.displayName = \"DrawerOverlay\";\n\n/**\n * Slide-in animation classes keyed by position.\n * Uses Tailwind animate utilities (animate-in / animate-out).\n */\nconst SLIDE_CLASSES: Record<DrawerPosition, string> = {\n right:\n \"inset-y-0 right-0 h-full w-2/3 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right\",\n left: \"inset-y-0 left-0 h-full w-2/3 data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left\",\n top: \"inset-x-0 top-0 w-full data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top\",\n bottom:\n \"inset-x-0 bottom-0 w-full rounded-t-xs data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom\",\n};\n\n/** Props for the {@link DrawerContent} component. */\nexport interface DrawerContentProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {\n /**\n * The edge from which the drawer slides in.\n *\n * Named `position` (rather than `side`) to avoid confusion with the CSS\n * `side` concept used by Radix Popover/Tooltip and to better convey the\n * spatial relationship of the drawer to the viewport.\n *\n * @default \"right\"\n */\n position?: DrawerPosition;\n /**\n * Controls the maximum extent of the drawer panel.\n * For left/right drawers this sets `max-width`; for top/bottom it sets `max-height`.\n * @default \"sm\"\n */\n size?: DrawerSize;\n /**\n * Whether to render the default {@link DrawerOverlay} behind the content.\n * Set to `false` to provide your own overlay or omit it entirely.\n *\n * Prefer setting `overlay` on the {@link Drawer} root instead so that\n * `modal` is also adjusted automatically.\n *\n * @default true\n */\n overlay?: boolean;\n /** Props forwarded to the default {@link DrawerOverlay} when `overlay` is `true`. */\n overlayProps?: DrawerOverlayProps;\n}\n\n/**\n * The panel that slides in from the chosen edge. Renders inside a portal with\n * an overlay backdrop by default.\n *\n * Includes focus-trap, `aria-describedby`, and Escape-to-close from Radix Dialog.\n */\nexport const DrawerContent = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Content>,\n DrawerContentProps\n>(\n (\n {\n className,\n position = \"right\",\n size = \"sm\",\n overlay: overlayProp,\n overlayProps,\n style,\n children,\n ...props\n },\n ref,\n ) => {\n const ctx = React.useContext(DrawerContext);\n const overlay = overlayProp ?? ctx.overlay;\n const isHorizontal = position === \"left\" || position === \"right\";\n const sizeClass = isHorizontal\n ? ({ sm: \"max-w-sm\", md: \"max-w-md\", lg: \"max-w-lg\", full: \"max-w-full\" } as const)[size]\n : (\n {\n sm: \"max-h-[24rem]\",\n md: \"max-h-[28rem]\",\n lg: \"max-h-[32rem]\",\n full: \"max-h-full\",\n } as const\n )[size];\n\n return (\n <DialogPrimitive.Portal>\n {overlay && <DrawerOverlay {...overlayProps} />}\n <DialogPrimitive.Content\n ref={ref}\n style={{ zIndex: \"calc(var(--fanvue-ui-portal-z-index, 50) + 1)\", ...style }}\n className={cn(\n \"fixed flex flex-col bg-surface-secondary shadow-lg outline-none backdrop-blur-lg\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:duration-150 data-[state=closed]:ease-in\",\n \"data-[state=open]:duration-200 data-[state=open]:ease-out\",\n SLIDE_CLASSES[position],\n sizeClass,\n className,\n )}\n {...props}\n >\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n );\n },\n);\nDrawerContent.displayName = \"DrawerContent\";\n\n/** Props for the {@link DrawerHeader} component. */\nexport interface DrawerHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Whether to show a built-in close (X) button. @default true */\n showClose?: boolean;\n /** Accessible label for the close button. @default \"Close drawer\" */\n closeLabel?: string;\n}\n\n/**\n * A semantic header area for the drawer, typically containing a title and description.\n * Renders a built-in close button by default.\n */\nexport const DrawerHeader = React.forwardRef<HTMLDivElement, DrawerHeaderProps>(\n ({ className, showClose = true, closeLabel = \"Close drawer\", children, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex items-start gap-2 p-4\", className)} {...props}>\n <div className=\"flex min-w-0 flex-1 flex-col gap-1.5\">{children}</div>\n {showClose && (\n <DialogPrimitive.Close asChild>\n <IconButton\n icon={<CloseIcon />}\n aria-label={closeLabel}\n variant=\"tertiary\"\n size=\"24\"\n className=\"shrink-0\"\n />\n </DialogPrimitive.Close>\n )}\n </div>\n ),\n);\nDrawerHeader.displayName = \"DrawerHeader\";\n\n/** Props for the {@link DrawerFooter} component. */\nexport interface DrawerFooterProps extends React.HTMLAttributes<HTMLDivElement> {}\n\n/** A semantic footer area for the drawer, typically containing action buttons. */\nexport const DrawerFooter = React.forwardRef<HTMLDivElement, DrawerFooterProps>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex flex-col gap-2 p-4\", className)} {...props} />\n ),\n);\nDrawerFooter.displayName = \"DrawerFooter\";\n\n/** Props for the {@link DrawerTitle} component. */\nexport interface DrawerTitleProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> {}\n\n/** An accessible title for the drawer. Required for screen readers. */\nexport const DrawerTitle = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Title>,\n DrawerTitleProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\"typography-semibold-body-lg truncate text-content-primary\", className)}\n {...props}\n />\n));\nDrawerTitle.displayName = \"DrawerTitle\";\n\n/** Props for the {@link DrawerDescription} component. */\nexport interface DrawerDescriptionProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> {}\n\n/** An accessible description for the drawer, providing supplementary context. */\nexport const DrawerDescription = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Description>,\n DrawerDescriptionProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn(\"typography-regular-body-md text-content-secondary\", className)}\n {...props}\n />\n));\nDrawerDescription.displayName = \"DrawerDescription\";\n"],"names":["React","jsx","DialogPrimitive","cn","jsxs","IconButton","CloseIcon"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Drawer.cjs","sources":["../../../../src/components/Drawer/Drawer.tsx"],"sourcesContent":["import * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useSuppressClickAfterDrag } from \"../../utils/useSuppressClickAfterDrag\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\n\n/** The side from which the drawer slides in. */\nexport type DrawerPosition = \"left\" | \"right\" | \"top\" | \"bottom\";\n\n/** Size presets for the drawer panel. Maps to max-width (left/right) or max-height (top/bottom). */\nexport type DrawerSize = \"sm\" | \"md\" | \"lg\" | \"full\";\n\n/**\n * Props for the {@link Drawer} root component.\n *\n * Inherits `open`, `onOpenChange`, and `defaultOpen` from Radix Dialog.Root.\n *\n * The Radix `modal` prop (default `true`) can also be passed to create a\n * non-modal drawer — useful for persistent side navigation that does not\n * block interaction with the rest of the page.\n *\n * **`overlay` behaviour:** When `overlay` is `false`, the component\n * automatically sets `modal={false}` on the underlying Radix Dialog.Root.\n * A modal dialog without a visible overlay is confusing because focus is\n * still trapped and scroll is blocked even though the page appears\n * interactive. Passing `modal={true}` explicitly will override this.\n */\nexport interface DrawerProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Root> {\n /**\n * Whether the default {@link DrawerOverlay} is rendered.\n * When `false`, `modal` is automatically set to `false` as well\n * (unless explicitly overridden) so focus-trap and scroll-lock are disabled.\n * @default true\n */\n overlay?: boolean;\n}\n\nconst DrawerContext = React.createContext<{ overlay: boolean }>({\n overlay: true,\n});\n\n/**\n * Root component that manages open/close state for a drawer.\n * Wraps Radix Dialog.Root.\n *\n * @example\n * ```tsx\n * <Drawer>\n * <DrawerTrigger>Open</DrawerTrigger>\n * <DrawerContent position=\"right\">\n * <DrawerHeader>\n * <DrawerTitle>Settings</DrawerTitle>\n * <DrawerDescription>Adjust your preferences.</DrawerDescription>\n * </DrawerHeader>\n * <p>Content goes here.</p>\n * <DrawerFooter>\n * <DrawerClose>Done</DrawerClose>\n * </DrawerFooter>\n * </DrawerContent>\n * </Drawer>\n * ```\n */\nexport function Drawer({ overlay = true, modal, children, ...props }: DrawerProps) {\n const resolvedModal = modal ?? (overlay ? undefined : false);\n return (\n <DrawerContext.Provider value={{ overlay }}>\n <DialogPrimitive.Root modal={resolvedModal} {...props}>\n {children}\n </DialogPrimitive.Root>\n </DrawerContext.Provider>\n );\n}\nDrawer.displayName = \"Drawer\";\n\n/** Props for the {@link DrawerTrigger} component. */\nexport type DrawerTriggerProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>;\n\n/**\n * The element that opens the drawer when clicked.\n *\n * On touch / pen, a press-and-release that crosses a small movement threshold\n * is treated as a drag and the resulting synthetic click is suppressed —\n * defends against Android Chrome opening the drawer on a scroll-drag-end.\n */\nexport const DrawerTrigger = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Trigger>,\n DrawerTriggerProps\n>((props, ref) => <DialogPrimitive.Trigger ref={ref} {...useSuppressClickAfterDrag(props)} />);\nDrawerTrigger.displayName = \"DrawerTrigger\";\n\n/** Props for the {@link DrawerClose} component. */\nexport type DrawerCloseProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>;\n\n/** Closes the drawer when clicked. Can be placed anywhere inside the drawer. */\nexport const DrawerClose = DialogPrimitive.Close;\nDrawerClose.displayName = \"DrawerClose\";\n\n/** Props for the {@link DrawerOverlay} component. */\nexport interface DrawerOverlayProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> {}\n\n/**\n * A translucent backdrop rendered behind the drawer content.\n * Clicking the overlay closes the drawer by default.\n */\nexport const DrawerOverlay = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Overlay>,\n DrawerOverlayProps\n>(({ className, style, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n className={cn(\n \"fixed inset-0 bg-bg-overlay\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=closed]:duration-150 data-[state=closed]:ease-in\",\n \"data-[state=open]:fade-in-0 data-[state=open]:duration-200 data-[state=open]:ease-out\",\n className,\n )}\n {...props}\n />\n));\nDrawerOverlay.displayName = \"DrawerOverlay\";\n\n/**\n * Slide-in animation classes keyed by position.\n * Uses Tailwind animate utilities (animate-in / animate-out).\n */\nconst SLIDE_CLASSES: Record<DrawerPosition, string> = {\n right:\n \"inset-y-0 right-0 h-full w-2/3 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right\",\n left: \"inset-y-0 left-0 h-full w-2/3 data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left\",\n top: \"inset-x-0 top-0 w-full data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top\",\n bottom:\n \"inset-x-0 bottom-0 w-full rounded-t-xs data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom\",\n};\n\n/** Props for the {@link DrawerContent} component. */\nexport interface DrawerContentProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {\n /**\n * The edge from which the drawer slides in.\n *\n * Named `position` (rather than `side`) to avoid confusion with the CSS\n * `side` concept used by Radix Popover/Tooltip and to better convey the\n * spatial relationship of the drawer to the viewport.\n *\n * @default \"right\"\n */\n position?: DrawerPosition;\n /**\n * Controls the maximum extent of the drawer panel.\n * For left/right drawers this sets `max-width`; for top/bottom it sets `max-height`.\n * @default \"sm\"\n */\n size?: DrawerSize;\n /**\n * Whether to render the default {@link DrawerOverlay} behind the content.\n * Set to `false` to provide your own overlay or omit it entirely.\n *\n * Prefer setting `overlay` on the {@link Drawer} root instead so that\n * `modal` is also adjusted automatically.\n *\n * @default true\n */\n overlay?: boolean;\n /** Props forwarded to the default {@link DrawerOverlay} when `overlay` is `true`. */\n overlayProps?: DrawerOverlayProps;\n}\n\n/**\n * The panel that slides in from the chosen edge. Renders inside a portal with\n * an overlay backdrop by default.\n *\n * Includes focus-trap, `aria-describedby`, and Escape-to-close from Radix Dialog.\n */\nexport const DrawerContent = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Content>,\n DrawerContentProps\n>(\n (\n {\n className,\n position = \"right\",\n size = \"sm\",\n overlay: overlayProp,\n overlayProps,\n style,\n children,\n ...props\n },\n ref,\n ) => {\n const ctx = React.useContext(DrawerContext);\n const overlay = overlayProp ?? ctx.overlay;\n const isHorizontal = position === \"left\" || position === \"right\";\n const sizeClass = isHorizontal\n ? (\n {\n sm: \"max-w-sm\",\n md: \"max-w-md\",\n lg: \"max-w-lg\",\n full: \"max-w-full\",\n } as const\n )[size]\n : (\n {\n sm: \"max-h-[24rem]\",\n md: \"max-h-[28rem]\",\n lg: \"max-h-[32rem]\",\n full: \"max-h-full\",\n } as const\n )[size];\n\n return (\n <DialogPrimitive.Portal>\n {overlay && <DrawerOverlay {...overlayProps} />}\n <DialogPrimitive.Content\n ref={ref}\n style={{\n zIndex: \"calc(var(--fanvue-ui-portal-z-index, 50) + 1)\",\n ...style,\n }}\n className={cn(\n \"fixed flex flex-col bg-surface-secondary shadow-lg outline-none backdrop-blur-lg\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:duration-150 data-[state=closed]:ease-in\",\n \"data-[state=open]:duration-200 data-[state=open]:ease-out\",\n SLIDE_CLASSES[position],\n sizeClass,\n className,\n )}\n {...props}\n >\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n );\n },\n);\nDrawerContent.displayName = \"DrawerContent\";\n\n/** Props for the {@link DrawerHeader} component. */\nexport interface DrawerHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Whether to show a built-in close (X) button. @default true */\n showClose?: boolean;\n /** Accessible label for the close button. @default \"Close drawer\" */\n closeLabel?: string;\n}\n\n/**\n * A semantic header area for the drawer, typically containing a title and description.\n * Renders a built-in close button by default.\n */\nexport const DrawerHeader = React.forwardRef<HTMLDivElement, DrawerHeaderProps>(\n ({ className, showClose = true, closeLabel = \"Close drawer\", children, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex items-start gap-2 p-4\", className)} {...props}>\n <div className=\"flex min-w-0 flex-1 flex-col gap-1.5\">{children}</div>\n {showClose && (\n <DialogPrimitive.Close asChild>\n <IconButton\n icon={<CloseIcon />}\n aria-label={closeLabel}\n variant=\"tertiary\"\n size=\"24\"\n className=\"shrink-0\"\n />\n </DialogPrimitive.Close>\n )}\n </div>\n ),\n);\nDrawerHeader.displayName = \"DrawerHeader\";\n\n/** Props for the {@link DrawerFooter} component. */\nexport interface DrawerFooterProps extends React.HTMLAttributes<HTMLDivElement> {}\n\n/** A semantic footer area for the drawer, typically containing action buttons. */\nexport const DrawerFooter = React.forwardRef<HTMLDivElement, DrawerFooterProps>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex flex-col gap-2 p-4\", className)} {...props} />\n ),\n);\nDrawerFooter.displayName = \"DrawerFooter\";\n\n/** Props for the {@link DrawerTitle} component. */\nexport interface DrawerTitleProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> {}\n\n/** An accessible title for the drawer. Required for screen readers. */\nexport const DrawerTitle = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Title>,\n DrawerTitleProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\"typography-semibold-body-lg truncate text-content-primary\", className)}\n {...props}\n />\n));\nDrawerTitle.displayName = \"DrawerTitle\";\n\n/** Props for the {@link DrawerDescription} component. */\nexport interface DrawerDescriptionProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> {}\n\n/** An accessible description for the drawer, providing supplementary context. */\nexport const DrawerDescription = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Description>,\n DrawerDescriptionProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn(\"typography-regular-body-md text-content-secondary\", className)}\n {...props}\n />\n));\nDrawerDescription.displayName = \"DrawerDescription\";\n"],"names":["React","jsx","DialogPrimitive","useSuppressClickAfterDrag","cn","jsxs","IconButton","CloseIcon"],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
4
4
|
const jsxRuntime = require("react/jsx-runtime");
|
|
5
5
|
const DropdownMenuPrimitive = require("@radix-ui/react-dropdown-menu");
|
|
6
|
+
const reactUseControllableState = require("@radix-ui/react-use-controllable-state");
|
|
6
7
|
const React = require("react");
|
|
7
8
|
const cn = require("../../utils/cn.cjs");
|
|
8
9
|
const floatingContentCollisionPadding = require("../../utils/floatingContentCollisionPadding.cjs");
|
|
@@ -24,8 +25,77 @@ function _interopNamespaceDefault(e) {
|
|
|
24
25
|
}
|
|
25
26
|
const DropdownMenuPrimitive__namespace = /* @__PURE__ */ _interopNamespaceDefault(DropdownMenuPrimitive);
|
|
26
27
|
const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
|
|
27
|
-
const
|
|
28
|
-
const
|
|
28
|
+
const TAP_MOVEMENT_THRESHOLD_PX = 10;
|
|
29
|
+
const ToggleOpenContext = React__namespace.createContext(null);
|
|
30
|
+
function DropdownMenu({
|
|
31
|
+
open: openProp,
|
|
32
|
+
defaultOpen,
|
|
33
|
+
onOpenChange,
|
|
34
|
+
children,
|
|
35
|
+
...props
|
|
36
|
+
}) {
|
|
37
|
+
const [open = false, setOpen] = reactUseControllableState.useControllableState({
|
|
38
|
+
prop: openProp,
|
|
39
|
+
defaultProp: defaultOpen ?? false,
|
|
40
|
+
onChange: onOpenChange
|
|
41
|
+
});
|
|
42
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ToggleOpenContext.Provider, { value: setOpen, children: /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuPrimitive__namespace.Root, { open, onOpenChange: setOpen, ...props, children }) });
|
|
43
|
+
}
|
|
44
|
+
const DropdownMenuTrigger = React__namespace.forwardRef((props, ref) => {
|
|
45
|
+
const toggleOpen = React__namespace.useContext(ToggleOpenContext);
|
|
46
|
+
const tapRef = React__namespace.useRef(null);
|
|
47
|
+
if (toggleOpen === null) {
|
|
48
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuPrimitive__namespace.Trigger, { ...props, ref });
|
|
49
|
+
}
|
|
50
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
51
|
+
DropdownMenuPrimitive__namespace.Trigger,
|
|
52
|
+
{
|
|
53
|
+
...props,
|
|
54
|
+
ref,
|
|
55
|
+
onPointerDown: (event) => {
|
|
56
|
+
props.onPointerDown?.(event);
|
|
57
|
+
if (event.pointerType === "mouse" || props.disabled) return;
|
|
58
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
59
|
+
tapRef.current = {
|
|
60
|
+
pointerId: event.pointerId,
|
|
61
|
+
x: event.clientX,
|
|
62
|
+
y: event.clientY,
|
|
63
|
+
movedPastThreshold: false
|
|
64
|
+
};
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
},
|
|
67
|
+
onPointerMove: (event) => {
|
|
68
|
+
props.onPointerMove?.(event);
|
|
69
|
+
const tap = tapRef.current;
|
|
70
|
+
if (tap === null || event.pointerId !== tap.pointerId || tap.movedPastThreshold) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const dx = event.clientX - tap.x;
|
|
74
|
+
const dy = event.clientY - tap.y;
|
|
75
|
+
if (Math.hypot(dx, dy) > TAP_MOVEMENT_THRESHOLD_PX) {
|
|
76
|
+
tap.movedPastThreshold = true;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
onPointerUp: (event) => {
|
|
80
|
+
props.onPointerUp?.(event);
|
|
81
|
+
const tap = tapRef.current;
|
|
82
|
+
if (tap === null || event.pointerId !== tap.pointerId) return;
|
|
83
|
+
const wasDrag = tap.movedPastThreshold;
|
|
84
|
+
tapRef.current = null;
|
|
85
|
+
if (!wasDrag && !props.disabled) {
|
|
86
|
+
toggleOpen((prev) => !prev);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
onPointerCancel: (event) => {
|
|
90
|
+
props.onPointerCancel?.(event);
|
|
91
|
+
const tap = tapRef.current;
|
|
92
|
+
if (tap !== null && event.pointerId === tap.pointerId) {
|
|
93
|
+
tapRef.current = null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
});
|
|
29
99
|
DropdownMenuTrigger.displayName = "DropdownMenuTrigger";
|
|
30
100
|
const DropdownMenuContent = React__namespace.forwardRef(
|
|
31
101
|
({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenu.cjs","sources":["../../../../src/components/DropdownMenu/DropdownMenu.tsx"],"sourcesContent":["import * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { FLOATING_CONTENT_COLLISION_PADDING } from \"../../utils/floatingContentCollisionPadding\";\n\n/** Props for the {@link DropdownMenu} root component. */\nexport interface DropdownMenuProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root> {}\n\n/** Root component that manages open/close state for a dropdown menu. */\nexport const DropdownMenu = DropdownMenuPrimitive.Root;\n\n/** Props for the {@link DropdownMenuTrigger} component. */\nexport type DropdownMenuTriggerProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Trigger\n>;\n\n/** The element that toggles the dropdown menu when clicked. */\nexport const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;\nDropdownMenuTrigger.displayName = \"DropdownMenuTrigger\";\n\n/** Props for the {@link DropdownMenuContent} component. */\nexport interface DropdownMenuContentProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> {}\n\n/**\n * The positioned content panel rendered inside a portal.\n *\n * Override the portal z-index per-instance via `style={{ zIndex: 1500 }}` or\n * globally with the `--fanvue-ui-portal-z-index` CSS custom property.\n *\n * @example\n * ```tsx\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild>\n * <Button>Open</Button>\n * </DropdownMenuTrigger>\n * <DropdownMenuContent>\n * <DropdownMenuItem>Option 1</DropdownMenuItem>\n * <DropdownMenuItem>Option 2</DropdownMenuItem>\n * </DropdownMenuContent>\n * </DropdownMenu>\n * ```\n */\nexport const DropdownMenuContent = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Content>,\n DropdownMenuContentProps\n>(\n (\n {\n className,\n style,\n sideOffset = 4,\n collisionPadding = FLOATING_CONTENT_COLLISION_PADDING,\n ...props\n },\n ref,\n ) => (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n className={cn(\n \"w-max min-w-(--radix-dropdown-menu-trigger-width) max-w-(--radix-dropdown-menu-content-available-width) overflow-y-auto rounded-xs border border-neutral-alphas-200 bg-bg-primary p-1 shadow-lg\",\n \"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n \"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95\",\n \"data-[side=top]:slide-in-from-bottom-2 data-[side=bottom]:slide-in-from-top-2\",\n \"data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2\",\n className,\n )}\n style={{\n zIndex: \"var(--fanvue-ui-portal-z-index, 50)\",\n maxHeight: \"var(--radix-dropdown-menu-content-available-height)\",\n ...style,\n }}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n ),\n);\nDropdownMenuContent.displayName = \"DropdownMenuContent\";\n\n/** Props for the {@link DropdownMenuGroup} component. */\nexport type DropdownMenuGroupProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Group\n>;\n\n/** Groups related menu items. Accepts an optional `DropdownMenuLabel`. */\nexport const DropdownMenuGroup = DropdownMenuPrimitive.Group;\nDropdownMenuGroup.displayName = \"DropdownMenuGroup\";\n\n/** Props for the {@link DropdownMenuLabel} component. */\nexport interface DropdownMenuLabelProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> {}\n\n/** A label for a group of items. Not focusable or selectable. */\nexport const DropdownMenuLabel = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Label>,\n DropdownMenuLabelProps\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn(\"typography-medium-body-xs px-2 py-1.5 text-content-secondary\", className)}\n {...props}\n />\n));\nDropdownMenuLabel.displayName = \"DropdownMenuLabel\";\n\n/** Available sizes for a dropdown menu item. */\nexport type DropdownMenuItemSize = \"sm\" | \"md\";\n\nexport interface DropdownMenuItemProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> {\n /** Height of the menu item row. @default \"sm\" */\n size?: DropdownMenuItemSize;\n /** Whether the item uses destructive (error) styling. */\n destructive?: boolean;\n /** Icon rendered before the label. */\n leadingIcon?: React.ReactNode;\n /** Icon rendered after the label. */\n trailingIcon?: React.ReactNode;\n /** Whether the item is in a selected state. */\n selected?: boolean;\n}\n\n/**\n * An individual item within a {@link DropdownMenuContent}.\n *\n * @example\n * ```tsx\n * <DropdownMenuItem>Edit profile</DropdownMenuItem>\n * <DropdownMenuItem destructive>Delete</DropdownMenuItem>\n * <DropdownMenuItem leadingIcon={<EditIcon />}>Edit</DropdownMenuItem>\n *\n * // As a link\n * <DropdownMenuItem asChild>\n * <a href=\"/settings\">Settings</a>\n * </DropdownMenuItem>\n * ```\n */\nexport const DropdownMenuItem = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Item>,\n DropdownMenuItemProps\n>(\n (\n {\n size = \"sm\",\n destructive,\n leadingIcon,\n trailingIcon,\n selected,\n className,\n children,\n asChild,\n ...props\n },\n ref,\n ) => {\n const itemClassName = cn(\n \"flex w-full cursor-pointer items-center gap-1 rounded px-2 outline-none\",\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50\",\n \"data-[highlighted]:bg-neutral-alphas-100\",\n size === \"sm\" ? \"min-h-[34px] py-1\" : \"min-h-[40px] py-1.5\",\n size === \"sm\" ? \"typography-medium-body-sm\" : \"typography-medium-body-md\",\n destructive && \"text-error-content\",\n selected && \"bg-success-surface\",\n className,\n );\n\n if (asChild) {\n return (\n <DropdownMenuPrimitive.Item ref={ref} asChild className={itemClassName} {...props}>\n {children}\n </DropdownMenuPrimitive.Item>\n );\n }\n\n return (\n <DropdownMenuPrimitive.Item ref={ref} className={itemClassName} {...props}>\n {leadingIcon}\n <span className=\"flex-1\">{children}</span>\n {trailingIcon}\n </DropdownMenuPrimitive.Item>\n );\n },\n);\nDropdownMenuItem.displayName = \"DropdownMenuItem\";\n\n/** Props for the {@link DropdownMenuSeparator} component. */\nexport interface DropdownMenuSeparatorProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> {}\n\n/** Visual separator between groups of items. */\nexport const DropdownMenuSeparator = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Separator>,\n DropdownMenuSeparatorProps\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn(\"my-1 h-px bg-neutral-alphas-200\", className)}\n {...props}\n />\n));\nDropdownMenuSeparator.displayName = \"DropdownMenuSeparator\";\n"],"names":["DropdownMenuPrimitive","React","FLOATING_CONTENT_COLLISION_PADDING","jsx","cn","jsxs"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAUO,MAAM,eAAeA,iCAAsB;AAQ3C,MAAM,sBAAsBA,iCAAsB;AACzD,oBAAoB,cAAc;AAyB3B,MAAM,sBAAsBC,iBAAM;AAAA,EAIvC,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb,mBAAmBC,gCAAAA;AAAAA,IACnB,GAAG;AAAA,EAAA,GAEL,QAEAC,2BAAAA,IAACH,iCAAsB,QAAtB,EACC,UAAAG,2BAAAA;AAAAA,IAACH,iCAAsB;AAAA,IAAtB;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAWI,GAAAA;AAAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,OAAO;AAAA,QACL,QAAQ;AAAA,QACR,WAAW;AAAA,QACX,GAAG;AAAA,MAAA;AAAA,MAEJ,GAAG;AAAA,IAAA;AAAA,EAAA,EACN,CACF;AAEJ;AACA,oBAAoB,cAAc;AAQ3B,MAAM,oBAAoBJ,iCAAsB;AACvD,kBAAkB,cAAc;AAOzB,MAAM,oBAAoBC,iBAAM,WAGrC,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QAC1BE,2BAAAA;AAAAA,EAACH,iCAAsB;AAAA,EAAtB;AAAA,IACC;AAAA,IACA,WAAWI,GAAAA,GAAG,gEAAgE,SAAS;AAAA,IACtF,GAAG;AAAA,EAAA;AACN,CACD;AACD,kBAAkB,cAAc;AAkCzB,MAAM,mBAAmBH,iBAAM;AAAA,EAIpC,CACE;AAAA,IACE,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,gBAAgBG,GAAAA;AAAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,OAAO,sBAAsB;AAAA,MACtC,SAAS,OAAO,8BAA8B;AAAA,MAC9C,eAAe;AAAA,MACf,YAAY;AAAA,MACZ;AAAA,IAAA;AAGF,QAAI,SAAS;AACX,aACED,+BAACH,iCAAsB,MAAtB,EAA2B,KAAU,SAAO,MAAC,WAAW,eAAgB,GAAG,OACzE,SAAA,CACH;AAAA,IAEJ;AAEA,WACEK,2BAAAA,KAACL,iCAAsB,MAAtB,EAA2B,KAAU,WAAW,eAAgB,GAAG,OACjE,UAAA;AAAA,MAAA;AAAA,MACDG,2BAAAA,IAAC,QAAA,EAAK,WAAU,UAAU,SAAA,CAAS;AAAA,MAClC;AAAA,IAAA,GACH;AAAA,EAEJ;AACF;AACA,iBAAiB,cAAc;AAOxB,MAAM,wBAAwBF,iBAAM,WAGzC,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QAC1BE,2BAAAA;AAAAA,EAACH,iCAAsB;AAAA,EAAtB;AAAA,IACC;AAAA,IACA,WAAWI,GAAAA,GAAG,mCAAmC,SAAS;AAAA,IACzD,GAAG;AAAA,EAAA;AACN,CACD;AACD,sBAAsB,cAAc;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"DropdownMenu.cjs","sources":["../../../../src/components/DropdownMenu/DropdownMenu.tsx"],"sourcesContent":["import * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { FLOATING_CONTENT_COLLISION_PADDING } from \"../../utils/floatingContentCollisionPadding\";\n\n// Movement, in CSS px, above which a touch press-and-release counts as a drag.\nconst TAP_MOVEMENT_THRESHOLD_PX = 10;\n\ntype ActiveTap = {\n pointerId: number;\n x: number;\n y: number;\n movedPastThreshold: boolean;\n};\n\n// Lets DropdownMenuTrigger toggle the menu directly so it can gate on touch\n// movement — see radix-ui/primitives#1912.\nconst ToggleOpenContext = React.createContext<\n ((updater: (prev: boolean) => boolean) => void) | null\n>(null);\n\n/** Props for the {@link DropdownMenu} root component. */\nexport interface DropdownMenuProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root> {}\n\n/** Root component that manages open/close state for a dropdown menu. */\nexport function DropdownMenu({\n open: openProp,\n defaultOpen,\n onOpenChange,\n children,\n ...props\n}: DropdownMenuProps) {\n const [open = false, setOpen] = useControllableState({\n prop: openProp,\n defaultProp: defaultOpen ?? false,\n onChange: onOpenChange,\n });\n\n return (\n <ToggleOpenContext.Provider value={setOpen}>\n <DropdownMenuPrimitive.Root open={open} onOpenChange={setOpen} {...props}>\n {children}\n </DropdownMenuPrimitive.Root>\n </ToggleOpenContext.Provider>\n );\n}\n\n/** Props for the {@link DropdownMenuTrigger} component. */\nexport type DropdownMenuTriggerProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Trigger\n>;\n\n/**\n * The element that toggles the dropdown menu when clicked.\n *\n * On touch devices, the menu only opens if the press-and-release stays within\n * a small movement threshold. A drag that incidentally ends over the trigger\n * (common when scrolling a feed on Android Chrome) is ignored. Mouse and\n * keyboard interactions are unchanged.\n */\nexport const DropdownMenuTrigger = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Trigger>,\n DropdownMenuTriggerProps\n>((props, ref) => {\n const toggleOpen = React.useContext(ToggleOpenContext);\n const tapRef = React.useRef<ActiveTap | null>(null);\n\n // Used outside our DropdownMenu wrapper — fall through to Radix defaults.\n if (toggleOpen === null) {\n return <DropdownMenuPrimitive.Trigger {...props} ref={ref} />;\n }\n\n return (\n <DropdownMenuPrimitive.Trigger\n {...props}\n ref={ref}\n onPointerDown={(event) => {\n props.onPointerDown?.(event);\n if (event.pointerType === \"mouse\" || props.disabled) return;\n // Keep pointerup / pointercancel on this element if the finger drifts off.\n // Optional because jsdom (used in tests) doesn't implement it.\n event.currentTarget.setPointerCapture?.(event.pointerId);\n tapRef.current = {\n pointerId: event.pointerId,\n x: event.clientX,\n y: event.clientY,\n movedPastThreshold: false,\n };\n // preventDefault stops Radix's pointerdown open path via composeEventHandlers.\n event.preventDefault();\n }}\n onPointerMove={(event) => {\n props.onPointerMove?.(event);\n const tap = tapRef.current;\n if (tap === null || event.pointerId !== tap.pointerId || tap.movedPastThreshold) {\n return;\n }\n const dx = event.clientX - tap.x;\n const dy = event.clientY - tap.y;\n if (Math.hypot(dx, dy) > TAP_MOVEMENT_THRESHOLD_PX) {\n tap.movedPastThreshold = true;\n }\n }}\n onPointerUp={(event) => {\n props.onPointerUp?.(event);\n const tap = tapRef.current;\n if (tap === null || event.pointerId !== tap.pointerId) return;\n const wasDrag = tap.movedPastThreshold;\n tapRef.current = null;\n if (!wasDrag && !props.disabled) {\n toggleOpen((prev) => !prev);\n }\n }}\n onPointerCancel={(event) => {\n props.onPointerCancel?.(event);\n const tap = tapRef.current;\n if (tap !== null && event.pointerId === tap.pointerId) {\n tapRef.current = null;\n }\n }}\n />\n );\n});\nDropdownMenuTrigger.displayName = \"DropdownMenuTrigger\";\n\n/** Props for the {@link DropdownMenuContent} component. */\nexport interface DropdownMenuContentProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> {}\n\n/**\n * The positioned content panel rendered inside a portal.\n *\n * Override the portal z-index per-instance via `style={{ zIndex: 1500 }}` or\n * globally with the `--fanvue-ui-portal-z-index` CSS custom property.\n *\n * @example\n * ```tsx\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild>\n * <Button>Open</Button>\n * </DropdownMenuTrigger>\n * <DropdownMenuContent>\n * <DropdownMenuItem>Option 1</DropdownMenuItem>\n * <DropdownMenuItem>Option 2</DropdownMenuItem>\n * </DropdownMenuContent>\n * </DropdownMenu>\n * ```\n */\nexport const DropdownMenuContent = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Content>,\n DropdownMenuContentProps\n>(\n (\n {\n className,\n style,\n sideOffset = 4,\n collisionPadding = FLOATING_CONTENT_COLLISION_PADDING,\n ...props\n },\n ref,\n ) => (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n className={cn(\n \"w-max min-w-(--radix-dropdown-menu-trigger-width) max-w-(--radix-dropdown-menu-content-available-width) overflow-y-auto rounded-xs border border-neutral-alphas-200 bg-bg-primary p-1 shadow-lg\",\n \"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n \"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95\",\n \"data-[side=top]:slide-in-from-bottom-2 data-[side=bottom]:slide-in-from-top-2\",\n \"data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2\",\n className,\n )}\n style={{\n zIndex: \"var(--fanvue-ui-portal-z-index, 50)\",\n maxHeight: \"var(--radix-dropdown-menu-content-available-height)\",\n ...style,\n }}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n ),\n);\nDropdownMenuContent.displayName = \"DropdownMenuContent\";\n\n/** Props for the {@link DropdownMenuGroup} component. */\nexport type DropdownMenuGroupProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Group\n>;\n\n/** Groups related menu items. Accepts an optional `DropdownMenuLabel`. */\nexport const DropdownMenuGroup = DropdownMenuPrimitive.Group;\nDropdownMenuGroup.displayName = \"DropdownMenuGroup\";\n\n/** Props for the {@link DropdownMenuLabel} component. */\nexport interface DropdownMenuLabelProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> {}\n\n/** A label for a group of items. Not focusable or selectable. */\nexport const DropdownMenuLabel = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Label>,\n DropdownMenuLabelProps\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn(\"typography-medium-body-xs px-2 py-1.5 text-content-secondary\", className)}\n {...props}\n />\n));\nDropdownMenuLabel.displayName = \"DropdownMenuLabel\";\n\n/** Available sizes for a dropdown menu item. */\nexport type DropdownMenuItemSize = \"sm\" | \"md\";\n\nexport interface DropdownMenuItemProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> {\n /** Height of the menu item row. @default \"sm\" */\n size?: DropdownMenuItemSize;\n /** Whether the item uses destructive (error) styling. */\n destructive?: boolean;\n /** Icon rendered before the label. */\n leadingIcon?: React.ReactNode;\n /** Icon rendered after the label. */\n trailingIcon?: React.ReactNode;\n /** Whether the item is in a selected state. */\n selected?: boolean;\n}\n\n/**\n * An individual item within a {@link DropdownMenuContent}.\n *\n * @example\n * ```tsx\n * <DropdownMenuItem>Edit profile</DropdownMenuItem>\n * <DropdownMenuItem destructive>Delete</DropdownMenuItem>\n * <DropdownMenuItem leadingIcon={<EditIcon />}>Edit</DropdownMenuItem>\n *\n * // As a link\n * <DropdownMenuItem asChild>\n * <a href=\"/settings\">Settings</a>\n * </DropdownMenuItem>\n * ```\n */\nexport const DropdownMenuItem = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Item>,\n DropdownMenuItemProps\n>(\n (\n {\n size = \"sm\",\n destructive,\n leadingIcon,\n trailingIcon,\n selected,\n className,\n children,\n asChild,\n ...props\n },\n ref,\n ) => {\n const itemClassName = cn(\n \"flex w-full cursor-pointer items-center gap-1 rounded px-2 outline-none\",\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50\",\n \"data-[highlighted]:bg-neutral-alphas-100\",\n size === \"sm\" ? \"min-h-[34px] py-1\" : \"min-h-[40px] py-1.5\",\n size === \"sm\" ? \"typography-medium-body-sm\" : \"typography-medium-body-md\",\n destructive && \"text-error-content\",\n selected && \"bg-success-surface\",\n className,\n );\n\n if (asChild) {\n return (\n <DropdownMenuPrimitive.Item ref={ref} asChild className={itemClassName} {...props}>\n {children}\n </DropdownMenuPrimitive.Item>\n );\n }\n\n return (\n <DropdownMenuPrimitive.Item ref={ref} className={itemClassName} {...props}>\n {leadingIcon}\n <span className=\"flex-1\">{children}</span>\n {trailingIcon}\n </DropdownMenuPrimitive.Item>\n );\n },\n);\nDropdownMenuItem.displayName = \"DropdownMenuItem\";\n\n/** Props for the {@link DropdownMenuSeparator} component. */\nexport interface DropdownMenuSeparatorProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> {}\n\n/** Visual separator between groups of items. */\nexport const DropdownMenuSeparator = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Separator>,\n DropdownMenuSeparatorProps\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn(\"my-1 h-px bg-neutral-alphas-200\", className)}\n {...props}\n />\n));\nDropdownMenuSeparator.displayName = \"DropdownMenuSeparator\";\n"],"names":["React","useControllableState","jsx","DropdownMenuPrimitive","FLOATING_CONTENT_COLLISION_PADDING","cn","jsxs"],"mappings":"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"}
|
|
@@ -6,6 +6,7 @@ const PopoverPrimitive = require("@radix-ui/react-popover");
|
|
|
6
6
|
const React = require("react");
|
|
7
7
|
const cn = require("../../utils/cn.cjs");
|
|
8
8
|
const floatingContentCollisionPadding = require("../../utils/floatingContentCollisionPadding.cjs");
|
|
9
|
+
const useSuppressClickAfterDrag = require("../../utils/useSuppressClickAfterDrag.cjs");
|
|
9
10
|
const Button = require("../Button/Button.cjs");
|
|
10
11
|
function _interopNamespaceDefault(e) {
|
|
11
12
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
@@ -26,7 +27,8 @@ function _interopNamespaceDefault(e) {
|
|
|
26
27
|
const PopoverPrimitive__namespace = /* @__PURE__ */ _interopNamespaceDefault(PopoverPrimitive);
|
|
27
28
|
const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
|
|
28
29
|
const InfoBox = PopoverPrimitive__namespace.Root;
|
|
29
|
-
const InfoBoxTrigger = PopoverPrimitive__namespace.Trigger;
|
|
30
|
+
const InfoBoxTrigger = React__namespace.forwardRef((props, ref) => /* @__PURE__ */ jsxRuntime.jsx(PopoverPrimitive__namespace.Trigger, { ref, ...useSuppressClickAfterDrag.useSuppressClickAfterDrag(props) }));
|
|
31
|
+
InfoBoxTrigger.displayName = "InfoBoxTrigger";
|
|
30
32
|
const ACTION_CLASSES = {
|
|
31
33
|
brand: "hover:bg-brand-primary-default/80 hover:text-content-on-brand",
|
|
32
34
|
tertiary: "text-content-primary-inverted hover:text-content-primary-inverted hover:bg-content-primary-inverted/10"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InfoBox.cjs","sources":["../../../../src/components/InfoBox/InfoBox.tsx"],"sourcesContent":["import * as PopoverPrimitive from \"@radix-ui/react-popover\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { FLOATING_CONTENT_COLLISION_PADDING } from \"../../utils/floatingContentCollisionPadding\";\nimport { Button } from \"../Button/Button\";\n\n/** Props for the {@link InfoBox} root component. */\nexport interface InfoBoxProps extends React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Root> {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n defaultOpen?: boolean;\n}\n\n/** Root component that manages open/close state for an info box. */\nexport const InfoBox = PopoverPrimitive.Root;\n\n/** Props for the {@link InfoBoxTrigger} component. */\nexport type InfoBoxTriggerProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Trigger>;\n\n
|
|
1
|
+
{"version":3,"file":"InfoBox.cjs","sources":["../../../../src/components/InfoBox/InfoBox.tsx"],"sourcesContent":["import * as PopoverPrimitive from \"@radix-ui/react-popover\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { FLOATING_CONTENT_COLLISION_PADDING } from \"../../utils/floatingContentCollisionPadding\";\nimport { useSuppressClickAfterDrag } from \"../../utils/useSuppressClickAfterDrag\";\nimport { Button } from \"../Button/Button\";\n\n/** Props for the {@link InfoBox} root component. */\nexport interface InfoBoxProps extends React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Root> {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n defaultOpen?: boolean;\n}\n\n/** Root component that manages open/close state for an info box. */\nexport const InfoBox = PopoverPrimitive.Root;\n\n/** Props for the {@link InfoBoxTrigger} component. */\nexport type InfoBoxTriggerProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Trigger>;\n\n/**\n * The element that triggers the info box on click.\n *\n * On touch / pen, a press-and-release that crosses a small movement threshold\n * is treated as a drag and the resulting synthetic click is suppressed —\n * defends against Android Chrome opening the popover on a scroll-drag-end.\n */\nexport const InfoBoxTrigger = React.forwardRef<\n React.ComponentRef<typeof PopoverPrimitive.Trigger>,\n InfoBoxTriggerProps\n>((props, ref) => <PopoverPrimitive.Trigger ref={ref} {...useSuppressClickAfterDrag(props)} />);\nInfoBoxTrigger.displayName = \"InfoBoxTrigger\";\n\n/** Action button with a label and click handler. */\ninterface InfoBoxButtonAction {\n label: string;\n onClick?: () => void;\n}\n\n/** Custom element rendered in place of the default action button. */\ninterface InfoBoxElementAction {\n element: React.ReactNode;\n}\n\n/** Action configuration for {@link InfoBoxContent}. */\nexport type InfoBoxAction = InfoBoxButtonAction | InfoBoxElementAction;\n\nexport interface InfoBoxContentProps\n extends React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> {\n /** Whether to show the directional arrow pointer. @default true */\n showArrow?: boolean;\n /** Heading text rendered at the top of the info box. */\n heading?: React.ReactNode;\n /** Icon element displayed to the left of the heading. */\n icon?: React.ReactNode;\n /** Pill or badge element displayed to the right of the heading. */\n pill?: React.ReactNode;\n /** Primary action button (brand green). */\n primaryAction?: InfoBoxAction;\n /** Secondary action button (ghost). */\n secondaryAction?: InfoBoxAction;\n}\n\nconst ACTION_CLASSES: Record<\"brand\" | \"tertiary\", string> = {\n brand: \"hover:bg-brand-primary-default/80 hover:text-content-on-brand\",\n tertiary:\n \"text-content-primary-inverted hover:text-content-primary-inverted hover:bg-content-primary-inverted/10\",\n};\n\nconst ActionButton = ({\n action,\n variant,\n}: {\n action: InfoBoxAction;\n variant: \"brand\" | \"tertiary\";\n}) =>\n \"element\" in action ? (\n action.element\n ) : (\n <Button variant={variant} onClick={action.onClick} className={ACTION_CLASSES[variant]}>\n {action.label}\n </Button>\n );\n\nexport const InfoBoxContent = React.forwardRef<\n React.ComponentRef<typeof PopoverPrimitive.Content>,\n InfoBoxContentProps\n>(\n (\n {\n className,\n showArrow = true,\n sideOffset = 8,\n heading,\n icon,\n pill,\n primaryAction,\n secondaryAction,\n children,\n style,\n onOpenAutoFocus,\n collisionPadding = FLOATING_CONTENT_COLLISION_PADDING,\n ...props\n },\n ref,\n ) => {\n const hasHeader = icon !== undefined || heading !== undefined || pill !== undefined;\n const hasActions = primaryAction !== undefined || secondaryAction !== undefined;\n const headingId = React.useId();\n\n return (\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n className={cn(\n \"typography-regular-body-md max-w-[280px] overflow-hidden rounded-md border border-white/20 bg-surface-primary-inverted p-4 text-content-primary-inverted shadow-[0px_2px_4px_0px_rgba(17,24,39,0.08)]\",\n className,\n )}\n align=\"center\"\n aria-labelledby={heading ? headingId : undefined}\n arrowPadding={12}\n onOpenAutoFocus={(e) => {\n // Prevent auto-focus stealing when opening — content is supplementary, not a dialog.\n e.preventDefault();\n onOpenAutoFocus?.(e);\n }}\n {...props}\n >\n <div className=\"flex flex-col gap-3\">\n {hasHeader && (\n <div className=\"flex items-center gap-3\">\n {icon && <div className=\"size-5 shrink-0\">{icon}</div>}\n {heading && (\n <p\n id={headingId}\n className=\"typography-semibold-body-lg min-w-0 flex-1 text-content-primary-inverted\"\n >\n {heading}\n </p>\n )}\n {pill && <div className=\"shrink-0\">{pill}</div>}\n </div>\n )}\n {children && (\n <div className=\"typography-regular-body-md text-content-primary-inverted\">\n {children}\n </div>\n )}\n {hasActions && (\n <div className=\"flex items-center gap-1\">\n {primaryAction && <ActionButton action={primaryAction} variant=\"brand\" />}\n {secondaryAction && <ActionButton action={secondaryAction} variant=\"tertiary\" />}\n </div>\n )}\n </div>\n {showArrow && (\n <PopoverPrimitive.Arrow\n className={\n \"-translate-y-px! fill-surface-primary-inverted stroke-2 stroke-surface-primary-inverted\"\n }\n width={12}\n height={6}\n />\n )}\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n );\n },\n);\nInfoBoxContent.displayName = \"InfoBoxContent\";\n"],"names":["PopoverPrimitive","React","jsx","useSuppressClickAfterDrag","Button","FLOATING_CONTENT_COLLISION_PADDING","jsxs","cn"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAeO,MAAM,UAAUA,4BAAiB;AAYjC,MAAM,iBAAiBC,iBAAM,WAGlC,CAAC,OAAO,QAAQC,2BAAAA,IAACF,4BAAiB,SAAjB,EAAyB,KAAW,GAAGG,0BAAAA,0BAA0B,KAAK,GAAG,CAAE;AAC9F,eAAe,cAAc;AAgC7B,MAAM,iBAAuD;AAAA,EAC3D,OAAO;AAAA,EACP,UACE;AACJ;AAEA,MAAM,eAAe,CAAC;AAAA,EACpB;AAAA,EACA;AACF,MAIE,aAAa,SACX,OAAO,yCAENC,OAAAA,QAAA,EAAO,SAAkB,SAAS,OAAO,SAAS,WAAW,eAAe,OAAO,GACjF,iBAAO,OACV;AAGG,MAAM,iBAAiBH,iBAAM;AAAA,EAIlC,CACE;AAAA,IACE;AAAA,IACA,YAAY;AAAA,IACZ,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmBI,gCAAAA;AAAAA,IACnB,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,YAAY,SAAS,UAAa,YAAY,UAAa,SAAS;AAC1E,UAAM,aAAa,kBAAkB,UAAa,oBAAoB;AACtE,UAAM,YAAYJ,iBAAM,MAAA;AAExB,WACEC,2BAAAA,IAACF,4BAAiB,QAAjB,EACC,UAAAM,2BAAAA;AAAAA,MAACN,4BAAiB;AAAA,MAAjB;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO,EAAE,QAAQ,uCAAuC,GAAG,MAAA;AAAA,QAC3D,WAAWO,GAAAA;AAAAA,UACT;AAAA,UACA;AAAA,QAAA;AAAA,QAEF,OAAM;AAAA,QACN,mBAAiB,UAAU,YAAY;AAAA,QACvC,cAAc;AAAA,QACd,iBAAiB,CAAC,MAAM;AAEtB,YAAE,eAAA;AACF,4BAAkB,CAAC;AAAA,QACrB;AAAA,QACC,GAAG;AAAA,QAEJ,UAAA;AAAA,UAAAD,2BAAAA,KAAC,OAAA,EAAI,WAAU,uBACZ,UAAA;AAAA,YAAA,aACCA,2BAAAA,KAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,cAAA,QAAQJ,2BAAAA,IAAC,OAAA,EAAI,WAAU,mBAAmB,UAAA,MAAK;AAAA,cAC/C,WACCA,2BAAAA;AAAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,IAAI;AAAA,kBACJ,WAAU;AAAA,kBAET,UAAA;AAAA,gBAAA;AAAA,cAAA;AAAA,cAGJ,QAAQA,2BAAAA,IAAC,OAAA,EAAI,WAAU,YAAY,UAAA,KAAA,CAAK;AAAA,YAAA,GAC3C;AAAA,YAED,YACCA,2BAAAA,IAAC,OAAA,EAAI,WAAU,4DACZ,UACH;AAAA,YAED,cACCI,2BAAAA,KAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,cAAA,iBAAiBJ,2BAAAA,IAAC,cAAA,EAAa,QAAQ,eAAe,SAAQ,SAAQ;AAAA,cACtE,mBAAmBA,2BAAAA,IAAC,cAAA,EAAa,QAAQ,iBAAiB,SAAQ,WAAA,CAAW;AAAA,YAAA,EAAA,CAChF;AAAA,UAAA,GAEJ;AAAA,UACC,aACCA,2BAAAA;AAAAA,YAACF,4BAAiB;AAAA,YAAjB;AAAA,cACC,WACE;AAAA,cAEF,OAAO;AAAA,cACP,QAAQ;AAAA,YAAA;AAAA,UAAA;AAAA,QACV;AAAA,MAAA;AAAA,IAAA,GAGN;AAAA,EAEJ;AACF;AACA,eAAe,cAAc;;;;"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
4
|
+
const React = require("react");
|
|
5
|
+
function _interopNamespaceDefault(e) {
|
|
6
|
+
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
7
|
+
if (e) {
|
|
8
|
+
for (const k in e) {
|
|
9
|
+
if (k !== "default") {
|
|
10
|
+
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
11
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
get: () => e[k]
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
n.default = e;
|
|
19
|
+
return Object.freeze(n);
|
|
20
|
+
}
|
|
21
|
+
const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
|
|
22
|
+
const TAP_MOVEMENT_THRESHOLD_PX = 10;
|
|
23
|
+
function useSuppressClickAfterDrag(props) {
|
|
24
|
+
const tapRef = React__namespace.useRef(null);
|
|
25
|
+
return {
|
|
26
|
+
...props,
|
|
27
|
+
onPointerDown(event) {
|
|
28
|
+
props.onPointerDown?.(event);
|
|
29
|
+
if (event.pointerType === "mouse") {
|
|
30
|
+
tapRef.current = null;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
34
|
+
tapRef.current = {
|
|
35
|
+
pointerId: event.pointerId,
|
|
36
|
+
x: event.clientX,
|
|
37
|
+
y: event.clientY,
|
|
38
|
+
movedPastThreshold: false
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
onPointerMove(event) {
|
|
42
|
+
props.onPointerMove?.(event);
|
|
43
|
+
const tap = tapRef.current;
|
|
44
|
+
if (tap === null || event.pointerId !== tap.pointerId || tap.movedPastThreshold) return;
|
|
45
|
+
const dx = event.clientX - tap.x;
|
|
46
|
+
const dy = event.clientY - tap.y;
|
|
47
|
+
if (Math.hypot(dx, dy) > TAP_MOVEMENT_THRESHOLD_PX) {
|
|
48
|
+
tap.movedPastThreshold = true;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
onPointerCancel(event) {
|
|
52
|
+
props.onPointerCancel?.(event);
|
|
53
|
+
const tap = tapRef.current;
|
|
54
|
+
if (tap !== null && event.pointerId === tap.pointerId) {
|
|
55
|
+
tapRef.current = null;
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
onClick(event) {
|
|
59
|
+
const tap = tapRef.current;
|
|
60
|
+
tapRef.current = null;
|
|
61
|
+
if (tap?.movedPastThreshold) {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
event.stopPropagation();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
props.onClick?.(event);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
exports.useSuppressClickAfterDrag = useSuppressClickAfterDrag;
|
|
71
|
+
//# sourceMappingURL=useSuppressClickAfterDrag.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSuppressClickAfterDrag.cjs","sources":["../../../src/utils/useSuppressClickAfterDrag.ts"],"sourcesContent":["import * as React from \"react\";\n\nconst TAP_MOVEMENT_THRESHOLD_PX = 10;\n\ntype ActiveTap = {\n pointerId: number;\n x: number;\n y: number;\n movedPastThreshold: boolean;\n};\n\ntype GatedHandlers = {\n onPointerDown?: React.PointerEventHandler;\n onPointerMove?: React.PointerEventHandler;\n onPointerCancel?: React.PointerEventHandler;\n onClick?: React.MouseEventHandler;\n};\n\n/**\n * Composes the consumer's pointer/click handlers with a touch-drag tracker\n * that suppresses the synthetic click an Android Chrome scroll-drag-end can\n * dispatch on a click-based Radix trigger (Popover / Dialog / Drawer).\n *\n * On touch / pen input, if the press-and-release crosses a movement threshold,\n * the subsequent `click` is preventDefault'd — short-circuiting Radix's own\n * `onOpenToggle` via `composeEventHandlers` — and stopPropagation'd so it\n * doesn't bubble to ancestors. Mouse input passes through unchanged because\n * mouse never triggers the Android scroll-drag bug class.\n *\n * Related: radix-ui/primitives#1912 (DropdownMenu pointerdown variant) and\n * #2702 (DismissableLayer touch dismiss). For the DropdownMenu pointerdown\n * variant see `components/DropdownMenu/DropdownMenu.tsx`.\n */\nexport function useSuppressClickAfterDrag<P extends GatedHandlers>(props: P): P {\n const tapRef = React.useRef<ActiveTap | null>(null);\n\n return {\n ...props,\n onPointerDown(event) {\n props.onPointerDown?.(event);\n if (event.pointerType === \"mouse\") {\n tapRef.current = null;\n return;\n }\n // Keep pointermove on this element if the finger drifts off.\n // Optional because jsdom (used in tests) doesn't implement it.\n event.currentTarget.setPointerCapture?.(event.pointerId);\n tapRef.current = {\n pointerId: event.pointerId,\n x: event.clientX,\n y: event.clientY,\n movedPastThreshold: false,\n };\n },\n onPointerMove(event) {\n props.onPointerMove?.(event);\n const tap = tapRef.current;\n if (tap === null || event.pointerId !== tap.pointerId || tap.movedPastThreshold) return;\n const dx = event.clientX - tap.x;\n const dy = event.clientY - tap.y;\n if (Math.hypot(dx, dy) > TAP_MOVEMENT_THRESHOLD_PX) {\n tap.movedPastThreshold = true;\n }\n },\n onPointerCancel(event) {\n props.onPointerCancel?.(event);\n const tap = tapRef.current;\n if (tap !== null && event.pointerId === tap.pointerId) {\n tapRef.current = null;\n }\n },\n onClick(event) {\n const tap = tapRef.current;\n tapRef.current = null;\n if (tap?.movedPastThreshold) {\n // preventDefault stops Radix's onClick → onOpenToggle via\n // composeEventHandlers. stopPropagation prevents the synthetic click\n // from bubbling to ancestor click handlers.\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n props.onClick?.(event);\n },\n };\n}\n"],"names":["React"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAEA,MAAM,4BAA4B;AA+B3B,SAAS,0BAAmD,OAAa;AAC9E,QAAM,SAASA,iBAAM,OAAyB,IAAI;AAElD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,cAAc,OAAO;AACnB,YAAM,gBAAgB,KAAK;AAC3B,UAAI,MAAM,gBAAgB,SAAS;AACjC,eAAO,UAAU;AACjB;AAAA,MACF;AAGA,YAAM,cAAc,oBAAoB,MAAM,SAAS;AACvD,aAAO,UAAU;AAAA,QACf,WAAW,MAAM;AAAA,QACjB,GAAG,MAAM;AAAA,QACT,GAAG,MAAM;AAAA,QACT,oBAAoB;AAAA,MAAA;AAAA,IAExB;AAAA,IACA,cAAc,OAAO;AACnB,YAAM,gBAAgB,KAAK;AAC3B,YAAM,MAAM,OAAO;AACnB,UAAI,QAAQ,QAAQ,MAAM,cAAc,IAAI,aAAa,IAAI,mBAAoB;AACjF,YAAM,KAAK,MAAM,UAAU,IAAI;AAC/B,YAAM,KAAK,MAAM,UAAU,IAAI;AAC/B,UAAI,KAAK,MAAM,IAAI,EAAE,IAAI,2BAA2B;AAClD,YAAI,qBAAqB;AAAA,MAC3B;AAAA,IACF;AAAA,IACA,gBAAgB,OAAO;AACrB,YAAM,kBAAkB,KAAK;AAC7B,YAAM,MAAM,OAAO;AACnB,UAAI,QAAQ,QAAQ,MAAM,cAAc,IAAI,WAAW;AACrD,eAAO,UAAU;AAAA,MACnB;AAAA,IACF;AAAA,IACA,QAAQ,OAAO;AACb,YAAM,MAAM,OAAO;AACnB,aAAO,UAAU;AACjB,UAAI,KAAK,oBAAoB;AAI3B,cAAM,eAAA;AACN,cAAM,gBAAA;AACN;AAAA,MACF;AACA,YAAM,UAAU,KAAK;AAAA,IACvB;AAAA,EAAA;AAEJ;;"}
|
|
@@ -3,11 +3,13 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { cn } from "../../utils/cn.mjs";
|
|
6
|
+
import { useSuppressClickAfterDrag } from "../../utils/useSuppressClickAfterDrag.mjs";
|
|
6
7
|
import { IconButton } from "../IconButton/IconButton.mjs";
|
|
7
8
|
import { ArrowLeftIcon } from "../Icons/ArrowLeftIcon.mjs";
|
|
8
9
|
import { CloseIcon } from "../Icons/CloseIcon.mjs";
|
|
9
10
|
const Dialog = DialogPrimitive.Root;
|
|
10
|
-
const DialogTrigger = DialogPrimitive.Trigger;
|
|
11
|
+
const DialogTrigger = React.forwardRef((props, ref) => /* @__PURE__ */ jsx(DialogPrimitive.Trigger, { ref, ...useSuppressClickAfterDrag(props) }));
|
|
12
|
+
DialogTrigger.displayName = "DialogTrigger";
|
|
11
13
|
const DialogClose = DialogPrimitive.Close;
|
|
12
14
|
const DialogOverlay = React.forwardRef(({ className, style, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
13
15
|
DialogPrimitive.Overlay,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.mjs","sources":["../../../src/components/Dialog/Dialog.tsx"],"sourcesContent":["import * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { ArrowLeftIcon } from \"../Icons/ArrowLeftIcon\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\n\n/** Props for the {@link Dialog} root component. */\nexport interface DialogProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Root> {\n /** Controlled open state. When provided, you must also supply `onOpenChange`. */\n open?: boolean;\n /** Called when the open state changes. Required when `open` is controlled. */\n onOpenChange?: (open: boolean) => void;\n /** The open state of the dialog when it is initially rendered (uncontrolled). */\n defaultOpen?: boolean;\n}\n\n/** Root component that manages open/close state for a dialog. */\nexport const Dialog = DialogPrimitive.Root;\n\n/** Props for the {@link DialogTrigger} component. */\nexport type DialogTriggerProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>;\n\n/** The element that opens the dialog when clicked. */\nexport const DialogTrigger = DialogPrimitive.Trigger;\n\n/** Convenience alias for Radix `Dialog.Close`. Closes the dialog when clicked. */\nexport const DialogClose = DialogPrimitive.Close;\n\n/** Props for the {@link DialogClose} component. */\nexport type DialogCloseProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>;\n\nexport interface DialogOverlayProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> {}\n\n/**\n * Semi-transparent backdrop rendered behind the dialog content.\n * Rendered inside a portal automatically by {@link DialogContent}.\n */\nexport const DialogOverlay = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Overlay>,\n DialogOverlayProps\n>(({ className, style, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n className={cn(\n \"data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 fixed inset-0 bg-bg-overlay data-[state=closed]:animate-out data-[state=open]:animate-in\",\n className,\n )}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n {...props}\n />\n));\nDialogOverlay.displayName = \"DialogOverlay\";\n\nexport interface DialogContentProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {\n /**\n * Width preset for the dialog.\n * - `\"sm\"` — 400px max-width (confirmations, simple forms)\n * - `\"md\"` — 440px max-width (default, standard dialogs)\n * - `\"lg\"` — 600px max-width (complex content, tables)\n *\n * @default \"md\"\n */\n size?: \"sm\" | \"md\" | \"lg\";\n /** When true, renders overlay automatically. @default true */\n overlay?: boolean;\n}\n\nconst SIZE_CLASSES: Record<NonNullable<DialogContentProps[\"size\"]>, string> = {\n sm: \"sm:max-w-[400px]\",\n md: \"sm:max-w-[440px]\",\n lg: \"sm:max-w-[600px]\",\n};\n\n/**\n * The dialog panel rendered inside a portal. Includes the overlay by default.\n *\n * On mobile viewports (<640px), the dialog slides up from the bottom as a sheet\n * with top-only border radius. On larger viewports it renders centered with\n * full border radius.\n *\n * @example\n * ```tsx\n * <Dialog>\n * <DialogTrigger asChild>\n * <Button>Open</Button>\n * </DialogTrigger>\n * <DialogContent>\n * <DialogHeader>\n * <DialogTitle>Title</DialogTitle>\n * </DialogHeader>\n * <DialogBody>Content here</DialogBody>\n * <DialogFooter>\n * <DialogClose asChild>\n * <Button variant=\"secondary\">Cancel</Button>\n * </DialogClose>\n * <Button>Accept</Button>\n * </DialogFooter>\n * </DialogContent>\n * </Dialog>\n * ```\n */\nexport const DialogContent = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Content>,\n DialogContentProps\n>(({ className, children, size = \"md\", overlay = true, style, onOpenAutoFocus, ...props }, ref) => (\n <DialogPrimitive.Portal>\n {overlay && <DialogOverlay />}\n <DialogPrimitive.Content\n ref={ref}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n onOpenAutoFocus={(e) => {\n if (onOpenAutoFocus) {\n onOpenAutoFocus(e);\n return;\n }\n e.preventDefault();\n (e.currentTarget as HTMLElement).focus();\n }}\n className={cn(\n // Base\n \"fixed flex flex-col overflow-hidden bg-bg-primary shadow-lg focus:outline-none dark:bg-surface-primary\",\n // Mobile: bottom sheet\n \"inset-x-0 bottom-0 max-h-[85vh] w-full rounded-t-lg\",\n // Animation (shared)\n \"data-[state=open]:fade-in-0 data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=closed]:animate-out\",\n // Mobile: slide up from bottom\n \"data-[state=open]:slide-in-from-bottom-full\",\n \"data-[state=closed]:slide-out-to-bottom-full\",\n // Desktop: centered dialog\n \"sm:inset-auto sm:top-1/2 sm:left-1/2 sm:max-h-[85vh] sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-lg\",\n // Desktop: scale in/out (cancel mobile slide)\n \"sm:data-[state=open]:slide-in-from-bottom-0 sm:data-[state=open]:zoom-in-95\",\n \"sm:data-[state=closed]:slide-out-to-bottom-0 sm:data-[state=closed]:zoom-out-95\",\n // Duration\n \"duration-200\",\n // Size\n SIZE_CLASSES[size],\n className,\n )}\n {...props}\n >\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n));\nDialogContent.displayName = \"DialogContent\";\n\nexport interface DialogHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Show the close (X) button in the header. @default true */\n showClose?: boolean;\n /** Show a back arrow button on the left side. Defaults to `true` when `onBack` is provided. */\n showBack?: boolean;\n /** Called when the back button is clicked. */\n onBack?: () => void;\n /** Accessible label for the back button. @default \"Go back\" */\n backLabel?: string;\n /** Accessible label for the close button. @default \"Close\" */\n closeLabel?: string;\n}\n\n/**\n * Header bar for the dialog. Renders the title with an optional back arrow\n * and close button.\n *\n * @example\n * ```tsx\n * <DialogHeader>\n * <DialogTitle>Settings</DialogTitle>\n * </DialogHeader>\n *\n * <DialogHeader showBack onBack={() => setStep(0)}>\n * <DialogTitle>Step 2</DialogTitle>\n * </DialogHeader>\n * ```\n */\nexport const DialogHeader = React.forwardRef<HTMLDivElement, DialogHeaderProps>(\n (\n {\n className,\n children,\n showClose = true,\n showBack,\n onBack,\n backLabel = \"Go back\",\n closeLabel = \"Close\",\n ...props\n },\n ref,\n ) => {\n const shouldShowBack = showBack ?? !!onBack;\n\n return (\n <div\n ref={ref}\n className={cn(\"flex h-16 shrink-0 items-center gap-2 px-6 py-4\", className)}\n {...props}\n >\n {shouldShowBack && (\n <IconButton\n variant=\"tertiary\"\n size=\"32\"\n icon={<ArrowLeftIcon />}\n onClick={onBack}\n disabled={!onBack}\n aria-label={backLabel}\n />\n )}\n <div className=\"min-w-0 flex-1\">{children}</div>\n {showClose && (\n <DialogPrimitive.Close asChild>\n <IconButton variant=\"tertiary\" size=\"32\" icon={<CloseIcon />} aria-label={closeLabel} />\n </DialogPrimitive.Close>\n )}\n </div>\n );\n },\n);\nDialogHeader.displayName = \"DialogHeader\";\n\n/** Props for the {@link DialogTitle} component. */\nexport type DialogTitleProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>;\n\n/**\n * Accessible title for the dialog. Must be rendered inside {@link DialogHeader}\n * or directly within {@link DialogContent}.\n */\nexport const DialogTitle = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Title>,\n DialogTitleProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\"typography-bold-heading-xs truncate text-content-primary\", className)}\n {...props}\n />\n));\nDialogTitle.displayName = \"DialogTitle\";\n\n/** Props for the {@link DialogDescription} component. */\nexport type DialogDescriptionProps = React.ComponentPropsWithoutRef<\n typeof DialogPrimitive.Description\n>;\n\n/** Accessible description for the dialog. Rendered as secondary text. */\nexport const DialogDescription = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Description>,\n DialogDescriptionProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn(\"typography-regular-body-lg text-content-secondary\", className)}\n {...props}\n />\n));\nDialogDescription.displayName = \"DialogDescription\";\n\nexport interface DialogBodyProps extends React.HTMLAttributes<HTMLDivElement> {}\n\n/**\n * Scrollable content area (slot) between the header and footer.\n * Grows to fill available space and scrolls when content overflows.\n */\nexport const DialogBody = React.forwardRef<HTMLDivElement, DialogBodyProps>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex-1 overflow-y-auto px-6 py-4\", className)} {...props} />\n ),\n);\nDialogBody.displayName = \"DialogBody\";\n\nexport interface DialogFooterProps extends React.HTMLAttributes<HTMLDivElement> {}\n\n/**\n * Footer bar for the dialog. Typically contains action buttons.\n * Children are laid out in a horizontal row with equal flex-basis.\n */\nexport const DialogFooter = React.forwardRef<HTMLDivElement, DialogFooterProps>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n \"flex shrink-0 items-center gap-3 px-6 pt-3 pb-6\",\n \"[&>*]:min-w-0 [&>*]:flex-1\",\n className,\n )}\n {...props}\n />\n ),\n);\nDialogFooter.displayName = \"DialogFooter\";\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Dialog.mjs","sources":["../../../src/components/Dialog/Dialog.tsx"],"sourcesContent":["import * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useSuppressClickAfterDrag } from \"../../utils/useSuppressClickAfterDrag\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { ArrowLeftIcon } from \"../Icons/ArrowLeftIcon\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\n\n/** Props for the {@link Dialog} root component. */\nexport interface DialogProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Root> {\n /** Controlled open state. When provided, you must also supply `onOpenChange`. */\n open?: boolean;\n /** Called when the open state changes. Required when `open` is controlled. */\n onOpenChange?: (open: boolean) => void;\n /** The open state of the dialog when it is initially rendered (uncontrolled). */\n defaultOpen?: boolean;\n}\n\n/** Root component that manages open/close state for a dialog. */\nexport const Dialog = DialogPrimitive.Root;\n\n/** Props for the {@link DialogTrigger} component. */\nexport type DialogTriggerProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>;\n\n/**\n * The element that opens the dialog when clicked.\n *\n * On touch / pen, a press-and-release that crosses a small movement threshold\n * is treated as a drag and the resulting synthetic click is suppressed —\n * defends against Android Chrome opening the dialog on a scroll-drag-end.\n */\nexport const DialogTrigger = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Trigger>,\n DialogTriggerProps\n>((props, ref) => <DialogPrimitive.Trigger ref={ref} {...useSuppressClickAfterDrag(props)} />);\nDialogTrigger.displayName = \"DialogTrigger\";\n\n/** Convenience alias for Radix `Dialog.Close`. Closes the dialog when clicked. */\nexport const DialogClose = DialogPrimitive.Close;\n\n/** Props for the {@link DialogClose} component. */\nexport type DialogCloseProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>;\n\nexport interface DialogOverlayProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> {}\n\n/**\n * Semi-transparent backdrop rendered behind the dialog content.\n * Rendered inside a portal automatically by {@link DialogContent}.\n */\nexport const DialogOverlay = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Overlay>,\n DialogOverlayProps\n>(({ className, style, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n className={cn(\n \"data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 fixed inset-0 bg-bg-overlay data-[state=closed]:animate-out data-[state=open]:animate-in\",\n className,\n )}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n {...props}\n />\n));\nDialogOverlay.displayName = \"DialogOverlay\";\n\nexport interface DialogContentProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {\n /**\n * Width preset for the dialog.\n * - `\"sm\"` — 400px max-width (confirmations, simple forms)\n * - `\"md\"` — 440px max-width (default, standard dialogs)\n * - `\"lg\"` — 600px max-width (complex content, tables)\n *\n * @default \"md\"\n */\n size?: \"sm\" | \"md\" | \"lg\";\n /** When true, renders overlay automatically. @default true */\n overlay?: boolean;\n}\n\nconst SIZE_CLASSES: Record<NonNullable<DialogContentProps[\"size\"]>, string> = {\n sm: \"sm:max-w-[400px]\",\n md: \"sm:max-w-[440px]\",\n lg: \"sm:max-w-[600px]\",\n};\n\n/**\n * The dialog panel rendered inside a portal. Includes the overlay by default.\n *\n * On mobile viewports (<640px), the dialog slides up from the bottom as a sheet\n * with top-only border radius. On larger viewports it renders centered with\n * full border radius.\n *\n * @example\n * ```tsx\n * <Dialog>\n * <DialogTrigger asChild>\n * <Button>Open</Button>\n * </DialogTrigger>\n * <DialogContent>\n * <DialogHeader>\n * <DialogTitle>Title</DialogTitle>\n * </DialogHeader>\n * <DialogBody>Content here</DialogBody>\n * <DialogFooter>\n * <DialogClose asChild>\n * <Button variant=\"secondary\">Cancel</Button>\n * </DialogClose>\n * <Button>Accept</Button>\n * </DialogFooter>\n * </DialogContent>\n * </Dialog>\n * ```\n */\nexport const DialogContent = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Content>,\n DialogContentProps\n>(({ className, children, size = \"md\", overlay = true, style, onOpenAutoFocus, ...props }, ref) => (\n <DialogPrimitive.Portal>\n {overlay && <DialogOverlay />}\n <DialogPrimitive.Content\n ref={ref}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n onOpenAutoFocus={(e) => {\n if (onOpenAutoFocus) {\n onOpenAutoFocus(e);\n return;\n }\n e.preventDefault();\n (e.currentTarget as HTMLElement).focus();\n }}\n className={cn(\n // Base\n \"fixed flex flex-col overflow-hidden bg-bg-primary shadow-lg focus:outline-none dark:bg-surface-primary\",\n // Mobile: bottom sheet\n \"inset-x-0 bottom-0 max-h-[85vh] w-full rounded-t-lg\",\n // Animation (shared)\n \"data-[state=open]:fade-in-0 data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=closed]:animate-out\",\n // Mobile: slide up from bottom\n \"data-[state=open]:slide-in-from-bottom-full\",\n \"data-[state=closed]:slide-out-to-bottom-full\",\n // Desktop: centered dialog\n \"sm:inset-auto sm:top-1/2 sm:left-1/2 sm:max-h-[85vh] sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-lg\",\n // Desktop: scale in/out (cancel mobile slide)\n \"sm:data-[state=open]:slide-in-from-bottom-0 sm:data-[state=open]:zoom-in-95\",\n \"sm:data-[state=closed]:slide-out-to-bottom-0 sm:data-[state=closed]:zoom-out-95\",\n // Duration\n \"duration-200\",\n // Size\n SIZE_CLASSES[size],\n className,\n )}\n {...props}\n >\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n));\nDialogContent.displayName = \"DialogContent\";\n\nexport interface DialogHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Show the close (X) button in the header. @default true */\n showClose?: boolean;\n /** Show a back arrow button on the left side. Defaults to `true` when `onBack` is provided. */\n showBack?: boolean;\n /** Called when the back button is clicked. */\n onBack?: () => void;\n /** Accessible label for the back button. @default \"Go back\" */\n backLabel?: string;\n /** Accessible label for the close button. @default \"Close\" */\n closeLabel?: string;\n}\n\n/**\n * Header bar for the dialog. Renders the title with an optional back arrow\n * and close button.\n *\n * @example\n * ```tsx\n * <DialogHeader>\n * <DialogTitle>Settings</DialogTitle>\n * </DialogHeader>\n *\n * <DialogHeader showBack onBack={() => setStep(0)}>\n * <DialogTitle>Step 2</DialogTitle>\n * </DialogHeader>\n * ```\n */\nexport const DialogHeader = React.forwardRef<HTMLDivElement, DialogHeaderProps>(\n (\n {\n className,\n children,\n showClose = true,\n showBack,\n onBack,\n backLabel = \"Go back\",\n closeLabel = \"Close\",\n ...props\n },\n ref,\n ) => {\n const shouldShowBack = showBack ?? !!onBack;\n\n return (\n <div\n ref={ref}\n className={cn(\"flex h-16 shrink-0 items-center gap-2 px-6 py-4\", className)}\n {...props}\n >\n {shouldShowBack && (\n <IconButton\n variant=\"tertiary\"\n size=\"32\"\n icon={<ArrowLeftIcon />}\n onClick={onBack}\n disabled={!onBack}\n aria-label={backLabel}\n />\n )}\n <div className=\"min-w-0 flex-1\">{children}</div>\n {showClose && (\n <DialogPrimitive.Close asChild>\n <IconButton variant=\"tertiary\" size=\"32\" icon={<CloseIcon />} aria-label={closeLabel} />\n </DialogPrimitive.Close>\n )}\n </div>\n );\n },\n);\nDialogHeader.displayName = \"DialogHeader\";\n\n/** Props for the {@link DialogTitle} component. */\nexport type DialogTitleProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>;\n\n/**\n * Accessible title for the dialog. Must be rendered inside {@link DialogHeader}\n * or directly within {@link DialogContent}.\n */\nexport const DialogTitle = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Title>,\n DialogTitleProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\"typography-bold-heading-xs truncate text-content-primary\", className)}\n {...props}\n />\n));\nDialogTitle.displayName = \"DialogTitle\";\n\n/** Props for the {@link DialogDescription} component. */\nexport type DialogDescriptionProps = React.ComponentPropsWithoutRef<\n typeof DialogPrimitive.Description\n>;\n\n/** Accessible description for the dialog. Rendered as secondary text. */\nexport const DialogDescription = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Description>,\n DialogDescriptionProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn(\"typography-regular-body-lg text-content-secondary\", className)}\n {...props}\n />\n));\nDialogDescription.displayName = \"DialogDescription\";\n\nexport interface DialogBodyProps extends React.HTMLAttributes<HTMLDivElement> {}\n\n/**\n * Scrollable content area (slot) between the header and footer.\n * Grows to fill available space and scrolls when content overflows.\n */\nexport const DialogBody = React.forwardRef<HTMLDivElement, DialogBodyProps>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex-1 overflow-y-auto px-6 py-4\", className)} {...props} />\n ),\n);\nDialogBody.displayName = \"DialogBody\";\n\nexport interface DialogFooterProps extends React.HTMLAttributes<HTMLDivElement> {}\n\n/**\n * Footer bar for the dialog. Typically contains action buttons.\n * Children are laid out in a horizontal row with equal flex-basis.\n */\nexport const DialogFooter = React.forwardRef<HTMLDivElement, DialogFooterProps>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n \"flex shrink-0 items-center gap-3 px-6 pt-3 pb-6\",\n \"[&>*]:min-w-0 [&>*]:flex-1\",\n className,\n )}\n {...props}\n />\n ),\n);\nDialogFooter.displayName = \"DialogFooter\";\n"],"names":[],"mappings":"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"}
|
|
@@ -3,15 +3,18 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { cn } from "../../utils/cn.mjs";
|
|
6
|
+
import { useSuppressClickAfterDrag } from "../../utils/useSuppressClickAfterDrag.mjs";
|
|
6
7
|
import { IconButton } from "../IconButton/IconButton.mjs";
|
|
7
8
|
import { CloseIcon } from "../Icons/CloseIcon.mjs";
|
|
8
|
-
const DrawerContext = React.createContext({
|
|
9
|
+
const DrawerContext = React.createContext({
|
|
10
|
+
overlay: true
|
|
11
|
+
});
|
|
9
12
|
function Drawer({ overlay = true, modal, children, ...props }) {
|
|
10
13
|
const resolvedModal = modal ?? (overlay ? void 0 : false);
|
|
11
14
|
return /* @__PURE__ */ jsx(DrawerContext.Provider, { value: { overlay }, children: /* @__PURE__ */ jsx(DialogPrimitive.Root, { modal: resolvedModal, ...props, children }) });
|
|
12
15
|
}
|
|
13
16
|
Drawer.displayName = "Drawer";
|
|
14
|
-
const DrawerTrigger = DialogPrimitive.Trigger;
|
|
17
|
+
const DrawerTrigger = React.forwardRef((props, ref) => /* @__PURE__ */ jsx(DialogPrimitive.Trigger, { ref, ...useSuppressClickAfterDrag(props) }));
|
|
15
18
|
DrawerTrigger.displayName = "DrawerTrigger";
|
|
16
19
|
const DrawerClose = DialogPrimitive.Close;
|
|
17
20
|
DrawerClose.displayName = "DrawerClose";
|
|
@@ -51,7 +54,12 @@ const DrawerContent = React.forwardRef(
|
|
|
51
54
|
const ctx = React.useContext(DrawerContext);
|
|
52
55
|
const overlay = overlayProp ?? ctx.overlay;
|
|
53
56
|
const isHorizontal = position === "left" || position === "right";
|
|
54
|
-
const sizeClass = isHorizontal ? {
|
|
57
|
+
const sizeClass = isHorizontal ? {
|
|
58
|
+
sm: "max-w-sm",
|
|
59
|
+
md: "max-w-md",
|
|
60
|
+
lg: "max-w-lg",
|
|
61
|
+
full: "max-w-full"
|
|
62
|
+
}[size] : {
|
|
55
63
|
sm: "max-h-[24rem]",
|
|
56
64
|
md: "max-h-[28rem]",
|
|
57
65
|
lg: "max-h-[32rem]",
|
|
@@ -63,7 +71,10 @@ const DrawerContent = React.forwardRef(
|
|
|
63
71
|
DialogPrimitive.Content,
|
|
64
72
|
{
|
|
65
73
|
ref,
|
|
66
|
-
style: {
|
|
74
|
+
style: {
|
|
75
|
+
zIndex: "calc(var(--fanvue-ui-portal-z-index, 50) + 1)",
|
|
76
|
+
...style
|
|
77
|
+
},
|
|
67
78
|
className: cn(
|
|
68
79
|
"fixed flex flex-col bg-surface-secondary shadow-lg outline-none backdrop-blur-lg",
|
|
69
80
|
"data-[state=closed]:animate-out data-[state=open]:animate-in",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Drawer.mjs","sources":["../../../src/components/Drawer/Drawer.tsx"],"sourcesContent":["import * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\n\n/** The side from which the drawer slides in. */\nexport type DrawerPosition = \"left\" | \"right\" | \"top\" | \"bottom\";\n\n/** Size presets for the drawer panel. Maps to max-width (left/right) or max-height (top/bottom). */\nexport type DrawerSize = \"sm\" | \"md\" | \"lg\" | \"full\";\n\n/**\n * Props for the {@link Drawer} root component.\n *\n * Inherits `open`, `onOpenChange`, and `defaultOpen` from Radix Dialog.Root.\n *\n * The Radix `modal` prop (default `true`) can also be passed to create a\n * non-modal drawer — useful for persistent side navigation that does not\n * block interaction with the rest of the page.\n *\n * **`overlay` behaviour:** When `overlay` is `false`, the component\n * automatically sets `modal={false}` on the underlying Radix Dialog.Root.\n * A modal dialog without a visible overlay is confusing because focus is\n * still trapped and scroll is blocked even though the page appears\n * interactive. Passing `modal={true}` explicitly will override this.\n */\nexport interface DrawerProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Root> {\n /**\n * Whether the default {@link DrawerOverlay} is rendered.\n * When `false`, `modal` is automatically set to `false` as well\n * (unless explicitly overridden) so focus-trap and scroll-lock are disabled.\n * @default true\n */\n overlay?: boolean;\n}\n\nconst DrawerContext = React.createContext<{ overlay: boolean }>({ overlay: true });\n\n/**\n * Root component that manages open/close state for a drawer.\n * Wraps Radix Dialog.Root.\n *\n * @example\n * ```tsx\n * <Drawer>\n * <DrawerTrigger>Open</DrawerTrigger>\n * <DrawerContent position=\"right\">\n * <DrawerHeader>\n * <DrawerTitle>Settings</DrawerTitle>\n * <DrawerDescription>Adjust your preferences.</DrawerDescription>\n * </DrawerHeader>\n * <p>Content goes here.</p>\n * <DrawerFooter>\n * <DrawerClose>Done</DrawerClose>\n * </DrawerFooter>\n * </DrawerContent>\n * </Drawer>\n * ```\n */\nexport function Drawer({ overlay = true, modal, children, ...props }: DrawerProps) {\n const resolvedModal = modal ?? (overlay ? undefined : false);\n return (\n <DrawerContext.Provider value={{ overlay }}>\n <DialogPrimitive.Root modal={resolvedModal} {...props}>\n {children}\n </DialogPrimitive.Root>\n </DrawerContext.Provider>\n );\n}\nDrawer.displayName = \"Drawer\";\n\n/** Props for the {@link DrawerTrigger} component. */\nexport type DrawerTriggerProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>;\n\n/** The element that opens the drawer when clicked. */\nexport const DrawerTrigger = DialogPrimitive.Trigger;\nDrawerTrigger.displayName = \"DrawerTrigger\";\n\n/** Props for the {@link DrawerClose} component. */\nexport type DrawerCloseProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>;\n\n/** Closes the drawer when clicked. Can be placed anywhere inside the drawer. */\nexport const DrawerClose = DialogPrimitive.Close;\nDrawerClose.displayName = \"DrawerClose\";\n\n/** Props for the {@link DrawerOverlay} component. */\nexport interface DrawerOverlayProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> {}\n\n/**\n * A translucent backdrop rendered behind the drawer content.\n * Clicking the overlay closes the drawer by default.\n */\nexport const DrawerOverlay = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Overlay>,\n DrawerOverlayProps\n>(({ className, style, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n className={cn(\n \"fixed inset-0 bg-bg-overlay\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=closed]:duration-150 data-[state=closed]:ease-in\",\n \"data-[state=open]:fade-in-0 data-[state=open]:duration-200 data-[state=open]:ease-out\",\n className,\n )}\n {...props}\n />\n));\nDrawerOverlay.displayName = \"DrawerOverlay\";\n\n/**\n * Slide-in animation classes keyed by position.\n * Uses Tailwind animate utilities (animate-in / animate-out).\n */\nconst SLIDE_CLASSES: Record<DrawerPosition, string> = {\n right:\n \"inset-y-0 right-0 h-full w-2/3 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right\",\n left: \"inset-y-0 left-0 h-full w-2/3 data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left\",\n top: \"inset-x-0 top-0 w-full data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top\",\n bottom:\n \"inset-x-0 bottom-0 w-full rounded-t-xs data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom\",\n};\n\n/** Props for the {@link DrawerContent} component. */\nexport interface DrawerContentProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {\n /**\n * The edge from which the drawer slides in.\n *\n * Named `position` (rather than `side`) to avoid confusion with the CSS\n * `side` concept used by Radix Popover/Tooltip and to better convey the\n * spatial relationship of the drawer to the viewport.\n *\n * @default \"right\"\n */\n position?: DrawerPosition;\n /**\n * Controls the maximum extent of the drawer panel.\n * For left/right drawers this sets `max-width`; for top/bottom it sets `max-height`.\n * @default \"sm\"\n */\n size?: DrawerSize;\n /**\n * Whether to render the default {@link DrawerOverlay} behind the content.\n * Set to `false` to provide your own overlay or omit it entirely.\n *\n * Prefer setting `overlay` on the {@link Drawer} root instead so that\n * `modal` is also adjusted automatically.\n *\n * @default true\n */\n overlay?: boolean;\n /** Props forwarded to the default {@link DrawerOverlay} when `overlay` is `true`. */\n overlayProps?: DrawerOverlayProps;\n}\n\n/**\n * The panel that slides in from the chosen edge. Renders inside a portal with\n * an overlay backdrop by default.\n *\n * Includes focus-trap, `aria-describedby`, and Escape-to-close from Radix Dialog.\n */\nexport const DrawerContent = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Content>,\n DrawerContentProps\n>(\n (\n {\n className,\n position = \"right\",\n size = \"sm\",\n overlay: overlayProp,\n overlayProps,\n style,\n children,\n ...props\n },\n ref,\n ) => {\n const ctx = React.useContext(DrawerContext);\n const overlay = overlayProp ?? ctx.overlay;\n const isHorizontal = position === \"left\" || position === \"right\";\n const sizeClass = isHorizontal\n ? ({ sm: \"max-w-sm\", md: \"max-w-md\", lg: \"max-w-lg\", full: \"max-w-full\" } as const)[size]\n : (\n {\n sm: \"max-h-[24rem]\",\n md: \"max-h-[28rem]\",\n lg: \"max-h-[32rem]\",\n full: \"max-h-full\",\n } as const\n )[size];\n\n return (\n <DialogPrimitive.Portal>\n {overlay && <DrawerOverlay {...overlayProps} />}\n <DialogPrimitive.Content\n ref={ref}\n style={{ zIndex: \"calc(var(--fanvue-ui-portal-z-index, 50) + 1)\", ...style }}\n className={cn(\n \"fixed flex flex-col bg-surface-secondary shadow-lg outline-none backdrop-blur-lg\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:duration-150 data-[state=closed]:ease-in\",\n \"data-[state=open]:duration-200 data-[state=open]:ease-out\",\n SLIDE_CLASSES[position],\n sizeClass,\n className,\n )}\n {...props}\n >\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n );\n },\n);\nDrawerContent.displayName = \"DrawerContent\";\n\n/** Props for the {@link DrawerHeader} component. */\nexport interface DrawerHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Whether to show a built-in close (X) button. @default true */\n showClose?: boolean;\n /** Accessible label for the close button. @default \"Close drawer\" */\n closeLabel?: string;\n}\n\n/**\n * A semantic header area for the drawer, typically containing a title and description.\n * Renders a built-in close button by default.\n */\nexport const DrawerHeader = React.forwardRef<HTMLDivElement, DrawerHeaderProps>(\n ({ className, showClose = true, closeLabel = \"Close drawer\", children, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex items-start gap-2 p-4\", className)} {...props}>\n <div className=\"flex min-w-0 flex-1 flex-col gap-1.5\">{children}</div>\n {showClose && (\n <DialogPrimitive.Close asChild>\n <IconButton\n icon={<CloseIcon />}\n aria-label={closeLabel}\n variant=\"tertiary\"\n size=\"24\"\n className=\"shrink-0\"\n />\n </DialogPrimitive.Close>\n )}\n </div>\n ),\n);\nDrawerHeader.displayName = \"DrawerHeader\";\n\n/** Props for the {@link DrawerFooter} component. */\nexport interface DrawerFooterProps extends React.HTMLAttributes<HTMLDivElement> {}\n\n/** A semantic footer area for the drawer, typically containing action buttons. */\nexport const DrawerFooter = React.forwardRef<HTMLDivElement, DrawerFooterProps>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex flex-col gap-2 p-4\", className)} {...props} />\n ),\n);\nDrawerFooter.displayName = \"DrawerFooter\";\n\n/** Props for the {@link DrawerTitle} component. */\nexport interface DrawerTitleProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> {}\n\n/** An accessible title for the drawer. Required for screen readers. */\nexport const DrawerTitle = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Title>,\n DrawerTitleProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\"typography-semibold-body-lg truncate text-content-primary\", className)}\n {...props}\n />\n));\nDrawerTitle.displayName = \"DrawerTitle\";\n\n/** Props for the {@link DrawerDescription} component. */\nexport interface DrawerDescriptionProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> {}\n\n/** An accessible description for the drawer, providing supplementary context. */\nexport const DrawerDescription = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Description>,\n DrawerDescriptionProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn(\"typography-regular-body-md text-content-secondary\", className)}\n {...props}\n />\n));\nDrawerDescription.displayName = \"DrawerDescription\";\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Drawer.mjs","sources":["../../../src/components/Drawer/Drawer.tsx"],"sourcesContent":["import * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useSuppressClickAfterDrag } from \"../../utils/useSuppressClickAfterDrag\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\n\n/** The side from which the drawer slides in. */\nexport type DrawerPosition = \"left\" | \"right\" | \"top\" | \"bottom\";\n\n/** Size presets for the drawer panel. Maps to max-width (left/right) or max-height (top/bottom). */\nexport type DrawerSize = \"sm\" | \"md\" | \"lg\" | \"full\";\n\n/**\n * Props for the {@link Drawer} root component.\n *\n * Inherits `open`, `onOpenChange`, and `defaultOpen` from Radix Dialog.Root.\n *\n * The Radix `modal` prop (default `true`) can also be passed to create a\n * non-modal drawer — useful for persistent side navigation that does not\n * block interaction with the rest of the page.\n *\n * **`overlay` behaviour:** When `overlay` is `false`, the component\n * automatically sets `modal={false}` on the underlying Radix Dialog.Root.\n * A modal dialog without a visible overlay is confusing because focus is\n * still trapped and scroll is blocked even though the page appears\n * interactive. Passing `modal={true}` explicitly will override this.\n */\nexport interface DrawerProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Root> {\n /**\n * Whether the default {@link DrawerOverlay} is rendered.\n * When `false`, `modal` is automatically set to `false` as well\n * (unless explicitly overridden) so focus-trap and scroll-lock are disabled.\n * @default true\n */\n overlay?: boolean;\n}\n\nconst DrawerContext = React.createContext<{ overlay: boolean }>({\n overlay: true,\n});\n\n/**\n * Root component that manages open/close state for a drawer.\n * Wraps Radix Dialog.Root.\n *\n * @example\n * ```tsx\n * <Drawer>\n * <DrawerTrigger>Open</DrawerTrigger>\n * <DrawerContent position=\"right\">\n * <DrawerHeader>\n * <DrawerTitle>Settings</DrawerTitle>\n * <DrawerDescription>Adjust your preferences.</DrawerDescription>\n * </DrawerHeader>\n * <p>Content goes here.</p>\n * <DrawerFooter>\n * <DrawerClose>Done</DrawerClose>\n * </DrawerFooter>\n * </DrawerContent>\n * </Drawer>\n * ```\n */\nexport function Drawer({ overlay = true, modal, children, ...props }: DrawerProps) {\n const resolvedModal = modal ?? (overlay ? undefined : false);\n return (\n <DrawerContext.Provider value={{ overlay }}>\n <DialogPrimitive.Root modal={resolvedModal} {...props}>\n {children}\n </DialogPrimitive.Root>\n </DrawerContext.Provider>\n );\n}\nDrawer.displayName = \"Drawer\";\n\n/** Props for the {@link DrawerTrigger} component. */\nexport type DrawerTriggerProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>;\n\n/**\n * The element that opens the drawer when clicked.\n *\n * On touch / pen, a press-and-release that crosses a small movement threshold\n * is treated as a drag and the resulting synthetic click is suppressed —\n * defends against Android Chrome opening the drawer on a scroll-drag-end.\n */\nexport const DrawerTrigger = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Trigger>,\n DrawerTriggerProps\n>((props, ref) => <DialogPrimitive.Trigger ref={ref} {...useSuppressClickAfterDrag(props)} />);\nDrawerTrigger.displayName = \"DrawerTrigger\";\n\n/** Props for the {@link DrawerClose} component. */\nexport type DrawerCloseProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>;\n\n/** Closes the drawer when clicked. Can be placed anywhere inside the drawer. */\nexport const DrawerClose = DialogPrimitive.Close;\nDrawerClose.displayName = \"DrawerClose\";\n\n/** Props for the {@link DrawerOverlay} component. */\nexport interface DrawerOverlayProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> {}\n\n/**\n * A translucent backdrop rendered behind the drawer content.\n * Clicking the overlay closes the drawer by default.\n */\nexport const DrawerOverlay = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Overlay>,\n DrawerOverlayProps\n>(({ className, style, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n className={cn(\n \"fixed inset-0 bg-bg-overlay\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=closed]:duration-150 data-[state=closed]:ease-in\",\n \"data-[state=open]:fade-in-0 data-[state=open]:duration-200 data-[state=open]:ease-out\",\n className,\n )}\n {...props}\n />\n));\nDrawerOverlay.displayName = \"DrawerOverlay\";\n\n/**\n * Slide-in animation classes keyed by position.\n * Uses Tailwind animate utilities (animate-in / animate-out).\n */\nconst SLIDE_CLASSES: Record<DrawerPosition, string> = {\n right:\n \"inset-y-0 right-0 h-full w-2/3 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right\",\n left: \"inset-y-0 left-0 h-full w-2/3 data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left\",\n top: \"inset-x-0 top-0 w-full data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top\",\n bottom:\n \"inset-x-0 bottom-0 w-full rounded-t-xs data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom\",\n};\n\n/** Props for the {@link DrawerContent} component. */\nexport interface DrawerContentProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {\n /**\n * The edge from which the drawer slides in.\n *\n * Named `position` (rather than `side`) to avoid confusion with the CSS\n * `side` concept used by Radix Popover/Tooltip and to better convey the\n * spatial relationship of the drawer to the viewport.\n *\n * @default \"right\"\n */\n position?: DrawerPosition;\n /**\n * Controls the maximum extent of the drawer panel.\n * For left/right drawers this sets `max-width`; for top/bottom it sets `max-height`.\n * @default \"sm\"\n */\n size?: DrawerSize;\n /**\n * Whether to render the default {@link DrawerOverlay} behind the content.\n * Set to `false` to provide your own overlay or omit it entirely.\n *\n * Prefer setting `overlay` on the {@link Drawer} root instead so that\n * `modal` is also adjusted automatically.\n *\n * @default true\n */\n overlay?: boolean;\n /** Props forwarded to the default {@link DrawerOverlay} when `overlay` is `true`. */\n overlayProps?: DrawerOverlayProps;\n}\n\n/**\n * The panel that slides in from the chosen edge. Renders inside a portal with\n * an overlay backdrop by default.\n *\n * Includes focus-trap, `aria-describedby`, and Escape-to-close from Radix Dialog.\n */\nexport const DrawerContent = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Content>,\n DrawerContentProps\n>(\n (\n {\n className,\n position = \"right\",\n size = \"sm\",\n overlay: overlayProp,\n overlayProps,\n style,\n children,\n ...props\n },\n ref,\n ) => {\n const ctx = React.useContext(DrawerContext);\n const overlay = overlayProp ?? ctx.overlay;\n const isHorizontal = position === \"left\" || position === \"right\";\n const sizeClass = isHorizontal\n ? (\n {\n sm: \"max-w-sm\",\n md: \"max-w-md\",\n lg: \"max-w-lg\",\n full: \"max-w-full\",\n } as const\n )[size]\n : (\n {\n sm: \"max-h-[24rem]\",\n md: \"max-h-[28rem]\",\n lg: \"max-h-[32rem]\",\n full: \"max-h-full\",\n } as const\n )[size];\n\n return (\n <DialogPrimitive.Portal>\n {overlay && <DrawerOverlay {...overlayProps} />}\n <DialogPrimitive.Content\n ref={ref}\n style={{\n zIndex: \"calc(var(--fanvue-ui-portal-z-index, 50) + 1)\",\n ...style,\n }}\n className={cn(\n \"fixed flex flex-col bg-surface-secondary shadow-lg outline-none backdrop-blur-lg\",\n \"data-[state=closed]:animate-out data-[state=open]:animate-in\",\n \"data-[state=closed]:duration-150 data-[state=closed]:ease-in\",\n \"data-[state=open]:duration-200 data-[state=open]:ease-out\",\n SLIDE_CLASSES[position],\n sizeClass,\n className,\n )}\n {...props}\n >\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n );\n },\n);\nDrawerContent.displayName = \"DrawerContent\";\n\n/** Props for the {@link DrawerHeader} component. */\nexport interface DrawerHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Whether to show a built-in close (X) button. @default true */\n showClose?: boolean;\n /** Accessible label for the close button. @default \"Close drawer\" */\n closeLabel?: string;\n}\n\n/**\n * A semantic header area for the drawer, typically containing a title and description.\n * Renders a built-in close button by default.\n */\nexport const DrawerHeader = React.forwardRef<HTMLDivElement, DrawerHeaderProps>(\n ({ className, showClose = true, closeLabel = \"Close drawer\", children, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex items-start gap-2 p-4\", className)} {...props}>\n <div className=\"flex min-w-0 flex-1 flex-col gap-1.5\">{children}</div>\n {showClose && (\n <DialogPrimitive.Close asChild>\n <IconButton\n icon={<CloseIcon />}\n aria-label={closeLabel}\n variant=\"tertiary\"\n size=\"24\"\n className=\"shrink-0\"\n />\n </DialogPrimitive.Close>\n )}\n </div>\n ),\n);\nDrawerHeader.displayName = \"DrawerHeader\";\n\n/** Props for the {@link DrawerFooter} component. */\nexport interface DrawerFooterProps extends React.HTMLAttributes<HTMLDivElement> {}\n\n/** A semantic footer area for the drawer, typically containing action buttons. */\nexport const DrawerFooter = React.forwardRef<HTMLDivElement, DrawerFooterProps>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex flex-col gap-2 p-4\", className)} {...props} />\n ),\n);\nDrawerFooter.displayName = \"DrawerFooter\";\n\n/** Props for the {@link DrawerTitle} component. */\nexport interface DrawerTitleProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> {}\n\n/** An accessible title for the drawer. Required for screen readers. */\nexport const DrawerTitle = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Title>,\n DrawerTitleProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\"typography-semibold-body-lg truncate text-content-primary\", className)}\n {...props}\n />\n));\nDrawerTitle.displayName = \"DrawerTitle\";\n\n/** Props for the {@link DrawerDescription} component. */\nexport interface DrawerDescriptionProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> {}\n\n/** An accessible description for the drawer, providing supplementary context. */\nexport const DrawerDescription = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Description>,\n DrawerDescriptionProps\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn(\"typography-regular-body-md text-content-secondary\", className)}\n {...props}\n />\n));\nDrawerDescription.displayName = \"DrawerDescription\";\n"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,81 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
4
|
+
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
|
4
5
|
import * as React from "react";
|
|
5
6
|
import { cn } from "../../utils/cn.mjs";
|
|
6
7
|
import { FLOATING_CONTENT_COLLISION_PADDING } from "../../utils/floatingContentCollisionPadding.mjs";
|
|
7
|
-
const
|
|
8
|
-
const
|
|
8
|
+
const TAP_MOVEMENT_THRESHOLD_PX = 10;
|
|
9
|
+
const ToggleOpenContext = React.createContext(null);
|
|
10
|
+
function DropdownMenu({
|
|
11
|
+
open: openProp,
|
|
12
|
+
defaultOpen,
|
|
13
|
+
onOpenChange,
|
|
14
|
+
children,
|
|
15
|
+
...props
|
|
16
|
+
}) {
|
|
17
|
+
const [open = false, setOpen] = useControllableState({
|
|
18
|
+
prop: openProp,
|
|
19
|
+
defaultProp: defaultOpen ?? false,
|
|
20
|
+
onChange: onOpenChange
|
|
21
|
+
});
|
|
22
|
+
return /* @__PURE__ */ jsx(ToggleOpenContext.Provider, { value: setOpen, children: /* @__PURE__ */ jsx(DropdownMenuPrimitive.Root, { open, onOpenChange: setOpen, ...props, children }) });
|
|
23
|
+
}
|
|
24
|
+
const DropdownMenuTrigger = React.forwardRef((props, ref) => {
|
|
25
|
+
const toggleOpen = React.useContext(ToggleOpenContext);
|
|
26
|
+
const tapRef = React.useRef(null);
|
|
27
|
+
if (toggleOpen === null) {
|
|
28
|
+
return /* @__PURE__ */ jsx(DropdownMenuPrimitive.Trigger, { ...props, ref });
|
|
29
|
+
}
|
|
30
|
+
return /* @__PURE__ */ jsx(
|
|
31
|
+
DropdownMenuPrimitive.Trigger,
|
|
32
|
+
{
|
|
33
|
+
...props,
|
|
34
|
+
ref,
|
|
35
|
+
onPointerDown: (event) => {
|
|
36
|
+
props.onPointerDown?.(event);
|
|
37
|
+
if (event.pointerType === "mouse" || props.disabled) return;
|
|
38
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
39
|
+
tapRef.current = {
|
|
40
|
+
pointerId: event.pointerId,
|
|
41
|
+
x: event.clientX,
|
|
42
|
+
y: event.clientY,
|
|
43
|
+
movedPastThreshold: false
|
|
44
|
+
};
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
},
|
|
47
|
+
onPointerMove: (event) => {
|
|
48
|
+
props.onPointerMove?.(event);
|
|
49
|
+
const tap = tapRef.current;
|
|
50
|
+
if (tap === null || event.pointerId !== tap.pointerId || tap.movedPastThreshold) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const dx = event.clientX - tap.x;
|
|
54
|
+
const dy = event.clientY - tap.y;
|
|
55
|
+
if (Math.hypot(dx, dy) > TAP_MOVEMENT_THRESHOLD_PX) {
|
|
56
|
+
tap.movedPastThreshold = true;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
onPointerUp: (event) => {
|
|
60
|
+
props.onPointerUp?.(event);
|
|
61
|
+
const tap = tapRef.current;
|
|
62
|
+
if (tap === null || event.pointerId !== tap.pointerId) return;
|
|
63
|
+
const wasDrag = tap.movedPastThreshold;
|
|
64
|
+
tapRef.current = null;
|
|
65
|
+
if (!wasDrag && !props.disabled) {
|
|
66
|
+
toggleOpen((prev) => !prev);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
onPointerCancel: (event) => {
|
|
70
|
+
props.onPointerCancel?.(event);
|
|
71
|
+
const tap = tapRef.current;
|
|
72
|
+
if (tap !== null && event.pointerId === tap.pointerId) {
|
|
73
|
+
tapRef.current = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
});
|
|
9
79
|
DropdownMenuTrigger.displayName = "DropdownMenuTrigger";
|
|
10
80
|
const DropdownMenuContent = React.forwardRef(
|
|
11
81
|
({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMenu.mjs","sources":["../../../src/components/DropdownMenu/DropdownMenu.tsx"],"sourcesContent":["import * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { FLOATING_CONTENT_COLLISION_PADDING } from \"../../utils/floatingContentCollisionPadding\";\n\n/** Props for the {@link DropdownMenu} root component. */\nexport interface DropdownMenuProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root> {}\n\n/** Root component that manages open/close state for a dropdown menu. */\nexport const DropdownMenu = DropdownMenuPrimitive.Root;\n\n/** Props for the {@link DropdownMenuTrigger} component. */\nexport type DropdownMenuTriggerProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Trigger\n>;\n\n/** The element that toggles the dropdown menu when clicked. */\nexport const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;\nDropdownMenuTrigger.displayName = \"DropdownMenuTrigger\";\n\n/** Props for the {@link DropdownMenuContent} component. */\nexport interface DropdownMenuContentProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> {}\n\n/**\n * The positioned content panel rendered inside a portal.\n *\n * Override the portal z-index per-instance via `style={{ zIndex: 1500 }}` or\n * globally with the `--fanvue-ui-portal-z-index` CSS custom property.\n *\n * @example\n * ```tsx\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild>\n * <Button>Open</Button>\n * </DropdownMenuTrigger>\n * <DropdownMenuContent>\n * <DropdownMenuItem>Option 1</DropdownMenuItem>\n * <DropdownMenuItem>Option 2</DropdownMenuItem>\n * </DropdownMenuContent>\n * </DropdownMenu>\n * ```\n */\nexport const DropdownMenuContent = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Content>,\n DropdownMenuContentProps\n>(\n (\n {\n className,\n style,\n sideOffset = 4,\n collisionPadding = FLOATING_CONTENT_COLLISION_PADDING,\n ...props\n },\n ref,\n ) => (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n className={cn(\n \"w-max min-w-(--radix-dropdown-menu-trigger-width) max-w-(--radix-dropdown-menu-content-available-width) overflow-y-auto rounded-xs border border-neutral-alphas-200 bg-bg-primary p-1 shadow-lg\",\n \"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n \"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95\",\n \"data-[side=top]:slide-in-from-bottom-2 data-[side=bottom]:slide-in-from-top-2\",\n \"data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2\",\n className,\n )}\n style={{\n zIndex: \"var(--fanvue-ui-portal-z-index, 50)\",\n maxHeight: \"var(--radix-dropdown-menu-content-available-height)\",\n ...style,\n }}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n ),\n);\nDropdownMenuContent.displayName = \"DropdownMenuContent\";\n\n/** Props for the {@link DropdownMenuGroup} component. */\nexport type DropdownMenuGroupProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Group\n>;\n\n/** Groups related menu items. Accepts an optional `DropdownMenuLabel`. */\nexport const DropdownMenuGroup = DropdownMenuPrimitive.Group;\nDropdownMenuGroup.displayName = \"DropdownMenuGroup\";\n\n/** Props for the {@link DropdownMenuLabel} component. */\nexport interface DropdownMenuLabelProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> {}\n\n/** A label for a group of items. Not focusable or selectable. */\nexport const DropdownMenuLabel = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Label>,\n DropdownMenuLabelProps\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn(\"typography-medium-body-xs px-2 py-1.5 text-content-secondary\", className)}\n {...props}\n />\n));\nDropdownMenuLabel.displayName = \"DropdownMenuLabel\";\n\n/** Available sizes for a dropdown menu item. */\nexport type DropdownMenuItemSize = \"sm\" | \"md\";\n\nexport interface DropdownMenuItemProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> {\n /** Height of the menu item row. @default \"sm\" */\n size?: DropdownMenuItemSize;\n /** Whether the item uses destructive (error) styling. */\n destructive?: boolean;\n /** Icon rendered before the label. */\n leadingIcon?: React.ReactNode;\n /** Icon rendered after the label. */\n trailingIcon?: React.ReactNode;\n /** Whether the item is in a selected state. */\n selected?: boolean;\n}\n\n/**\n * An individual item within a {@link DropdownMenuContent}.\n *\n * @example\n * ```tsx\n * <DropdownMenuItem>Edit profile</DropdownMenuItem>\n * <DropdownMenuItem destructive>Delete</DropdownMenuItem>\n * <DropdownMenuItem leadingIcon={<EditIcon />}>Edit</DropdownMenuItem>\n *\n * // As a link\n * <DropdownMenuItem asChild>\n * <a href=\"/settings\">Settings</a>\n * </DropdownMenuItem>\n * ```\n */\nexport const DropdownMenuItem = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Item>,\n DropdownMenuItemProps\n>(\n (\n {\n size = \"sm\",\n destructive,\n leadingIcon,\n trailingIcon,\n selected,\n className,\n children,\n asChild,\n ...props\n },\n ref,\n ) => {\n const itemClassName = cn(\n \"flex w-full cursor-pointer items-center gap-1 rounded px-2 outline-none\",\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50\",\n \"data-[highlighted]:bg-neutral-alphas-100\",\n size === \"sm\" ? \"min-h-[34px] py-1\" : \"min-h-[40px] py-1.5\",\n size === \"sm\" ? \"typography-medium-body-sm\" : \"typography-medium-body-md\",\n destructive && \"text-error-content\",\n selected && \"bg-success-surface\",\n className,\n );\n\n if (asChild) {\n return (\n <DropdownMenuPrimitive.Item ref={ref} asChild className={itemClassName} {...props}>\n {children}\n </DropdownMenuPrimitive.Item>\n );\n }\n\n return (\n <DropdownMenuPrimitive.Item ref={ref} className={itemClassName} {...props}>\n {leadingIcon}\n <span className=\"flex-1\">{children}</span>\n {trailingIcon}\n </DropdownMenuPrimitive.Item>\n );\n },\n);\nDropdownMenuItem.displayName = \"DropdownMenuItem\";\n\n/** Props for the {@link DropdownMenuSeparator} component. */\nexport interface DropdownMenuSeparatorProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> {}\n\n/** Visual separator between groups of items. */\nexport const DropdownMenuSeparator = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Separator>,\n DropdownMenuSeparatorProps\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn(\"my-1 h-px bg-neutral-alphas-200\", className)}\n {...props}\n />\n));\nDropdownMenuSeparator.displayName = \"DropdownMenuSeparator\";\n"],"names":[],"mappings":";;;;;;AAUO,MAAM,eAAe,sBAAsB;AAQ3C,MAAM,sBAAsB,sBAAsB;AACzD,oBAAoB,cAAc;AAyB3B,MAAM,sBAAsB,MAAM;AAAA,EAIvC,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb,mBAAmB;AAAA,IACnB,GAAG;AAAA,EAAA,GAEL,QAEA,oBAAC,sBAAsB,QAAtB,EACC,UAAA;AAAA,IAAC,sBAAsB;AAAA,IAAtB;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,OAAO;AAAA,QACL,QAAQ;AAAA,QACR,WAAW;AAAA,QACX,GAAG;AAAA,MAAA;AAAA,MAEJ,GAAG;AAAA,IAAA;AAAA,EAAA,EACN,CACF;AAEJ;AACA,oBAAoB,cAAc;AAQ3B,MAAM,oBAAoB,sBAAsB;AACvD,kBAAkB,cAAc;AAOzB,MAAM,oBAAoB,MAAM,WAGrC,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QAC1B;AAAA,EAAC,sBAAsB;AAAA,EAAtB;AAAA,IACC;AAAA,IACA,WAAW,GAAG,gEAAgE,SAAS;AAAA,IACtF,GAAG;AAAA,EAAA;AACN,CACD;AACD,kBAAkB,cAAc;AAkCzB,MAAM,mBAAmB,MAAM;AAAA,EAIpC,CACE;AAAA,IACE,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,gBAAgB;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,OAAO,sBAAsB;AAAA,MACtC,SAAS,OAAO,8BAA8B;AAAA,MAC9C,eAAe;AAAA,MACf,YAAY;AAAA,MACZ;AAAA,IAAA;AAGF,QAAI,SAAS;AACX,aACE,oBAAC,sBAAsB,MAAtB,EAA2B,KAAU,SAAO,MAAC,WAAW,eAAgB,GAAG,OACzE,SAAA,CACH;AAAA,IAEJ;AAEA,WACE,qBAAC,sBAAsB,MAAtB,EAA2B,KAAU,WAAW,eAAgB,GAAG,OACjE,UAAA;AAAA,MAAA;AAAA,MACD,oBAAC,QAAA,EAAK,WAAU,UAAU,SAAA,CAAS;AAAA,MAClC;AAAA,IAAA,GACH;AAAA,EAEJ;AACF;AACA,iBAAiB,cAAc;AAOxB,MAAM,wBAAwB,MAAM,WAGzC,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QAC1B;AAAA,EAAC,sBAAsB;AAAA,EAAtB;AAAA,IACC;AAAA,IACA,WAAW,GAAG,mCAAmC,SAAS;AAAA,IACzD,GAAG;AAAA,EAAA;AACN,CACD;AACD,sBAAsB,cAAc;"}
|
|
1
|
+
{"version":3,"file":"DropdownMenu.mjs","sources":["../../../src/components/DropdownMenu/DropdownMenu.tsx"],"sourcesContent":["import * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { FLOATING_CONTENT_COLLISION_PADDING } from \"../../utils/floatingContentCollisionPadding\";\n\n// Movement, in CSS px, above which a touch press-and-release counts as a drag.\nconst TAP_MOVEMENT_THRESHOLD_PX = 10;\n\ntype ActiveTap = {\n pointerId: number;\n x: number;\n y: number;\n movedPastThreshold: boolean;\n};\n\n// Lets DropdownMenuTrigger toggle the menu directly so it can gate on touch\n// movement — see radix-ui/primitives#1912.\nconst ToggleOpenContext = React.createContext<\n ((updater: (prev: boolean) => boolean) => void) | null\n>(null);\n\n/** Props for the {@link DropdownMenu} root component. */\nexport interface DropdownMenuProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root> {}\n\n/** Root component that manages open/close state for a dropdown menu. */\nexport function DropdownMenu({\n open: openProp,\n defaultOpen,\n onOpenChange,\n children,\n ...props\n}: DropdownMenuProps) {\n const [open = false, setOpen] = useControllableState({\n prop: openProp,\n defaultProp: defaultOpen ?? false,\n onChange: onOpenChange,\n });\n\n return (\n <ToggleOpenContext.Provider value={setOpen}>\n <DropdownMenuPrimitive.Root open={open} onOpenChange={setOpen} {...props}>\n {children}\n </DropdownMenuPrimitive.Root>\n </ToggleOpenContext.Provider>\n );\n}\n\n/** Props for the {@link DropdownMenuTrigger} component. */\nexport type DropdownMenuTriggerProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Trigger\n>;\n\n/**\n * The element that toggles the dropdown menu when clicked.\n *\n * On touch devices, the menu only opens if the press-and-release stays within\n * a small movement threshold. A drag that incidentally ends over the trigger\n * (common when scrolling a feed on Android Chrome) is ignored. Mouse and\n * keyboard interactions are unchanged.\n */\nexport const DropdownMenuTrigger = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Trigger>,\n DropdownMenuTriggerProps\n>((props, ref) => {\n const toggleOpen = React.useContext(ToggleOpenContext);\n const tapRef = React.useRef<ActiveTap | null>(null);\n\n // Used outside our DropdownMenu wrapper — fall through to Radix defaults.\n if (toggleOpen === null) {\n return <DropdownMenuPrimitive.Trigger {...props} ref={ref} />;\n }\n\n return (\n <DropdownMenuPrimitive.Trigger\n {...props}\n ref={ref}\n onPointerDown={(event) => {\n props.onPointerDown?.(event);\n if (event.pointerType === \"mouse\" || props.disabled) return;\n // Keep pointerup / pointercancel on this element if the finger drifts off.\n // Optional because jsdom (used in tests) doesn't implement it.\n event.currentTarget.setPointerCapture?.(event.pointerId);\n tapRef.current = {\n pointerId: event.pointerId,\n x: event.clientX,\n y: event.clientY,\n movedPastThreshold: false,\n };\n // preventDefault stops Radix's pointerdown open path via composeEventHandlers.\n event.preventDefault();\n }}\n onPointerMove={(event) => {\n props.onPointerMove?.(event);\n const tap = tapRef.current;\n if (tap === null || event.pointerId !== tap.pointerId || tap.movedPastThreshold) {\n return;\n }\n const dx = event.clientX - tap.x;\n const dy = event.clientY - tap.y;\n if (Math.hypot(dx, dy) > TAP_MOVEMENT_THRESHOLD_PX) {\n tap.movedPastThreshold = true;\n }\n }}\n onPointerUp={(event) => {\n props.onPointerUp?.(event);\n const tap = tapRef.current;\n if (tap === null || event.pointerId !== tap.pointerId) return;\n const wasDrag = tap.movedPastThreshold;\n tapRef.current = null;\n if (!wasDrag && !props.disabled) {\n toggleOpen((prev) => !prev);\n }\n }}\n onPointerCancel={(event) => {\n props.onPointerCancel?.(event);\n const tap = tapRef.current;\n if (tap !== null && event.pointerId === tap.pointerId) {\n tapRef.current = null;\n }\n }}\n />\n );\n});\nDropdownMenuTrigger.displayName = \"DropdownMenuTrigger\";\n\n/** Props for the {@link DropdownMenuContent} component. */\nexport interface DropdownMenuContentProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> {}\n\n/**\n * The positioned content panel rendered inside a portal.\n *\n * Override the portal z-index per-instance via `style={{ zIndex: 1500 }}` or\n * globally with the `--fanvue-ui-portal-z-index` CSS custom property.\n *\n * @example\n * ```tsx\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild>\n * <Button>Open</Button>\n * </DropdownMenuTrigger>\n * <DropdownMenuContent>\n * <DropdownMenuItem>Option 1</DropdownMenuItem>\n * <DropdownMenuItem>Option 2</DropdownMenuItem>\n * </DropdownMenuContent>\n * </DropdownMenu>\n * ```\n */\nexport const DropdownMenuContent = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Content>,\n DropdownMenuContentProps\n>(\n (\n {\n className,\n style,\n sideOffset = 4,\n collisionPadding = FLOATING_CONTENT_COLLISION_PADDING,\n ...props\n },\n ref,\n ) => (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n className={cn(\n \"w-max min-w-(--radix-dropdown-menu-trigger-width) max-w-(--radix-dropdown-menu-content-available-width) overflow-y-auto rounded-xs border border-neutral-alphas-200 bg-bg-primary p-1 shadow-lg\",\n \"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n \"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95\",\n \"data-[side=top]:slide-in-from-bottom-2 data-[side=bottom]:slide-in-from-top-2\",\n \"data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2\",\n className,\n )}\n style={{\n zIndex: \"var(--fanvue-ui-portal-z-index, 50)\",\n maxHeight: \"var(--radix-dropdown-menu-content-available-height)\",\n ...style,\n }}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n ),\n);\nDropdownMenuContent.displayName = \"DropdownMenuContent\";\n\n/** Props for the {@link DropdownMenuGroup} component. */\nexport type DropdownMenuGroupProps = React.ComponentPropsWithoutRef<\n typeof DropdownMenuPrimitive.Group\n>;\n\n/** Groups related menu items. Accepts an optional `DropdownMenuLabel`. */\nexport const DropdownMenuGroup = DropdownMenuPrimitive.Group;\nDropdownMenuGroup.displayName = \"DropdownMenuGroup\";\n\n/** Props for the {@link DropdownMenuLabel} component. */\nexport interface DropdownMenuLabelProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> {}\n\n/** A label for a group of items. Not focusable or selectable. */\nexport const DropdownMenuLabel = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Label>,\n DropdownMenuLabelProps\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn(\"typography-medium-body-xs px-2 py-1.5 text-content-secondary\", className)}\n {...props}\n />\n));\nDropdownMenuLabel.displayName = \"DropdownMenuLabel\";\n\n/** Available sizes for a dropdown menu item. */\nexport type DropdownMenuItemSize = \"sm\" | \"md\";\n\nexport interface DropdownMenuItemProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> {\n /** Height of the menu item row. @default \"sm\" */\n size?: DropdownMenuItemSize;\n /** Whether the item uses destructive (error) styling. */\n destructive?: boolean;\n /** Icon rendered before the label. */\n leadingIcon?: React.ReactNode;\n /** Icon rendered after the label. */\n trailingIcon?: React.ReactNode;\n /** Whether the item is in a selected state. */\n selected?: boolean;\n}\n\n/**\n * An individual item within a {@link DropdownMenuContent}.\n *\n * @example\n * ```tsx\n * <DropdownMenuItem>Edit profile</DropdownMenuItem>\n * <DropdownMenuItem destructive>Delete</DropdownMenuItem>\n * <DropdownMenuItem leadingIcon={<EditIcon />}>Edit</DropdownMenuItem>\n *\n * // As a link\n * <DropdownMenuItem asChild>\n * <a href=\"/settings\">Settings</a>\n * </DropdownMenuItem>\n * ```\n */\nexport const DropdownMenuItem = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Item>,\n DropdownMenuItemProps\n>(\n (\n {\n size = \"sm\",\n destructive,\n leadingIcon,\n trailingIcon,\n selected,\n className,\n children,\n asChild,\n ...props\n },\n ref,\n ) => {\n const itemClassName = cn(\n \"flex w-full cursor-pointer items-center gap-1 rounded px-2 outline-none\",\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50\",\n \"data-[highlighted]:bg-neutral-alphas-100\",\n size === \"sm\" ? \"min-h-[34px] py-1\" : \"min-h-[40px] py-1.5\",\n size === \"sm\" ? \"typography-medium-body-sm\" : \"typography-medium-body-md\",\n destructive && \"text-error-content\",\n selected && \"bg-success-surface\",\n className,\n );\n\n if (asChild) {\n return (\n <DropdownMenuPrimitive.Item ref={ref} asChild className={itemClassName} {...props}>\n {children}\n </DropdownMenuPrimitive.Item>\n );\n }\n\n return (\n <DropdownMenuPrimitive.Item ref={ref} className={itemClassName} {...props}>\n {leadingIcon}\n <span className=\"flex-1\">{children}</span>\n {trailingIcon}\n </DropdownMenuPrimitive.Item>\n );\n },\n);\nDropdownMenuItem.displayName = \"DropdownMenuItem\";\n\n/** Props for the {@link DropdownMenuSeparator} component. */\nexport interface DropdownMenuSeparatorProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> {}\n\n/** Visual separator between groups of items. */\nexport const DropdownMenuSeparator = React.forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Separator>,\n DropdownMenuSeparatorProps\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn(\"my-1 h-px bg-neutral-alphas-200\", className)}\n {...props}\n />\n));\nDropdownMenuSeparator.displayName = \"DropdownMenuSeparator\";\n"],"names":[],"mappings":"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"}
|
|
@@ -4,9 +4,11 @@ import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { cn } from "../../utils/cn.mjs";
|
|
6
6
|
import { FLOATING_CONTENT_COLLISION_PADDING } from "../../utils/floatingContentCollisionPadding.mjs";
|
|
7
|
+
import { useSuppressClickAfterDrag } from "../../utils/useSuppressClickAfterDrag.mjs";
|
|
7
8
|
import { Button } from "../Button/Button.mjs";
|
|
8
9
|
const InfoBox = PopoverPrimitive.Root;
|
|
9
|
-
const InfoBoxTrigger = PopoverPrimitive.Trigger;
|
|
10
|
+
const InfoBoxTrigger = React.forwardRef((props, ref) => /* @__PURE__ */ jsx(PopoverPrimitive.Trigger, { ref, ...useSuppressClickAfterDrag(props) }));
|
|
11
|
+
InfoBoxTrigger.displayName = "InfoBoxTrigger";
|
|
10
12
|
const ACTION_CLASSES = {
|
|
11
13
|
brand: "hover:bg-brand-primary-default/80 hover:text-content-on-brand",
|
|
12
14
|
tertiary: "text-content-primary-inverted hover:text-content-primary-inverted hover:bg-content-primary-inverted/10"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InfoBox.mjs","sources":["../../../src/components/InfoBox/InfoBox.tsx"],"sourcesContent":["import * as PopoverPrimitive from \"@radix-ui/react-popover\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { FLOATING_CONTENT_COLLISION_PADDING } from \"../../utils/floatingContentCollisionPadding\";\nimport { Button } from \"../Button/Button\";\n\n/** Props for the {@link InfoBox} root component. */\nexport interface InfoBoxProps extends React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Root> {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n defaultOpen?: boolean;\n}\n\n/** Root component that manages open/close state for an info box. */\nexport const InfoBox = PopoverPrimitive.Root;\n\n/** Props for the {@link InfoBoxTrigger} component. */\nexport type InfoBoxTriggerProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Trigger>;\n\n
|
|
1
|
+
{"version":3,"file":"InfoBox.mjs","sources":["../../../src/components/InfoBox/InfoBox.tsx"],"sourcesContent":["import * as PopoverPrimitive from \"@radix-ui/react-popover\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { FLOATING_CONTENT_COLLISION_PADDING } from \"../../utils/floatingContentCollisionPadding\";\nimport { useSuppressClickAfterDrag } from \"../../utils/useSuppressClickAfterDrag\";\nimport { Button } from \"../Button/Button\";\n\n/** Props for the {@link InfoBox} root component. */\nexport interface InfoBoxProps extends React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Root> {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n defaultOpen?: boolean;\n}\n\n/** Root component that manages open/close state for an info box. */\nexport const InfoBox = PopoverPrimitive.Root;\n\n/** Props for the {@link InfoBoxTrigger} component. */\nexport type InfoBoxTriggerProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Trigger>;\n\n/**\n * The element that triggers the info box on click.\n *\n * On touch / pen, a press-and-release that crosses a small movement threshold\n * is treated as a drag and the resulting synthetic click is suppressed —\n * defends against Android Chrome opening the popover on a scroll-drag-end.\n */\nexport const InfoBoxTrigger = React.forwardRef<\n React.ComponentRef<typeof PopoverPrimitive.Trigger>,\n InfoBoxTriggerProps\n>((props, ref) => <PopoverPrimitive.Trigger ref={ref} {...useSuppressClickAfterDrag(props)} />);\nInfoBoxTrigger.displayName = \"InfoBoxTrigger\";\n\n/** Action button with a label and click handler. */\ninterface InfoBoxButtonAction {\n label: string;\n onClick?: () => void;\n}\n\n/** Custom element rendered in place of the default action button. */\ninterface InfoBoxElementAction {\n element: React.ReactNode;\n}\n\n/** Action configuration for {@link InfoBoxContent}. */\nexport type InfoBoxAction = InfoBoxButtonAction | InfoBoxElementAction;\n\nexport interface InfoBoxContentProps\n extends React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> {\n /** Whether to show the directional arrow pointer. @default true */\n showArrow?: boolean;\n /** Heading text rendered at the top of the info box. */\n heading?: React.ReactNode;\n /** Icon element displayed to the left of the heading. */\n icon?: React.ReactNode;\n /** Pill or badge element displayed to the right of the heading. */\n pill?: React.ReactNode;\n /** Primary action button (brand green). */\n primaryAction?: InfoBoxAction;\n /** Secondary action button (ghost). */\n secondaryAction?: InfoBoxAction;\n}\n\nconst ACTION_CLASSES: Record<\"brand\" | \"tertiary\", string> = {\n brand: \"hover:bg-brand-primary-default/80 hover:text-content-on-brand\",\n tertiary:\n \"text-content-primary-inverted hover:text-content-primary-inverted hover:bg-content-primary-inverted/10\",\n};\n\nconst ActionButton = ({\n action,\n variant,\n}: {\n action: InfoBoxAction;\n variant: \"brand\" | \"tertiary\";\n}) =>\n \"element\" in action ? (\n action.element\n ) : (\n <Button variant={variant} onClick={action.onClick} className={ACTION_CLASSES[variant]}>\n {action.label}\n </Button>\n );\n\nexport const InfoBoxContent = React.forwardRef<\n React.ComponentRef<typeof PopoverPrimitive.Content>,\n InfoBoxContentProps\n>(\n (\n {\n className,\n showArrow = true,\n sideOffset = 8,\n heading,\n icon,\n pill,\n primaryAction,\n secondaryAction,\n children,\n style,\n onOpenAutoFocus,\n collisionPadding = FLOATING_CONTENT_COLLISION_PADDING,\n ...props\n },\n ref,\n ) => {\n const hasHeader = icon !== undefined || heading !== undefined || pill !== undefined;\n const hasActions = primaryAction !== undefined || secondaryAction !== undefined;\n const headingId = React.useId();\n\n return (\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...style }}\n className={cn(\n \"typography-regular-body-md max-w-[280px] overflow-hidden rounded-md border border-white/20 bg-surface-primary-inverted p-4 text-content-primary-inverted shadow-[0px_2px_4px_0px_rgba(17,24,39,0.08)]\",\n className,\n )}\n align=\"center\"\n aria-labelledby={heading ? headingId : undefined}\n arrowPadding={12}\n onOpenAutoFocus={(e) => {\n // Prevent auto-focus stealing when opening — content is supplementary, not a dialog.\n e.preventDefault();\n onOpenAutoFocus?.(e);\n }}\n {...props}\n >\n <div className=\"flex flex-col gap-3\">\n {hasHeader && (\n <div className=\"flex items-center gap-3\">\n {icon && <div className=\"size-5 shrink-0\">{icon}</div>}\n {heading && (\n <p\n id={headingId}\n className=\"typography-semibold-body-lg min-w-0 flex-1 text-content-primary-inverted\"\n >\n {heading}\n </p>\n )}\n {pill && <div className=\"shrink-0\">{pill}</div>}\n </div>\n )}\n {children && (\n <div className=\"typography-regular-body-md text-content-primary-inverted\">\n {children}\n </div>\n )}\n {hasActions && (\n <div className=\"flex items-center gap-1\">\n {primaryAction && <ActionButton action={primaryAction} variant=\"brand\" />}\n {secondaryAction && <ActionButton action={secondaryAction} variant=\"tertiary\" />}\n </div>\n )}\n </div>\n {showArrow && (\n <PopoverPrimitive.Arrow\n className={\n \"-translate-y-px! fill-surface-primary-inverted stroke-2 stroke-surface-primary-inverted\"\n }\n width={12}\n height={6}\n />\n )}\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n );\n },\n);\nInfoBoxContent.displayName = \"InfoBoxContent\";\n"],"names":[],"mappings":";;;;;;;;AAeO,MAAM,UAAU,iBAAiB;AAYjC,MAAM,iBAAiB,MAAM,WAGlC,CAAC,OAAO,QAAQ,oBAAC,iBAAiB,SAAjB,EAAyB,KAAW,GAAG,0BAA0B,KAAK,GAAG,CAAE;AAC9F,eAAe,cAAc;AAgC7B,MAAM,iBAAuD;AAAA,EAC3D,OAAO;AAAA,EACP,UACE;AACJ;AAEA,MAAM,eAAe,CAAC;AAAA,EACpB;AAAA,EACA;AACF,MAIE,aAAa,SACX,OAAO,8BAEN,QAAA,EAAO,SAAkB,SAAS,OAAO,SAAS,WAAW,eAAe,OAAO,GACjF,iBAAO,OACV;AAGG,MAAM,iBAAiB,MAAM;AAAA,EAIlC,CACE;AAAA,IACE;AAAA,IACA,YAAY;AAAA,IACZ,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,YAAY,SAAS,UAAa,YAAY,UAAa,SAAS;AAC1E,UAAM,aAAa,kBAAkB,UAAa,oBAAoB;AACtE,UAAM,YAAY,MAAM,MAAA;AAExB,WACE,oBAAC,iBAAiB,QAAjB,EACC,UAAA;AAAA,MAAC,iBAAiB;AAAA,MAAjB;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO,EAAE,QAAQ,uCAAuC,GAAG,MAAA;AAAA,QAC3D,WAAW;AAAA,UACT;AAAA,UACA;AAAA,QAAA;AAAA,QAEF,OAAM;AAAA,QACN,mBAAiB,UAAU,YAAY;AAAA,QACvC,cAAc;AAAA,QACd,iBAAiB,CAAC,MAAM;AAEtB,YAAE,eAAA;AACF,4BAAkB,CAAC;AAAA,QACrB;AAAA,QACC,GAAG;AAAA,QAEJ,UAAA;AAAA,UAAA,qBAAC,OAAA,EAAI,WAAU,uBACZ,UAAA;AAAA,YAAA,aACC,qBAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,cAAA,QAAQ,oBAAC,OAAA,EAAI,WAAU,mBAAmB,UAAA,MAAK;AAAA,cAC/C,WACC;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,IAAI;AAAA,kBACJ,WAAU;AAAA,kBAET,UAAA;AAAA,gBAAA;AAAA,cAAA;AAAA,cAGJ,QAAQ,oBAAC,OAAA,EAAI,WAAU,YAAY,UAAA,KAAA,CAAK;AAAA,YAAA,GAC3C;AAAA,YAED,YACC,oBAAC,OAAA,EAAI,WAAU,4DACZ,UACH;AAAA,YAED,cACC,qBAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,cAAA,iBAAiB,oBAAC,cAAA,EAAa,QAAQ,eAAe,SAAQ,SAAQ;AAAA,cACtE,mBAAmB,oBAAC,cAAA,EAAa,QAAQ,iBAAiB,SAAQ,WAAA,CAAW;AAAA,YAAA,EAAA,CAChF;AAAA,UAAA,GAEJ;AAAA,UACC,aACC;AAAA,YAAC,iBAAiB;AAAA,YAAjB;AAAA,cACC,WACE;AAAA,cAEF,OAAO;AAAA,cACP,QAAQ;AAAA,YAAA;AAAA,UAAA;AAAA,QACV;AAAA,MAAA;AAAA,IAAA,GAGN;AAAA,EAEJ;AACF;AACA,eAAe,cAAc;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1636,8 +1636,14 @@ export declare const DialogTitle: React_2.ForwardRefExoticComponent<Omit<DialogP
|
|
|
1636
1636
|
/** Props for the {@link DialogTitle} component. */
|
|
1637
1637
|
export declare type DialogTitleProps = React_2.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>;
|
|
1638
1638
|
|
|
1639
|
-
/**
|
|
1640
|
-
|
|
1639
|
+
/**
|
|
1640
|
+
* The element that opens the dialog when clicked.
|
|
1641
|
+
*
|
|
1642
|
+
* On touch / pen, a press-and-release that crosses a small movement threshold
|
|
1643
|
+
* is treated as a drag and the resulting synthetic click is suppressed —
|
|
1644
|
+
* defends against Android Chrome opening the dialog on a scroll-drag-end.
|
|
1645
|
+
*/
|
|
1646
|
+
export declare const DialogTrigger: React_2.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogTriggerProps & React_2.RefAttributes<HTMLButtonElement>, "ref"> & React_2.RefAttributes<HTMLButtonElement>>;
|
|
1641
1647
|
|
|
1642
1648
|
/** Props for the {@link DialogTrigger} component. */
|
|
1643
1649
|
export declare type DialogTriggerProps = React_2.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>;
|
|
@@ -1873,14 +1879,20 @@ export declare const DrawerTitle: React_2.ForwardRefExoticComponent<DrawerTitleP
|
|
|
1873
1879
|
export declare interface DrawerTitleProps extends React_2.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> {
|
|
1874
1880
|
}
|
|
1875
1881
|
|
|
1876
|
-
/**
|
|
1877
|
-
|
|
1882
|
+
/**
|
|
1883
|
+
* The element that opens the drawer when clicked.
|
|
1884
|
+
*
|
|
1885
|
+
* On touch / pen, a press-and-release that crosses a small movement threshold
|
|
1886
|
+
* is treated as a drag and the resulting synthetic click is suppressed —
|
|
1887
|
+
* defends against Android Chrome opening the drawer on a scroll-drag-end.
|
|
1888
|
+
*/
|
|
1889
|
+
export declare const DrawerTrigger: React_2.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogTriggerProps & React_2.RefAttributes<HTMLButtonElement>, "ref"> & React_2.RefAttributes<HTMLButtonElement>>;
|
|
1878
1890
|
|
|
1879
1891
|
/** Props for the {@link DrawerTrigger} component. */
|
|
1880
1892
|
export declare type DrawerTriggerProps = React_2.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>;
|
|
1881
1893
|
|
|
1882
1894
|
/** Root component that manages open/close state for a dropdown menu. */
|
|
1883
|
-
export declare
|
|
1895
|
+
export declare function DropdownMenu({ open: openProp, defaultOpen, onOpenChange, children, ...props }: DropdownMenuProps): JSX.Element;
|
|
1884
1896
|
|
|
1885
1897
|
/**
|
|
1886
1898
|
* The positioned content panel rendered inside a portal.
|
|
@@ -1964,8 +1976,15 @@ export declare const DropdownMenuSeparator: React_2.ForwardRefExoticComponent<Dr
|
|
|
1964
1976
|
export declare interface DropdownMenuSeparatorProps extends React_2.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> {
|
|
1965
1977
|
}
|
|
1966
1978
|
|
|
1967
|
-
/**
|
|
1968
|
-
|
|
1979
|
+
/**
|
|
1980
|
+
* The element that toggles the dropdown menu when clicked.
|
|
1981
|
+
*
|
|
1982
|
+
* On touch devices, the menu only opens if the press-and-release stays within
|
|
1983
|
+
* a small movement threshold. A drag that incidentally ends over the trigger
|
|
1984
|
+
* (common when scrolling a feed on Android Chrome) is ignored. Mouse and
|
|
1985
|
+
* keyboard interactions are unchanged.
|
|
1986
|
+
*/
|
|
1987
|
+
export declare const DropdownMenuTrigger: React_2.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuTriggerProps & React_2.RefAttributes<HTMLButtonElement>, "ref"> & React_2.RefAttributes<HTMLButtonElement>>;
|
|
1969
1988
|
|
|
1970
1989
|
/** Props for the {@link DropdownMenuTrigger} component. */
|
|
1971
1990
|
export declare type DropdownMenuTriggerProps = React_2.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger>;
|
|
@@ -2403,8 +2422,14 @@ export declare interface InfoBoxProps extends React_2.ComponentPropsWithoutRef<t
|
|
|
2403
2422
|
defaultOpen?: boolean;
|
|
2404
2423
|
}
|
|
2405
2424
|
|
|
2406
|
-
/**
|
|
2407
|
-
|
|
2425
|
+
/**
|
|
2426
|
+
* The element that triggers the info box on click.
|
|
2427
|
+
*
|
|
2428
|
+
* On touch / pen, a press-and-release that crosses a small movement threshold
|
|
2429
|
+
* is treated as a drag and the resulting synthetic click is suppressed —
|
|
2430
|
+
* defends against Android Chrome opening the popover on a scroll-drag-end.
|
|
2431
|
+
*/
|
|
2432
|
+
export declare const InfoBoxTrigger: React_2.ForwardRefExoticComponent<Omit<PopoverPrimitive.PopoverTriggerProps & React_2.RefAttributes<HTMLButtonElement>, "ref"> & React_2.RefAttributes<HTMLButtonElement>>;
|
|
2408
2433
|
|
|
2409
2434
|
/** Props for the {@link InfoBoxTrigger} component. */
|
|
2410
2435
|
export declare type InfoBoxTriggerProps = React_2.ComponentPropsWithoutRef<typeof PopoverPrimitive.Trigger>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
const TAP_MOVEMENT_THRESHOLD_PX = 10;
|
|
4
|
+
function useSuppressClickAfterDrag(props) {
|
|
5
|
+
const tapRef = React.useRef(null);
|
|
6
|
+
return {
|
|
7
|
+
...props,
|
|
8
|
+
onPointerDown(event) {
|
|
9
|
+
props.onPointerDown?.(event);
|
|
10
|
+
if (event.pointerType === "mouse") {
|
|
11
|
+
tapRef.current = null;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
15
|
+
tapRef.current = {
|
|
16
|
+
pointerId: event.pointerId,
|
|
17
|
+
x: event.clientX,
|
|
18
|
+
y: event.clientY,
|
|
19
|
+
movedPastThreshold: false
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
onPointerMove(event) {
|
|
23
|
+
props.onPointerMove?.(event);
|
|
24
|
+
const tap = tapRef.current;
|
|
25
|
+
if (tap === null || event.pointerId !== tap.pointerId || tap.movedPastThreshold) return;
|
|
26
|
+
const dx = event.clientX - tap.x;
|
|
27
|
+
const dy = event.clientY - tap.y;
|
|
28
|
+
if (Math.hypot(dx, dy) > TAP_MOVEMENT_THRESHOLD_PX) {
|
|
29
|
+
tap.movedPastThreshold = true;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
onPointerCancel(event) {
|
|
33
|
+
props.onPointerCancel?.(event);
|
|
34
|
+
const tap = tapRef.current;
|
|
35
|
+
if (tap !== null && event.pointerId === tap.pointerId) {
|
|
36
|
+
tapRef.current = null;
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
onClick(event) {
|
|
40
|
+
const tap = tapRef.current;
|
|
41
|
+
tapRef.current = null;
|
|
42
|
+
if (tap?.movedPastThreshold) {
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
event.stopPropagation();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
props.onClick?.(event);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
useSuppressClickAfterDrag
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=useSuppressClickAfterDrag.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSuppressClickAfterDrag.mjs","sources":["../../src/utils/useSuppressClickAfterDrag.ts"],"sourcesContent":["import * as React from \"react\";\n\nconst TAP_MOVEMENT_THRESHOLD_PX = 10;\n\ntype ActiveTap = {\n pointerId: number;\n x: number;\n y: number;\n movedPastThreshold: boolean;\n};\n\ntype GatedHandlers = {\n onPointerDown?: React.PointerEventHandler;\n onPointerMove?: React.PointerEventHandler;\n onPointerCancel?: React.PointerEventHandler;\n onClick?: React.MouseEventHandler;\n};\n\n/**\n * Composes the consumer's pointer/click handlers with a touch-drag tracker\n * that suppresses the synthetic click an Android Chrome scroll-drag-end can\n * dispatch on a click-based Radix trigger (Popover / Dialog / Drawer).\n *\n * On touch / pen input, if the press-and-release crosses a movement threshold,\n * the subsequent `click` is preventDefault'd — short-circuiting Radix's own\n * `onOpenToggle` via `composeEventHandlers` — and stopPropagation'd so it\n * doesn't bubble to ancestors. Mouse input passes through unchanged because\n * mouse never triggers the Android scroll-drag bug class.\n *\n * Related: radix-ui/primitives#1912 (DropdownMenu pointerdown variant) and\n * #2702 (DismissableLayer touch dismiss). For the DropdownMenu pointerdown\n * variant see `components/DropdownMenu/DropdownMenu.tsx`.\n */\nexport function useSuppressClickAfterDrag<P extends GatedHandlers>(props: P): P {\n const tapRef = React.useRef<ActiveTap | null>(null);\n\n return {\n ...props,\n onPointerDown(event) {\n props.onPointerDown?.(event);\n if (event.pointerType === \"mouse\") {\n tapRef.current = null;\n return;\n }\n // Keep pointermove on this element if the finger drifts off.\n // Optional because jsdom (used in tests) doesn't implement it.\n event.currentTarget.setPointerCapture?.(event.pointerId);\n tapRef.current = {\n pointerId: event.pointerId,\n x: event.clientX,\n y: event.clientY,\n movedPastThreshold: false,\n };\n },\n onPointerMove(event) {\n props.onPointerMove?.(event);\n const tap = tapRef.current;\n if (tap === null || event.pointerId !== tap.pointerId || tap.movedPastThreshold) return;\n const dx = event.clientX - tap.x;\n const dy = event.clientY - tap.y;\n if (Math.hypot(dx, dy) > TAP_MOVEMENT_THRESHOLD_PX) {\n tap.movedPastThreshold = true;\n }\n },\n onPointerCancel(event) {\n props.onPointerCancel?.(event);\n const tap = tapRef.current;\n if (tap !== null && event.pointerId === tap.pointerId) {\n tapRef.current = null;\n }\n },\n onClick(event) {\n const tap = tapRef.current;\n tapRef.current = null;\n if (tap?.movedPastThreshold) {\n // preventDefault stops Radix's onClick → onOpenToggle via\n // composeEventHandlers. stopPropagation prevents the synthetic click\n // from bubbling to ancestor click handlers.\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n props.onClick?.(event);\n },\n };\n}\n"],"names":[],"mappings":";;AAEA,MAAM,4BAA4B;AA+B3B,SAAS,0BAAmD,OAAa;AAC9E,QAAM,SAAS,MAAM,OAAyB,IAAI;AAElD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,cAAc,OAAO;AACnB,YAAM,gBAAgB,KAAK;AAC3B,UAAI,MAAM,gBAAgB,SAAS;AACjC,eAAO,UAAU;AACjB;AAAA,MACF;AAGA,YAAM,cAAc,oBAAoB,MAAM,SAAS;AACvD,aAAO,UAAU;AAAA,QACf,WAAW,MAAM;AAAA,QACjB,GAAG,MAAM;AAAA,QACT,GAAG,MAAM;AAAA,QACT,oBAAoB;AAAA,MAAA;AAAA,IAExB;AAAA,IACA,cAAc,OAAO;AACnB,YAAM,gBAAgB,KAAK;AAC3B,YAAM,MAAM,OAAO;AACnB,UAAI,QAAQ,QAAQ,MAAM,cAAc,IAAI,aAAa,IAAI,mBAAoB;AACjF,YAAM,KAAK,MAAM,UAAU,IAAI;AAC/B,YAAM,KAAK,MAAM,UAAU,IAAI;AAC/B,UAAI,KAAK,MAAM,IAAI,EAAE,IAAI,2BAA2B;AAClD,YAAI,qBAAqB;AAAA,MAC3B;AAAA,IACF;AAAA,IACA,gBAAgB,OAAO;AACrB,YAAM,kBAAkB,KAAK;AAC7B,YAAM,MAAM,OAAO;AACnB,UAAI,QAAQ,QAAQ,MAAM,cAAc,IAAI,WAAW;AACrD,eAAO,UAAU;AAAA,MACnB;AAAA,IACF;AAAA,IACA,QAAQ,OAAO;AACb,YAAM,MAAM,OAAO;AACnB,aAAO,UAAU;AACjB,UAAI,KAAK,oBAAoB;AAI3B,cAAM,eAAA;AACN,cAAM,gBAAA;AACN;AAAA,MACF;AACA,YAAM,UAAU,KAAK;AAAA,IACvB;AAAA,EAAA;AAEJ;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fanvue/ui",
|
|
3
|
-
"version": "2.15.
|
|
3
|
+
"version": "2.15.1",
|
|
4
4
|
"description": "React component library built with Tailwind CSS for Fanvue ecosystem",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org",
|
|
@@ -105,6 +105,7 @@
|
|
|
105
105
|
"@radix-ui/react-tabs": "1.1.13",
|
|
106
106
|
"@radix-ui/react-toast": "1.2.15",
|
|
107
107
|
"@radix-ui/react-tooltip": "1.2.8",
|
|
108
|
+
"@radix-ui/react-use-controllable-state": "1.2.2",
|
|
108
109
|
"clsx": "2.1.1",
|
|
109
110
|
"tailwind-merge": "3.4.0"
|
|
110
111
|
},
|