@fanvue/ui 3.14.1 → 3.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/cjs/components/AudioRecordButton/AudioRecordButton.cjs +54 -0
  2. package/dist/cjs/components/AudioRecordButton/AudioRecordButton.cjs.map +1 -0
  3. package/dist/cjs/components/AudioUpload/AudioUpload.cjs +5 -5
  4. package/dist/cjs/components/AudioUpload/AudioUpload.cjs.map +1 -1
  5. package/dist/cjs/components/ChatInput/ChatInput.cjs +6 -11
  6. package/dist/cjs/components/ChatInput/ChatInput.cjs.map +1 -1
  7. package/dist/cjs/components/ChatMessage/ChatMessage.cjs +228 -0
  8. package/dist/cjs/components/ChatMessage/ChatMessage.cjs.map +1 -0
  9. package/dist/cjs/components/Dialog/Dialog.cjs +1 -1
  10. package/dist/cjs/components/Dialog/Dialog.cjs.map +1 -1
  11. package/dist/cjs/components/DropdownMenu/DropdownMenu.cjs +160 -71
  12. package/dist/cjs/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
  13. package/dist/cjs/components/IconButton/IconButton.cjs +110 -20
  14. package/dist/cjs/components/IconButton/IconButton.cjs.map +1 -1
  15. package/dist/cjs/components/Icons/DenseGridViewIcon.cjs +52 -0
  16. package/dist/cjs/components/Icons/DenseGridViewIcon.cjs.map +1 -0
  17. package/dist/cjs/components/MediaStatusIndicator/MediaStatusIndicator.cjs +66 -0
  18. package/dist/cjs/components/MediaStatusIndicator/MediaStatusIndicator.cjs.map +1 -0
  19. package/dist/cjs/components/Select/Select.cjs +61 -17
  20. package/dist/cjs/components/Select/Select.cjs.map +1 -1
  21. package/dist/cjs/components/SubscribeButton/SubscribeButton.cjs +53 -0
  22. package/dist/cjs/components/SubscribeButton/SubscribeButton.cjs.map +1 -0
  23. package/dist/cjs/index.cjs +10 -0
  24. package/dist/cjs/index.cjs.map +1 -1
  25. package/dist/components/AudioRecordButton/AudioRecordButton.mjs +37 -0
  26. package/dist/components/AudioRecordButton/AudioRecordButton.mjs.map +1 -0
  27. package/dist/components/AudioUpload/AudioUpload.mjs +5 -5
  28. package/dist/components/AudioUpload/AudioUpload.mjs.map +1 -1
  29. package/dist/components/ChatInput/ChatInput.mjs +6 -11
  30. package/dist/components/ChatInput/ChatInput.mjs.map +1 -1
  31. package/dist/components/ChatMessage/ChatMessage.mjs +211 -0
  32. package/dist/components/ChatMessage/ChatMessage.mjs.map +1 -0
  33. package/dist/components/Dialog/Dialog.mjs +1 -1
  34. package/dist/components/Dialog/Dialog.mjs.map +1 -1
  35. package/dist/components/DropdownMenu/DropdownMenu.mjs +161 -72
  36. package/dist/components/DropdownMenu/DropdownMenu.mjs.map +1 -1
  37. package/dist/components/IconButton/IconButton.mjs +110 -20
  38. package/dist/components/IconButton/IconButton.mjs.map +1 -1
  39. package/dist/components/Icons/DenseGridViewIcon.mjs +35 -0
  40. package/dist/components/Icons/DenseGridViewIcon.mjs.map +1 -0
  41. package/dist/components/MediaStatusIndicator/MediaStatusIndicator.mjs +49 -0
  42. package/dist/components/MediaStatusIndicator/MediaStatusIndicator.mjs.map +1 -0
  43. package/dist/components/Select/Select.mjs +61 -17
  44. package/dist/components/Select/Select.mjs.map +1 -1
  45. package/dist/components/SubscribeButton/SubscribeButton.mjs +36 -0
  46. package/dist/components/SubscribeButton/SubscribeButton.mjs.map +1 -0
  47. package/dist/index.d.ts +272 -11
  48. package/dist/index.mjs +10 -0
  49. package/dist/index.mjs.map +1 -1
  50. package/dist/styles/theme.css +1 -1
  51. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatMessage.cjs","sources":["../../../../src/components/ChatMessage/ChatMessage.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { Avatar } from \"../Avatar/Avatar\";\nimport { DoubleTickIcon } from \"../Icons/DoubleTickIcon\";\nimport { TrashIcon } from \"../Icons/TrashIcon\";\n\nexport type ChatMessageUser = \"sender\" | \"receiver\";\n\nexport type ChatMessageVariant = \"text\" | \"typing\" | \"audio\" | \"deleted\";\n\nexport type ChatMessageStatus = \"delivered\" | \"read\";\n\nexport interface ChatMessageProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Who sent the message. `\"sender\"` is the current user (right-aligned, green\n * bubble, delivery tick). `\"receiver\"` is the other party (left-aligned, grey\n * bubble, with avatar). @default \"receiver\"\n */\n user?: ChatMessageUser;\n /** The kind of content the message carries. @default \"text\" */\n variant?: ChatMessageVariant;\n /** Message body for the `\"text\"` variant. Keep it to inline content. */\n message?: React.ReactNode;\n /** Timestamp shown with the message, e.g. `\"16:00\"`. */\n time?: string;\n /**\n * Delivery status shown on sender messages (a double tick). Ignored for\n * receiver messages. `\"read\"` renders the tick in the read colour. @default \"delivered\"\n */\n status?: ChatMessageStatus;\n /** Duration label for the `\"audio\"` variant, e.g. `\"0:05\"`. @default \"0:00\" */\n audioDuration?: string;\n /**\n * Relative bar heights (values `0`–`1`) for the `\"audio\"` waveform. Defaults\n * to a flat row of dots matching the unplayed design state.\n */\n waveform?: number[];\n /** Whether the audio is playing (controlled). Pairs with {@link onPlayingChange}. */\n playing?: boolean;\n /** Initial playing state when uncontrolled. @default false */\n defaultPlaying?: boolean;\n /** Fired when the audio play/pause button is pressed, with the next playing state. */\n onPlayingChange?: (playing: boolean) => void;\n /** Render the receiver avatar. Reserves the avatar space when `false` so grouped bubbles stay aligned. @default true */\n showAvatar?: boolean;\n /** Avatar image URL for receiver messages. */\n avatarSrc?: string;\n /** Avatar alt text. @default \"Avatar\" */\n avatarAlt?: string;\n /** Avatar fallback (initials or icon) shown before the image loads. */\n avatarFallback?: React.ReactNode;\n /** Show the online indicator on the receiver avatar. @default false */\n online?: boolean;\n /** Accessible label for the typing indicator. @default \"Typing\" */\n typingLabel?: string;\n /** Text shown for the `\"deleted\"` variant. @default \"Message deleted\" */\n deletedLabel?: string;\n /** Accessible label for the audio play button. @default \"Play\" */\n playLabel?: string;\n /** Accessible label for the audio pause button. @default \"Pause\" */\n pauseLabel?: string;\n}\n\nconst DEFAULT_WAVEFORM = Array.from({ length: 64 }, () => 0);\nconst WAVEFORM_MIN_HEIGHT = 4;\nconst WAVEFORM_MAX_HEIGHT = 24;\n\nconst bubbleColors: Record<ChatMessageUser, string> = {\n sender: \"bg-messages-background-sender border-messages-background-sender-stroke\",\n receiver: \"bg-messages-background-receiver border-messages-background-receiver-2\",\n};\n\nfunction ChatMessageMeta({\n time,\n showTick,\n status,\n className,\n}: {\n time?: string;\n showTick: boolean;\n status: ChatMessageStatus;\n className?: string;\n}) {\n return (\n <span\n className={cn(\n \"typography-description-12px-regular inline-flex items-center gap-1 whitespace-nowrap text-content-secondary leading-none\",\n className,\n )}\n >\n {time ? <time>{time}</time> : null}\n {showTick ? (\n // The 24px geometry (stroked) scaled to 16px — the icon's 16px variant\n // is filled and renders as a blob at this size.\n <DoubleTickIcon\n size={24}\n aria-hidden={false}\n role=\"img\"\n aria-label={status === \"read\" ? \"Read\" : \"Delivered\"}\n className={cn(\n \"size-4 shrink-0\",\n status === \"read\" ? \"text-messages-read\" : \"text-icons-disabled\",\n )}\n />\n ) : null}\n </span>\n );\n}\n\nfunction TypingIndicator({ label }: { label: string }) {\n const dot = \"size-2 shrink-0 rounded-full motion-safe:animate-bounce\";\n return (\n // biome-ignore lint/a11y/useSemanticElements: <output> is not appropriate for a typing indicator; role=\"status\" is the correct live-region pattern\n <span role=\"status\" aria-label={label} className=\"flex items-center gap-2 px-0.5 py-1\">\n <span className={cn(dot, \"bg-content-primary\")} />\n <span className={cn(dot, \"bg-neutral-alphas-700 [animation-delay:150ms]\")} />\n <span className={cn(dot, \"bg-neutral-alphas-400 [animation-delay:300ms]\")} />\n </span>\n );\n}\n\nfunction Waveform({ bars }: { bars: number[] }) {\n return (\n <span className=\"flex min-w-0 flex-1 items-center gap-1 overflow-hidden\" aria-hidden>\n {bars.map((amplitude, index) => {\n const clamped = Math.min(1, Math.max(0, amplitude));\n const height = WAVEFORM_MIN_HEIGHT + clamped * (WAVEFORM_MAX_HEIGHT - WAVEFORM_MIN_HEIGHT);\n return (\n <span\n // biome-ignore lint/suspicious/noArrayIndexKey: bars are positional and have no stable id\n key={index}\n className=\"w-1 shrink-0 rounded-3xs bg-messages-waveform-default\"\n style={{ height }}\n />\n );\n })}\n </span>\n );\n}\n\nfunction PlayGlyph() {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\" className=\"size-6\">\n <path d=\"M8 5.14v13.72a1 1 0 0 0 1.54.84l10.28-6.86a1 1 0 0 0 0-1.68L9.54 4.3A1 1 0 0 0 8 5.14Z\" />\n </svg>\n );\n}\n\nfunction PauseGlyph() {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\" className=\"size-6\">\n <rect x=\"7\" y=\"5\" width=\"3.5\" height=\"14\" rx=\"1\" />\n <rect x=\"13.5\" y=\"5\" width=\"3.5\" height=\"14\" rx=\"1\" />\n </svg>\n );\n}\n\n/**\n * A single chat message rendered as a bubble. Sender messages sit on the right\n * with a green bubble and a delivery tick; receiver messages sit on the left\n * with a grey bubble and an avatar. Supports plain text, a typing indicator, a\n * voice message with waveform, and a deleted-message placeholder.\n *\n * Text bubbles place the timestamp inline after short messages and drop it to\n * the bottom-right corner once the text wraps, so no variant switch is needed\n * for short versus long content.\n *\n * @example\n * ```tsx\n * <ChatMessage user=\"sender\" message=\"On my way!\" time=\"16:00\" status=\"read\" />\n * <ChatMessage user=\"receiver\" message=\"See you soon\" time=\"16:01\" avatarSrc=\"/jane.jpg\" online />\n * <ChatMessage user=\"receiver\" variant=\"typing\" avatarSrc=\"/jane.jpg\" />\n * ```\n */\nexport const ChatMessage = React.forwardRef<HTMLDivElement, ChatMessageProps>(\n (\n {\n className,\n user = \"receiver\",\n variant = \"text\",\n message,\n time,\n status = \"delivered\",\n audioDuration = \"0:00\",\n waveform,\n playing,\n defaultPlaying = false,\n onPlayingChange,\n showAvatar = true,\n avatarSrc,\n avatarAlt,\n avatarFallback,\n online = false,\n typingLabel = \"Typing\",\n deletedLabel = \"Message deleted\",\n playLabel = \"Play\",\n pauseLabel = \"Pause\",\n ...props\n },\n ref,\n ) => {\n const isSender = user === \"sender\";\n const showTick = isSender;\n\n const [internalPlaying, setInternalPlaying] = React.useState(defaultPlaying);\n const isPlaying = playing ?? internalPlaying;\n const togglePlaying = () => {\n const next = !isPlaying;\n if (playing === undefined) setInternalPlaying(next);\n onPlayingChange?.(next);\n };\n\n const bubbleBase = cn(\n \"w-fit min-w-0 max-w-full rounded-md border border-solid\",\n bubbleColors[user],\n );\n const hasMeta = Boolean(time) || showTick;\n\n let bubble: React.ReactNode;\n\n if (variant === \"typing\") {\n bubble = (\n <div className={cn(bubbleBase, \"px-3 py-2\")}>\n <TypingIndicator label={typingLabel} />\n </div>\n );\n } else if (variant === \"deleted\") {\n bubble = (\n <div className={cn(bubbleBase, \"flex items-baseline gap-6 px-3 py-2 opacity-60\")}>\n <span className=\"flex items-center gap-2\">\n <TrashIcon size={16} className=\"shrink-0 text-content-primary\" />\n <span className=\"typography-body-default-16px-regular whitespace-nowrap text-content-primary\">\n {deletedLabel}\n </span>\n </span>\n {hasMeta ? <ChatMessageMeta time={time} showTick={showTick} status={status} /> : null}\n </div>\n );\n } else if (variant === \"audio\") {\n bubble = (\n <div className={cn(bubbleBase, \"flex w-full flex-col p-2\")}>\n <div className=\"flex w-full flex-col items-start gap-2 px-1\">\n <div className=\"flex w-full items-center gap-3 pt-1\">\n <button\n type=\"button\"\n onClick={togglePlaying}\n aria-pressed={isPlaying}\n aria-label={isPlaying ? pauseLabel : playLabel}\n className={cn(\n \"flex size-12 shrink-0 items-center justify-center rounded-full bg-buttons-secondary-default text-icons-primary\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none motion-safe:transition-colors motion-safe:duration-150\",\n )}\n >\n {isPlaying ? <PauseGlyph /> : <PlayGlyph />}\n </button>\n <Waveform bars={waveform ?? DEFAULT_WAVEFORM} />\n <span className=\"typography-body-small-14px-regular whitespace-nowrap text-content-primary\">\n {audioDuration}\n </span>\n </div>\n {hasMeta ? (\n <ChatMessageMeta\n time={time}\n showTick={showTick}\n status={status}\n className=\"self-end\"\n />\n ) : null}\n </div>\n </div>\n );\n } else {\n // \"text\": the message and timestamp share a wrapping row. A brief message\n // keeps the time inline (24px gap = the design's Short treatment); once the\n // text wraps, the time drops to its own line bottom-right (the Default\n // treatment). No length prop needed — the layout adapts to the content.\n bubble = (\n <div className={cn(bubbleBase, \"px-3 py-2\")}>\n <div className=\"flex flex-wrap items-baseline justify-end gap-x-6 gap-y-2\">\n <p className=\"typography-body-default-16px-regular m-0 min-w-0 break-words text-content-primary\">\n {message}\n </p>\n {hasMeta ? (\n <ChatMessageMeta\n time={time}\n showTick={showTick}\n status={status}\n className=\"shrink-0\"\n />\n ) : null}\n </div>\n </div>\n );\n }\n\n const alignItems = variant === \"typing\" || variant === \"deleted\" ? \"items-center\" : \"items-end\";\n\n return (\n <div\n ref={ref}\n className={cn(\n \"flex min-h-10 w-full gap-2\",\n alignItems,\n isSender ? \"justify-end\" : \"justify-start\",\n className,\n )}\n {...props}\n >\n {!isSender &&\n (showAvatar ? (\n <div className=\"relative flex shrink-0\">\n <Avatar size={40} src={avatarSrc} alt={avatarAlt} fallback={avatarFallback} />\n {online ? (\n <span\n aria-hidden=\"true\"\n className=\"absolute right-0 bottom-0 size-2.5 rounded-full border border-border-background bg-messages-status-active\"\n />\n ) : null}\n </div>\n ) : (\n <div aria-hidden=\"true\" className=\"w-10 shrink-0\" />\n ))}\n {bubble}\n </div>\n );\n },\n);\n\nChatMessage.displayName = \"ChatMessage\";\n"],"names":["jsxs","cn","jsx","DoubleTickIcon","React","TrashIcon","Avatar"],"mappings":"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"}
@@ -139,7 +139,7 @@ const DialogHeader = React__namespace.forwardRef(
139
139
  "div",
140
140
  {
141
141
  ref,
142
- className: cn.cn("flex shrink-0 items-center justify-end gap-4", className),
142
+ className: cn.cn("flex shrink-0 items-start justify-end gap-4", className),
143
143
  ...props,
144
144
  children: [
145
145
  shouldShowBack && /* @__PURE__ */ jsxRuntime.jsx(
@@ -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 { 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 by {@link DialogContent}; portaled to `document.body` when {@link DialogContent} `portal` is true.\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-background-overlay-default 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 * When true, teleports overlay and panel to `document.body`.\n * When false, renders inline in the React tree (useful inside theme providers or scoped containers).\n * @default true\n */\n portal?: boolean;\n /** Show the v2 mobile sheet pull handle. Only rendered when `mobilePresentation` is `\"sheet\"`. @default true */\n showMobileHandle?: boolean;\n /**\n * How the dialog presents below the `sm` breakpoint.\n * - `\"sheet\"` — bottom sheet pinned to the viewport bottom edge (default)\n * - `\"card\"` — centered floating card per the v2-modal confirmation spec:\n * 16px side margins, 24px padding, 32px radius on all corners, no pull handle\n *\n * @default \"sheet\"\n */\n mobilePresentation?: \"sheet\" | \"card\";\n /** Props forwarded to the default {@link DialogOverlay} when `overlay` is `true`. */\n overlayProps?: DialogOverlayProps;\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. Includes the overlay by default and portals to `document.body` by default.\n *\n * Set `portal={false}` to keep overlay and content in the DOM subtree of the parent `Dialog`.\n * `fixed` positioning still applies; ancestors with `transform` or `overflow` may affect layout.\n *\n * On mobile viewports (<640px), the dialog slides up from the bottom as a sheet\n * with top-only border radius by default; pass `mobilePresentation=\"card\"` to\n * render a centered floating card instead (used for small confirmation dialogs).\n * On larger viewports it renders centered with 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>(\n (\n {\n className,\n children,\n size = \"md\",\n overlay = true,\n portal = true,\n showMobileHandle = true,\n mobilePresentation = \"sheet\",\n overlayProps,\n style,\n onOpenAutoFocus,\n ...props\n },\n ref,\n ) => {\n const content = (\n <>\n {overlay && <DialogOverlay {...overlayProps} />}\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 \"fixed flex flex-col overflow-hidden border border-modal-stroke bg-modal-background shadow-blur-menu backdrop-blur-[4px] focus:outline-none\",\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 mobilePresentation === \"card\"\n ? // Floating confirmation card (v2-modal): 16px side margins, vertically centered, 32px radius\n cn(\n \"dialog-max-h-dynamic inset-x-4 top-1/2 -translate-y-1/2 rounded-xl p-6\",\n \"data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95\",\n \"sm:inset-x-auto\",\n )\n : // Bottom sheet pinned to the viewport bottom edge\n cn(\n \"dialog-max-h-dynamic inset-x-0 bottom-0 w-full rounded-t-xl p-4 pt-3\",\n \"pb-[calc(1rem+env(safe-area-inset-bottom,0px))]\",\n \"data-[state=open]:slide-in-from-bottom-full\",\n \"data-[state=closed]:slide-out-to-bottom-full\",\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 ),\n \"sm:dialog-max-h-dynamic sm:inset-auto sm:top-1/2 sm:left-1/2 sm:w-full sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-lg sm:p-6\",\n \"duration-200\",\n SIZE_CLASSES[size],\n className,\n )}\n {...props}\n >\n {showMobileHandle && mobilePresentation === \"sheet\" && (\n <div\n aria-hidden=\"true\"\n className=\"mb-3 h-1 w-8 shrink-0 self-center rounded-full bg-icons-tertiary sm:hidden\"\n />\n )}\n {children}\n </DialogPrimitive.Content>\n </>\n );\n\n return portal ? <DialogPrimitive.Portal>{content}</DialogPrimitive.Portal> : content;\n },\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 shrink-0 items-center justify-end gap-4\", className)}\n {...props}\n >\n {shouldShowBack && (\n <IconButton\n variant=\"secondary\"\n size=\"32\"\n icon={<ArrowLeftIcon size={16} />}\n onClick={onBack}\n disabled={!onBack}\n aria-label={backLabel}\n />\n )}\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 variant=\"secondary\"\n size=\"32\"\n icon={<CloseIcon size={16} />}\n aria-label={closeLabel}\n />\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-header-heading-xs 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-body-default-16px-regular 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 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(\"flex shrink-0 items-center gap-2\", \"[&>*]:min-w-0 [&>*]:flex-1\", className)}\n {...props}\n />\n ),\n);\nDialogFooter.displayName = \"DialogFooter\";\n"],"names":["DialogPrimitive","React","jsx","useSuppressClickAfterDrag","cn","jsxs","Fragment","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 by {@link DialogContent}; portaled to `document.body` when {@link DialogContent} `portal` is true.\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-background-overlay-default 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 * When true, teleports overlay and panel to `document.body`.\n * When false, renders inline in the React tree (useful inside theme providers or scoped containers).\n * @default true\n */\n portal?: boolean;\n /** Show the v2 mobile sheet pull handle. Only rendered when `mobilePresentation` is `\"sheet\"`. @default true */\n showMobileHandle?: boolean;\n /**\n * How the dialog presents below the `sm` breakpoint.\n * - `\"sheet\"` — bottom sheet pinned to the viewport bottom edge (default)\n * - `\"card\"` — centered floating card per the v2-modal confirmation spec:\n * 16px side margins, 24px padding, 32px radius on all corners, no pull handle\n *\n * @default \"sheet\"\n */\n mobilePresentation?: \"sheet\" | \"card\";\n /** Props forwarded to the default {@link DialogOverlay} when `overlay` is `true`. */\n overlayProps?: DialogOverlayProps;\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. Includes the overlay by default and portals to `document.body` by default.\n *\n * Set `portal={false}` to keep overlay and content in the DOM subtree of the parent `Dialog`.\n * `fixed` positioning still applies; ancestors with `transform` or `overflow` may affect layout.\n *\n * On mobile viewports (<640px), the dialog slides up from the bottom as a sheet\n * with top-only border radius by default; pass `mobilePresentation=\"card\"` to\n * render a centered floating card instead (used for small confirmation dialogs).\n * On larger viewports it renders centered with 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>(\n (\n {\n className,\n children,\n size = \"md\",\n overlay = true,\n portal = true,\n showMobileHandle = true,\n mobilePresentation = \"sheet\",\n overlayProps,\n style,\n onOpenAutoFocus,\n ...props\n },\n ref,\n ) => {\n const content = (\n <>\n {overlay && <DialogOverlay {...overlayProps} />}\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 \"fixed flex flex-col overflow-hidden border border-modal-stroke bg-modal-background shadow-blur-menu backdrop-blur-[4px] focus:outline-none\",\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 mobilePresentation === \"card\"\n ? // Floating confirmation card (v2-modal): 16px side margins, vertically centered, 32px radius\n cn(\n \"dialog-max-h-dynamic inset-x-4 top-1/2 -translate-y-1/2 rounded-xl p-6\",\n \"data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95\",\n \"sm:inset-x-auto\",\n )\n : // Bottom sheet pinned to the viewport bottom edge\n cn(\n \"dialog-max-h-dynamic inset-x-0 bottom-0 w-full rounded-t-xl p-4 pt-3\",\n \"pb-[calc(1rem+env(safe-area-inset-bottom,0px))]\",\n \"data-[state=open]:slide-in-from-bottom-full\",\n \"data-[state=closed]:slide-out-to-bottom-full\",\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 ),\n \"sm:dialog-max-h-dynamic sm:inset-auto sm:top-1/2 sm:left-1/2 sm:w-full sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-lg sm:p-6\",\n \"duration-200\",\n SIZE_CLASSES[size],\n className,\n )}\n {...props}\n >\n {showMobileHandle && mobilePresentation === \"sheet\" && (\n <div\n aria-hidden=\"true\"\n className=\"mb-3 h-1 w-8 shrink-0 self-center rounded-full bg-icons-tertiary sm:hidden\"\n />\n )}\n {children}\n </DialogPrimitive.Content>\n </>\n );\n\n return portal ? <DialogPrimitive.Portal>{content}</DialogPrimitive.Portal> : content;\n },\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 // items-start (not items-center) so the back/close buttons align to the\n // title's first line; otherwise they float to the vertical center of a\n // multi-line title + description column.\n className={cn(\"flex shrink-0 items-start justify-end gap-4\", className)}\n {...props}\n >\n {shouldShowBack && (\n <IconButton\n variant=\"secondary\"\n size=\"32\"\n icon={<ArrowLeftIcon size={16} />}\n onClick={onBack}\n disabled={!onBack}\n aria-label={backLabel}\n />\n )}\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 variant=\"secondary\"\n size=\"32\"\n icon={<CloseIcon size={16} />}\n aria-label={closeLabel}\n />\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-header-heading-xs 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-body-default-16px-regular 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 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(\"flex shrink-0 items-center gap-2\", \"[&>*]:min-w-0 [&>*]:flex-1\", className)}\n {...props}\n />\n ),\n);\nDialogFooter.displayName = \"DialogFooter\";\n"],"names":["DialogPrimitive","React","jsx","useSuppressClickAfterDrag","cn","jsxs","Fragment","IconButton","ArrowLeftIcon","CloseIcon"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmBO,MAAM,SAASA,2BAAgB;AAY/B,MAAM,gBAAgBC,iBAAM,WAGjC,CAAC,OAAO,QAAQC,2BAAAA,IAACF,2BAAgB,SAAhB,EAAwB,KAAW,GAAGG,0BAAAA,0BAA0B,KAAK,GAAG,CAAE;AAC7F,cAAc,cAAc;AAGrB,MAAM,cAAcH,2BAAgB;AAYpC,MAAM,gBAAgBC,iBAAM,WAGjC,CAAC,EAAE,WAAW,OAAO,GAAG,SAAS,QACjCC,2BAAAA;AAAAA,EAACF,2BAAgB;AAAA,EAAhB;AAAA,IACC;AAAA,IACA,WAAWI,GAAAA;AAAAA,MACT;AAAA,MACA;AAAA,IAAA;AAAA,IAEF,OAAO,EAAE,QAAQ,uCAAuC,GAAG,MAAA;AAAA,IAC1D,GAAG;AAAA,EAAA;AACN,CACD;AACD,cAAc,cAAc;AAoC5B,MAAM,eAAwE;AAAA,EAC5E,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAkCO,MAAM,gBAAgBH,iBAAM;AAAA,EAIjC,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,UAAU;AAAA,IACV,SAAS;AAAA,IACT,mBAAmB;AAAA,IACnB,qBAAqB;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,UACJI,2BAAAA,KAAAC,WAAAA,UAAA,EACG,UAAA;AAAA,MAAA,WAAWJ,2BAAAA,IAAC,eAAA,EAAe,GAAG,aAAA,CAAc;AAAA,MAC7CG,2BAAAA;AAAAA,QAACL,2BAAgB;AAAA,QAAhB;AAAA,UACC;AAAA,UACA,OAAO,EAAE,QAAQ,uCAAuC,GAAG,MAAA;AAAA,UAC3D,iBAAiB,CAAC,MAAM;AACtB,gBAAI,iBAAiB;AACnB,8BAAgB,CAAC;AACjB;AAAA,YACF;AACA,cAAE,eAAA;AACD,cAAE,cAA8B,MAAA;AAAA,UACnC;AAAA,UACA,WAAWI,GAAAA;AAAAA,YACT;AAAA,YACA;AAAA,YACA;AAAA,YACA,uBAAuB;AAAA;AAAA,cAEnBA,GAAAA;AAAAA,gBACE;AAAA,gBACA;AAAA,gBACA;AAAA,cAAA;AAAA;AAAA;AAAA,cAGFA,GAAAA;AAAAA,gBACE;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,cAAA;AAAA;AAAA,YAEN;AAAA,YACA;AAAA,YACA,aAAa,IAAI;AAAA,YACjB;AAAA,UAAA;AAAA,UAED,GAAG;AAAA,UAEH,UAAA;AAAA,YAAA,oBAAoB,uBAAuB,WAC1CF,2BAAAA;AAAAA,cAAC;AAAA,cAAA;AAAA,gBACC,eAAY;AAAA,gBACZ,WAAU;AAAA,cAAA;AAAA,YAAA;AAAA,YAGb;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IACH,GACF;AAGF,WAAO,SAASA,2BAAAA,IAACF,2BAAgB,QAAhB,EAAwB,mBAAQ,IAA4B;AAAA,EAC/E;AACF;AACA,cAAc,cAAc;AA8BrB,MAAM,eAAeC,iBAAM;AAAA,EAChC,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,iBAAiB,YAAY,CAAC,CAAC;AAErC,WACEI,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QAIA,WAAWD,GAAAA,GAAG,+CAA+C,SAAS;AAAA,QACrE,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,kBACCF,2BAAAA;AAAAA,YAACK,WAAAA;AAAAA,YAAA;AAAA,cACC,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,MAAML,2BAAAA,IAACM,cAAAA,eAAA,EAAc,MAAM,GAAA,CAAI;AAAA,cAC/B,SAAS;AAAA,cACT,UAAU,CAAC;AAAA,cACX,cAAY;AAAA,YAAA;AAAA,UAAA;AAAA,UAGhBN,2BAAAA,IAAC,OAAA,EAAI,WAAU,wCAAwC,SAAA,CAAS;AAAA,UAC/D,aACCA,2BAAAA,IAACF,2BAAgB,OAAhB,EAAsB,SAAO,MAC5B,UAAAE,2BAAAA;AAAAA,YAACK,WAAAA;AAAAA,YAAA;AAAA,cACC,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,MAAML,2BAAAA,IAACO,UAAAA,WAAA,EAAU,MAAM,GAAA,CAAI;AAAA,cAC3B,cAAY;AAAA,YAAA;AAAA,UAAA,EACd,CACF;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AACA,aAAa,cAAc;AASpB,MAAM,cAAcR,iBAAM,WAG/B,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QAC1BC,2BAAAA;AAAAA,EAACF,2BAAgB;AAAA,EAAhB;AAAA,IACC;AAAA,IACA,WAAWI,GAAAA,GAAG,qDAAqD,SAAS;AAAA,IAC3E,GAAG;AAAA,EAAA;AACN,CACD;AACD,YAAY,cAAc;AAQnB,MAAM,oBAAoBH,iBAAM,WAGrC,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QAC1BC,2BAAAA;AAAAA,EAACF,2BAAgB;AAAA,EAAhB;AAAA,IACC;AAAA,IACA,WAAWI,GAAAA,GAAG,+DAA+D,SAAS;AAAA,IACrF,GAAG;AAAA,EAAA;AACN,CACD;AACD,kBAAkB,cAAc;AAQzB,MAAM,aAAaH,iBAAM;AAAA,EAC9B,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QACxBC,2BAAAA,IAAC,OAAA,EAAI,KAAU,WAAWE,GAAAA,GAAG,+BAA+B,SAAS,GAAI,GAAG,MAAA,CAAO;AAEvF;AACA,WAAW,cAAc;AAQlB,MAAM,eAAeH,iBAAM;AAAA,EAChC,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QACxBC,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,WAAWE,GAAAA,GAAG,oCAAoC,8BAA8B,SAAS;AAAA,MACxF,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;AACA,aAAa,cAAc;;;;;;;;;;;"}
@@ -3,11 +3,14 @@
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 reactSlot = require("@radix-ui/react-slot");
6
7
  const reactUseControllableState = require("@radix-ui/react-use-controllable-state");
7
8
  const React = require("react");
8
9
  const cn = require("../../utils/cn.cjs");
9
10
  const floatingContentCollisionPadding = require("../../utils/floatingContentCollisionPadding.cjs");
11
+ const Drawer = require("../Drawer/Drawer.cjs");
10
12
  const IconButton = require("../IconButton/IconButton.cjs");
13
+ const CheckIcon = require("../Icons/CheckIcon.cjs");
11
14
  const CloseIcon = require("../Icons/CloseIcon.cjs");
12
15
  const SearchIcon = require("../Icons/SearchIcon.cjs");
13
16
  function _interopNamespaceDefault(e) {
@@ -39,10 +42,12 @@ const NAVIGATION_KEYS = /* @__PURE__ */ new Set([
39
42
  "Enter"
40
43
  ]);
41
44
  const ToggleOpenContext = React__namespace.createContext(null);
45
+ const DropdownMenuVariantContext = React__namespace.createContext("menu");
42
46
  function DropdownMenu({
43
47
  open: openProp,
44
48
  defaultOpen,
45
49
  onOpenChange,
50
+ variant = "menu",
46
51
  children,
47
52
  ...props
48
53
  }) {
@@ -51,11 +56,18 @@ function DropdownMenu({
51
56
  defaultProp: defaultOpen ?? false,
52
57
  onChange: onOpenChange
53
58
  });
54
- return /* @__PURE__ */ jsxRuntime.jsx(ToggleOpenContext.Provider, { value: setOpen, children: /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuPrimitive__namespace.Root, { open, onOpenChange: setOpen, ...props, children }) });
59
+ if (variant === "sheet") {
60
+ return /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuVariantContext.Provider, { value: "sheet", children: /* @__PURE__ */ jsxRuntime.jsx(ToggleOpenContext.Provider, { value: setOpen, children: /* @__PURE__ */ jsxRuntime.jsx(Drawer.Drawer, { open, onOpenChange: setOpen, children }) }) });
61
+ }
62
+ return /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuVariantContext.Provider, { value: "menu", children: /* @__PURE__ */ jsxRuntime.jsx(ToggleOpenContext.Provider, { value: setOpen, children: /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuPrimitive__namespace.Root, { open, onOpenChange: setOpen, ...props, children }) }) });
55
63
  }
56
64
  const DropdownMenuTrigger = React__namespace.forwardRef((props, ref) => {
65
+ const variant = React__namespace.useContext(DropdownMenuVariantContext);
57
66
  const toggleOpen = React__namespace.useContext(ToggleOpenContext);
58
67
  const tapRef = React__namespace.useRef(null);
68
+ if (variant === "sheet") {
69
+ return /* @__PURE__ */ jsxRuntime.jsx(Drawer.DrawerTrigger, { ...props, ref });
70
+ }
59
71
  if (toggleOpen === null) {
60
72
  return /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuPrimitive__namespace.Trigger, { ...props, ref });
61
73
  }
@@ -114,46 +126,68 @@ const DropdownMenuContent = React__namespace.forwardRef(
114
126
  className,
115
127
  style,
116
128
  sideOffset = 4,
129
+ // Radix defaults `avoidCollisions` to true, so passing `collisionPadding`
130
+ // is enough to keep the menu flipping/shifting to stay on screen — no
131
+ // hand-rolled reposition logic needed.
117
132
  collisionPadding = floatingContentCollisionPadding.FLOATING_CONTENT_COLLISION_PADDING,
133
+ children,
118
134
  ...props
119
- }, ref) => /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
120
- DropdownMenuPrimitive__namespace.Content,
121
- {
122
- ref,
123
- sideOffset,
124
- collisionPadding,
125
- className: cn.cn(
126
- "w-max min-w-(--radix-dropdown-menu-trigger-width) max-w-(--radix-dropdown-menu-content-available-width) overflow-y-auto rounded-sm border border-neutral-alphas-200 bg-surface-primary p-1 text-content-primary shadow-lg",
127
- "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
128
- "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
129
- "data-[side=top]:slide-in-from-bottom-2 data-[side=bottom]:slide-in-from-top-2",
130
- "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
131
- className
132
- ),
133
- style: {
134
- zIndex: "var(--fanvue-ui-portal-z-index, 50)",
135
- maxHeight: "var(--radix-dropdown-menu-content-available-height)",
136
- ...style
137
- },
138
- ...props
135
+ }, ref) => {
136
+ const variant = React__namespace.useContext(DropdownMenuVariantContext);
137
+ if (variant === "sheet") {
138
+ return /* @__PURE__ */ jsxRuntime.jsx(
139
+ Drawer.DrawerContent,
140
+ {
141
+ ref,
142
+ position: "bottom",
143
+ variant: "sheet",
144
+ className: cn.cn("flex flex-col gap-1 p-1", className),
145
+ style,
146
+ ...props,
147
+ children
148
+ }
149
+ );
139
150
  }
140
- ) })
151
+ return /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
152
+ DropdownMenuPrimitive__namespace.Content,
153
+ {
154
+ ref,
155
+ sideOffset,
156
+ collisionPadding,
157
+ className: cn.cn(
158
+ "w-max min-w-(--radix-dropdown-menu-trigger-width) max-w-(--radix-dropdown-menu-content-available-width) overflow-y-auto rounded-sm border border-neutral-alphas-200 bg-surface-primary p-1 text-content-primary shadow-lg",
159
+ "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
160
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
161
+ "data-[side=top]:slide-in-from-bottom-2 data-[side=bottom]:slide-in-from-top-2",
162
+ "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
163
+ className
164
+ ),
165
+ style: {
166
+ zIndex: "var(--fanvue-ui-portal-z-index, 50)",
167
+ maxHeight: "var(--radix-dropdown-menu-content-available-height)",
168
+ ...style
169
+ },
170
+ ...props,
171
+ children
172
+ }
173
+ ) });
174
+ }
141
175
  );
142
176
  DropdownMenuContent.displayName = "DropdownMenuContent";
143
177
  const DropdownMenuGroup = DropdownMenuPrimitive__namespace.Group;
144
178
  DropdownMenuGroup.displayName = "DropdownMenuGroup";
145
- const DropdownMenuLabel = React__namespace.forwardRef(({ className, position = "default", ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
146
- DropdownMenuPrimitive__namespace.Label,
147
- {
148
- ref,
149
- className: cn.cn(
150
- "typography-description-12px-regular flex items-center px-3 text-content-secondary",
151
- position === "top" ? "py-2" : "pb-2 pt-4",
152
- className
153
- ),
154
- ...props
179
+ const DropdownMenuLabel = React__namespace.forwardRef(({ className, position = "default", ...props }, ref) => {
180
+ const variant = React__namespace.useContext(DropdownMenuVariantContext);
181
+ const labelClassName = cn.cn(
182
+ "typography-description-12px-regular flex items-center px-3 text-content-secondary",
183
+ position === "top" ? "py-2" : "pb-2 pt-4",
184
+ className
185
+ );
186
+ if (variant === "sheet") {
187
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: labelClassName, ...props });
155
188
  }
156
- ));
189
+ return /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuPrimitive__namespace.Label, { ref, className: labelClassName, ...props });
190
+ });
157
191
  DropdownMenuLabel.displayName = "DropdownMenuLabel";
158
192
  const SIZE_NORMALIZED = {
159
193
  "40": "40",
@@ -165,14 +199,18 @@ const ITEM_SIZE_CLASSES = {
165
199
  "40": "min-h-10 py-2 typography-body-default-16px-regular",
166
200
  "32": "min-h-8 py-[7px] typography-body-small-14px-regular"
167
201
  };
168
- const ITEM_SELECTED_TYPOGRAPHY = {
169
- "40": "typography-body-default-16px-semibold",
170
- "32": "typography-body-small-14px-semibold"
171
- };
172
202
  const ITEM_COUNT_TYPOGRAPHY = {
173
203
  "40": "typography-body-default-16px-regular",
174
204
  "32": "typography-body-small-14px-regular"
175
205
  };
206
+ function SelectedCheckIndicator({ hasDescription }) {
207
+ return /* @__PURE__ */ jsxRuntime.jsx(
208
+ CheckIcon.CheckIcon,
209
+ {
210
+ className: cn.cn("size-4 shrink-0 text-content-primary", hasDescription && "self-start")
211
+ }
212
+ );
213
+ }
176
214
  const DropdownMenuItem = React__namespace.forwardRef(
177
215
  ({
178
216
  size = "40",
@@ -186,8 +224,12 @@ const DropdownMenuItem = React__namespace.forwardRef(
186
224
  className,
187
225
  children,
188
226
  asChild,
227
+ onSelect,
228
+ disabled,
189
229
  ...props
190
230
  }, ref) => {
231
+ const variant = React__namespace.useContext(DropdownMenuVariantContext);
232
+ const toggleOpen = React__namespace.useContext(ToggleOpenContext);
191
233
  const normalizedSize = SIZE_NORMALIZED[size];
192
234
  const hasDescription = description != null;
193
235
  const hasAvatar = avatar != null;
@@ -200,17 +242,19 @@ const DropdownMenuItem = React__namespace.forwardRef(
200
242
  hasAvatar && !hasDescription && normalizedSize === "32" && "py-1",
201
243
  "data-[highlighted]:bg-neutral-alphas-50",
202
244
  "data-[disabled]:cursor-not-allowed data-[disabled]:text-content-disabled",
245
+ "disabled:cursor-not-allowed disabled:text-content-disabled",
246
+ // Sheet-variant asChild items are marked disabled via aria-disabled
247
+ // (see below), not the native disabled attribute or Radix's
248
+ // data-disabled — neither selector above matches them.
249
+ "aria-disabled:cursor-not-allowed aria-disabled:text-content-disabled",
203
250
  destructive && "text-error-content",
204
- selected && [
205
- "bg-buttons-primary-default text-content-primary-inverted",
206
- "data-[highlighted]:bg-buttons-primary-default",
207
- ITEM_SELECTED_TYPOGRAPHY[normalizedSize]
208
- ],
251
+ // bg-interaction-hover aliases to the same token as the plain hover
252
+ // background above, so a selected row would be indistinguishable from a
253
+ // hovered-but-unselected one. Use the next step up the neutral-alphas
254
+ // ramp instead (still a subtle overlay, not the heavy filled style).
255
+ selected && ["bg-neutral-alphas-100", "data-[highlighted]:bg-neutral-alphas-200"],
209
256
  className
210
257
  );
211
- if (asChild) {
212
- return /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuPrimitive__namespace.Item, { ref, asChild: true, className: itemClassName, ...props, children });
213
- }
214
258
  const iconAlignClassName = hasDescription ? "flex shrink-0 items-center pt-1" : null;
215
259
  const countNode = count != null && /* @__PURE__ */ jsxRuntime.jsx(
216
260
  "span",
@@ -218,41 +262,85 @@ const DropdownMenuItem = React__namespace.forwardRef(
218
262
  className: cn.cn(
219
263
  "shrink-0 tabular-nums",
220
264
  ITEM_COUNT_TYPOGRAPHY[normalizedSize],
221
- destructive ? "text-error-content" : selected ? "text-content-primary-inverted" : "text-content-tertiary",
265
+ destructive ? "text-error-content" : "text-content-tertiary",
222
266
  "group-data-[disabled]:text-content-disabled"
223
267
  ),
224
268
  children: count
225
269
  }
226
270
  );
227
- return /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuPrimitive__namespace.Item, { ref, className: itemClassName, ...props, children: [
271
+ const trailingNode = trailingIcon != null ? hasDescription ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: iconAlignClassName, children: trailingIcon }) : trailingIcon : selected && /* @__PURE__ */ jsxRuntime.jsx(SelectedCheckIndicator, { hasDescription });
272
+ const itemChildren = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
228
273
  avatar != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0", children: avatar }) : leadingIcon != null && (hasDescription ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: iconAlignClassName, children: leadingIcon }) : leadingIcon),
229
274
  hasDescription ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
230
275
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children }),
231
- /* @__PURE__ */ jsxRuntime.jsx(
232
- "span",
233
- {
234
- className: cn.cn(
235
- "typography-body-small-14px-regular truncate",
236
- selected ? "text-content-primary-inverted" : "text-content-secondary"
237
- ),
238
- children: description
239
- }
240
- )
276
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typography-body-small-14px-regular truncate text-content-secondary", children: description })
241
277
  ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children }),
242
278
  countNode,
243
- trailingIcon != null && (hasDescription ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: iconAlignClassName, children: trailingIcon }) : trailingIcon)
279
+ trailingNode
244
280
  ] });
281
+ if (variant === "sheet") {
282
+ const Comp = asChild ? reactSlot.Slot : "button";
283
+ const { onClick: consumerOnClick, ...restProps } = props;
284
+ const sheetSpecificProps = !asChild ? { type: "button", disabled } : disabled ? { "aria-disabled": true } : {};
285
+ return /* @__PURE__ */ jsxRuntime.jsx(
286
+ Comp,
287
+ {
288
+ ref,
289
+ ...restProps,
290
+ ...sheetSpecificProps,
291
+ role: "option",
292
+ "aria-selected": selected,
293
+ className: itemClassName,
294
+ onClick: (event) => {
295
+ if (disabled) {
296
+ event.preventDefault();
297
+ return;
298
+ }
299
+ consumerOnClick?.(event);
300
+ if (event.defaultPrevented) return;
301
+ onSelect?.(event.nativeEvent);
302
+ if (!event.nativeEvent.defaultPrevented) toggleOpen?.(() => false);
303
+ },
304
+ children: asChild ? children : itemChildren
305
+ }
306
+ );
307
+ }
308
+ if (asChild) {
309
+ return /* @__PURE__ */ jsxRuntime.jsx(
310
+ DropdownMenuPrimitive__namespace.Item,
311
+ {
312
+ ref,
313
+ asChild: true,
314
+ className: itemClassName,
315
+ disabled,
316
+ onSelect,
317
+ ...props,
318
+ children
319
+ }
320
+ );
321
+ }
322
+ return /* @__PURE__ */ jsxRuntime.jsx(
323
+ DropdownMenuPrimitive__namespace.Item,
324
+ {
325
+ ref,
326
+ className: itemClassName,
327
+ disabled,
328
+ onSelect,
329
+ ...props,
330
+ children: itemChildren
331
+ }
332
+ );
245
333
  }
246
334
  );
247
335
  DropdownMenuItem.displayName = "DropdownMenuItem";
248
- const DropdownMenuSeparator = React__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
249
- DropdownMenuPrimitive__namespace.Separator,
250
- {
251
- ref,
252
- className: cn.cn("my-1 h-px bg-neutral-alphas-200", className),
253
- ...props
336
+ const DropdownMenuSeparator = React__namespace.forwardRef(({ className, ...props }, ref) => {
337
+ const variant = React__namespace.useContext(DropdownMenuVariantContext);
338
+ const separatorClassName = cn.cn("my-1 h-px bg-neutral-alphas-200", className);
339
+ if (variant === "sheet") {
340
+ return /* @__PURE__ */ jsxRuntime.jsx("hr", { ref, className: separatorClassName, ...props });
254
341
  }
255
- ));
342
+ return /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuPrimitive__namespace.Separator, { ref, className: separatorClassName, ...props });
343
+ });
256
344
  DropdownMenuSeparator.displayName = "DropdownMenuSeparator";
257
345
  const DropdownMenuHeader = React__namespace.forwardRef(
258
346
  ({
@@ -358,8 +446,11 @@ const DropdownMenuRadioItem = React__namespace.forwardRef(({ className, children
358
446
  "group flex w-full cursor-pointer items-start gap-3 rounded-xs px-4 py-2 outline-none",
359
447
  "data-[highlighted]:bg-neutral-alphas-50",
360
448
  "data-[disabled]:cursor-not-allowed data-[disabled]:text-content-disabled",
361
- "data-[state=checked]:bg-buttons-primary-default data-[state=checked]:text-content-primary-inverted",
362
- "data-[state=checked]:data-[highlighted]:bg-buttons-primary-default",
449
+ // See DropdownMenuItem above: bg-interaction-hover aliases to the same
450
+ // token as the plain hover background, so it can't distinguish the
451
+ // checked state from an unchecked-but-hovered row.
452
+ "data-[state=checked]:bg-neutral-alphas-100",
453
+ "data-[state=checked]:data-[highlighted]:bg-neutral-alphas-200",
363
454
  className
364
455
  ),
365
456
  ...props,
@@ -369,11 +460,10 @@ const DropdownMenuRadioItem = React__namespace.forwardRef(({ className, children
369
460
  {
370
461
  className: cn.cn(
371
462
  "mt-1 flex size-4 shrink-0 items-center justify-center rounded-full border border-icons-primary",
372
- "group-data-[disabled]:border-content-disabled",
373
- "group-data-[state=checked]:border-icons-primary-inverted"
463
+ "group-data-[disabled]:border-content-disabled"
374
464
  ),
375
465
  "aria-hidden": "true",
376
- children: /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuPrimitive__namespace.ItemIndicator, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-2 rounded-full bg-content-primary-inverted" }) })
466
+ children: /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuPrimitive__namespace.ItemIndicator, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-2 rounded-full bg-content-primary" }) })
377
467
  }
378
468
  ),
379
469
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
@@ -383,7 +473,6 @@ const DropdownMenuRadioItem = React__namespace.forwardRef(({ className, children
383
473
  {
384
474
  className: cn.cn(
385
475
  "typography-description-12px-regular text-content-secondary",
386
- "group-data-[state=checked]:text-content-primary-inverted",
387
476
  "group-data-[disabled]:text-content-disabled"
388
477
  ),
389
478
  children: helper