@tribepad/themis 1.3.0 → 1.3.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/CHANGELOG.md +15 -0
- package/dist/elements/AlertDialog/index.js +1 -1
- package/dist/elements/AlertDialog/index.js.map +1 -1
- package/dist/elements/AlertDialog/index.mjs +1 -1
- package/dist/elements/AlertDialog/index.mjs.map +1 -1
- package/dist/elements/Card/Card.types.d.ts +1 -1
- package/dist/elements/Card/Card.types.d.ts.map +1 -1
- package/dist/elements/Card/index.js +1 -1
- package/dist/elements/Card/index.js.map +1 -1
- package/dist/elements/Card/index.mjs +1 -1
- package/dist/elements/Card/index.mjs.map +1 -1
- package/dist/elements/FormLayout/index.js +1 -1
- package/dist/elements/FormLayout/index.js.map +1 -1
- package/dist/elements/FormLayout/index.mjs +1 -1
- package/dist/elements/FormLayout/index.mjs.map +1 -1
- package/dist/elements/Modal/index.js +1 -1
- package/dist/elements/Modal/index.js.map +1 -1
- package/dist/elements/Modal/index.mjs +1 -1
- package/dist/elements/Modal/index.mjs.map +1 -1
- package/dist/elements/PageTitle/index.js +1 -1
- package/dist/elements/PageTitle/index.js.map +1 -1
- package/dist/elements/PageTitle/index.mjs +1 -1
- package/dist/elements/PageTitle/index.mjs.map +1 -1
- package/dist/elements/PasswordField/index.css +1 -1
- package/dist/elements/PasswordField/index.css.map +1 -1
- package/dist/elements/PasswordField/index.js +1 -1
- package/dist/elements/PasswordField/index.js.map +1 -1
- package/dist/elements/PasswordField/index.mjs +1 -1
- package/dist/elements/PasswordField/index.mjs.map +1 -1
- package/dist/elements/SearchField/index.css +1 -1
- package/dist/elements/SearchField/index.css.map +1 -1
- package/dist/elements/SearchField/index.js +1 -1
- package/dist/elements/SearchField/index.js.map +1 -1
- package/dist/elements/SearchField/index.mjs +1 -1
- package/dist/elements/SearchField/index.mjs.map +1 -1
- package/dist/elements/Select/Select.styles.d.ts.map +1 -1
- package/dist/elements/Select/index.js +1 -1
- package/dist/elements/Select/index.js.map +1 -1
- package/dist/elements/Select/index.mjs +1 -1
- package/dist/elements/Select/index.mjs.map +1 -1
- package/dist/elements/TextField/TextField.styles.d.ts +6 -0
- package/dist/elements/TextField/TextField.styles.d.ts.map +1 -1
- package/dist/elements/TextField/index.css +1 -1
- package/dist/elements/TextField/index.css.map +1 -1
- package/dist/elements/TextField/index.js +1 -1
- package/dist/elements/TextField/index.js.map +1 -1
- package/dist/elements/TextField/index.mjs +1 -1
- package/dist/elements/TextField/index.mjs.map +1 -1
- package/dist/elements/index.css +1 -1
- package/dist/elements/index.css.map +1 -1
- package/dist/elements/index.js +1 -1
- package/dist/elements/index.js.map +1 -1
- package/dist/elements/index.mjs +1 -1
- package/dist/elements/index.mjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/elements/Card/Card.stories.tsx +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/styles/shared-variants.ts","../../../src/elements/Modal/Modal.styles.ts","../../../src/elements/Modal/Modal.tsx"],"names":["cn","inputs","twMerge","clsx","MODAL_ANIMATION_IN","MODAL_ANIMATION_OUT","MODAL_SLIDE_IN","MODAL_SLIDE_OUT","MODAL_OVERLAY_IN","MODAL_OVERLAY_OUT","REDUCED_MOTION","modalContentVariants","cva","modalOverlayVariants","ModalContext","createContext","ModalRoot","children","defaultOpen","isOpen","onOpenChange","role","childArray","Children","child","isValidElement","ModalContent","triggerChild","ModalTrigger","unwrappedContent","jsx","AriaDialogTrigger","triggerElement","unwrappedTrigger","jsxs","size","animation","animationDuration","isDismissable","isKeyboardDismissDisabled","showClose","className","useContext","overlayClasses","modalClasses","mergedModalClasses","AriaModalOverlay","AriaModal","AriaDialog","close","Fragment","AriaButton","X","ModalOverlay","_props","ModalHeader","ModalTitle","as","Heading","ModalDescription","ModalFooter","ModalClose","state","OverlayTriggerStateContext","childElement","handlePress","existingOnPress","cloneElement","Modal"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Class Name Utility\n * Merges Tailwind CSS classes with conflict resolution\n *\n * Combines clsx for conditional classes and tailwind-merge for deduplication\n *\n * @example\n * cn('px-2 py-1', 'px-4') // => 'py-1 px-4' (px-4 overrides px-2)\n * cn('text-red-500', condition && 'text-blue-500') // => conditional application\n */\n\nimport { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * Shared CVA Variant Utilities\n *\n * Common patterns extracted from component variants for consistency and reduced bundle size.\n * Use these constants in CVA definitions to ensure consistent styling across Themis.\n *\n * @see interaction-states.ts for interaction-specific styles (focus, hover, pressed)\n */\n\n// =============================================================================\n// Focus Ring Patterns\n// =============================================================================\n\n/**\n * Focus-within ring (for container elements with focusable children)\n * Use when the container should show focus when any child is focused\n */\nexport const FOCUS_WITHIN_RING = [\n 'focus-within:outline-none',\n 'focus-within:ring-2',\n 'focus-within:ring-[var(--ring)]',\n 'focus-within:ring-offset-2',\n] as const;\n\n/**\n * Focus-visible ring (for directly focusable elements)\n * Use for buttons, inputs, and other interactive elements\n */\nexport const FOCUS_VISIBLE_RING = [\n 'focus-visible:outline-none',\n 'focus-visible:ring-2',\n 'focus-visible:ring-[var(--ring)]',\n 'focus-visible:ring-offset-2',\n] as const;\n\n/**\n * Standard focus ring (for elements using :focus pseudo-class)\n * Prefer focus-visible when possible for better UX\n */\nexport const FOCUS_RING = [\n 'focus:outline-none',\n 'focus:ring-2',\n 'focus:ring-[var(--ring)]',\n] as const;\n\n/**\n * Focus with background change (for segments, cells, menu items)\n */\nexport const FOCUS_HIGHLIGHT = [\n 'focus:outline-none',\n 'focus:bg-[var(--accent)]',\n 'focus:text-[var(--accent-foreground)]',\n] as const;\n\n// =============================================================================\n// Disabled State Patterns\n// =============================================================================\n\n/**\n * Standard disabled state using disabled attribute\n */\nexport const DISABLED_STANDARD = [\n 'disabled:pointer-events-none',\n 'disabled:opacity-50',\n] as const;\n\n/**\n * Disabled state using data attribute (React Aria pattern)\n */\nexport const DISABLED_DATA_ATTR = [\n 'data-[disabled]:pointer-events-none',\n 'data-[disabled]:opacity-50',\n 'data-[disabled]:cursor-not-allowed',\n] as const;\n\n// =============================================================================\n// Size-Based Text Variants\n// =============================================================================\n\n/**\n * Small text size scale (xs -> sm -> base)\n */\nexport const TEXT_SIZE_SMALL_SCALE = {\n sm: 'text-xs',\n default: 'text-sm',\n lg: 'text-base',\n} as const;\n\n/**\n * Medium text size scale (sm -> base -> lg)\n */\nexport const TEXT_SIZE_MEDIUM_SCALE = {\n sm: 'text-sm',\n default: 'text-base',\n lg: 'text-lg',\n} as const;\n\n// =============================================================================\n// Touch Target Utilities\n// =============================================================================\n\n/**\n * WCAG 2.2 AAA minimum touch target (44x44px)\n */\nexport const TOUCH_TARGET_MIN = [\n 'min-h-[44px]',\n 'min-w-[44px]',\n] as const;\n\n/**\n * Common button/cell sizes with touch target compliance\n */\nexport const INTERACTIVE_SIZES = {\n sm: 'h-9 w-9', // 36px - desktop only, NOT AAA compliant\n default: 'h-11 w-11', // 44px - AAA compliant\n lg: 'h-14 w-14', // 56px - AAA compliant, enhanced\n} as const;\n\n/**\n * Height-only sizes for fields and inputs\n */\nexport const FIELD_HEIGHTS = {\n sm: 'h-9', // 36px\n default: 'h-10', // 40px\n lg: 'h-11', // 44px\n} as const;\n\n/**\n * Layer 2: Visual sizing for form field inputs (height + padding + font).\n * Use on the <input>, <select>, or trigger element itself.\n *\n * Components may append additional classes (e.g. gap-* for segmented fields, py-* for text inputs).\n */\nexport const FIELD_INPUT_SIZES = {\n sm: `${FIELD_HEIGHTS.sm} px-2 text-xs`,\n default: `${FIELD_HEIGHTS.default} px-3 text-sm`,\n lg: `${FIELD_HEIGHTS.lg} px-4 text-base`,\n} as const;\n\n/**\n * Layer 1: Invisible touch target wrapper for form fields.\n * Apply to a <label> (or wrapper div) around the input element.\n * The <label> natively routes clicks/taps to the contained input\n * without JavaScript — achieving a 44px touch target while the\n * input stays at its visual height (h-9/h-10/h-11).\n */\nexport const FIELD_TOUCH_TARGET = 'min-h-[44px] flex items-center' as const;\n\n// =============================================================================\n// Message/Feedback Patterns\n// =============================================================================\n\n/**\n * Error message styling\n */\nexport const ERROR_MESSAGE_BASE = [\n 'flex',\n 'items-center',\n 'gap-1.5',\n 'text-[var(--destructive-background)]',\n] as const;\n\n/**\n * Success message styling\n */\nexport const SUCCESS_MESSAGE_BASE = [\n 'flex',\n 'items-center',\n 'gap-1.5',\n 'text-[var(--success-background)]',\n] as const;\n\n/**\n * Description/helper text styling\n */\nexport const DESCRIPTION_BASE = [\n 'text-[var(--menu-muted)]',\n] as const;\n\n/**\n * Label base styling\n */\nexport const LABEL_BASE = [\n 'font-medium',\n 'text-[var(--content-foreground)]',\n] as const;\n\n/**\n * Required indicator pattern\n */\nexport const REQUIRED_INDICATOR = \"after:content-['*'] after:ml-0.5 after:text-[var(--destructive-background)]\";\n\n// =============================================================================\n// Animation Patterns\n// =============================================================================\n\n/**\n * Popover/dropdown entry animation\n */\nexport const POPOVER_ANIMATION_IN = [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:zoom-in-95',\n] as const;\n\n/**\n * Popover/dropdown exit animation\n */\nexport const POPOVER_ANIMATION_OUT = [\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:zoom-out-95',\n] as const;\n\n/**\n * Modal content enter animation (fade + zoom)\n * Uses React Aria data-[entering]/data-[exiting] attributes\n */\nexport const MODAL_ANIMATION_IN = [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:zoom-in-95',\n 'data-[entering]:duration-200',\n 'data-[entering]:ease-out',\n] as const;\n\n/**\n * Modal content exit animation (fade + zoom)\n * Uses React Aria data-[entering]/data-[exiting] attributes\n */\nexport const MODAL_ANIMATION_OUT = [\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:zoom-out-95',\n 'data-[exiting]:duration-150',\n 'data-[exiting]:ease-in',\n] as const;\n\n/**\n * Modal slide enter animation (fade + slide up)\n */\nexport const MODAL_SLIDE_IN = [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:slide-in-from-bottom-4',\n 'data-[entering]:duration-200',\n 'data-[entering]:ease-out',\n] as const;\n\n/**\n * Modal slide exit animation (fade + slide down)\n */\nexport const MODAL_SLIDE_OUT = [\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:slide-out-to-bottom-4',\n 'data-[exiting]:duration-150',\n 'data-[exiting]:ease-in',\n] as const;\n\n/**\n * Modal overlay enter/exit animation (fade only)\n */\nexport const MODAL_OVERLAY_IN = [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:duration-200',\n] as const;\n\nexport const MODAL_OVERLAY_OUT = [\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:duration-150',\n] as const;\n\n/**\n * Reduced motion support (WCAG 2.2)\n */\nexport const REDUCED_MOTION = [\n 'motion-reduce:transition-none',\n 'motion-reduce:animate-none',\n] as const;\n\n/**\n * Standard transition for colors\n */\nexport const TRANSITION_COLORS = [\n 'transition-colors',\n 'duration-200',\n] as const;\n\n/**\n * Fast transition for interactions\n */\nexport const TRANSITION_FAST = [\n 'transition-colors',\n 'duration-150',\n] as const;\n\n// =============================================================================\n// Hover State Patterns\n// =============================================================================\n\n/**\n * Accent background on hover (for interactive items)\n */\nexport const HOVER_ACCENT = [\n 'hover:bg-[var(--accent)]',\n 'hover:text-[var(--accent-foreground)]',\n] as const;\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Combines multiple style arrays into a flat array for CVA base styles\n */\nexport function combineStyles(...styles: (readonly string[] | string)[]): string[] {\n return styles.flatMap(s => Array.isArray(s) ? [...s] : [s]);\n}\n","import { cva, type VariantProps } from 'class-variance-authority';\nimport {\n MODAL_ANIMATION_IN,\n MODAL_ANIMATION_OUT,\n MODAL_SLIDE_IN,\n MODAL_SLIDE_OUT,\n MODAL_OVERLAY_IN,\n MODAL_OVERLAY_OUT,\n REDUCED_MOTION,\n} from '../../styles/shared-variants';\n\n/**\n * CVA Variants for Modal.Content\n *\n * Size and animation variant definitions for the modal content container.\n * Uses React Aria data-[entering]/data-[exiting] attributes for enter+exit animations.\n * Uses CSS custom properties for all colours to support theming.\n *\n * @see Modal.types.ts (ModalSize, ModalAnimation)\n */\nexport const modalContentVariants = cva(\n [\n // Base styles\n 'relative',\n 'bg-[var(--content-background)]',\n 'text-[var(--content-foreground)]',\n 'rounded-lg',\n 'shadow-lg',\n 'p-6',\n 'w-full',\n 'outline-none',\n // Responsive: full-width on mobile with padding, constrained on desktop\n 'mx-4',\n 'sm:mx-0',\n // Reduced motion support\n ...REDUCED_MOTION,\n ],\n {\n variants: {\n size: {\n sm: 'max-w-sm', // 300px\n md: 'max-w-md', // 425px\n lg: 'max-w-lg', // 600px\n xl: 'max-w-2xl', // 800px\n full: 'max-w-full min-h-screen rounded-none', // Full screen\n },\n animation: {\n 'fade-zoom': [\n ...MODAL_ANIMATION_IN,\n ...MODAL_ANIMATION_OUT,\n ],\n fade: [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:duration-200',\n 'data-[entering]:ease-out',\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:duration-150',\n 'data-[exiting]:ease-in',\n ],\n slide: [\n ...MODAL_SLIDE_IN,\n ...MODAL_SLIDE_OUT,\n ],\n none: '',\n },\n },\n defaultVariants: {\n size: 'md',\n animation: 'fade-zoom',\n },\n }\n);\n\n/**\n * CVA Variants for Modal Overlay\n *\n * Animation variant definitions for the modal backdrop overlay.\n * Uses React Aria data-[entering]/data-[exiting] attributes for enter+exit animations.\n * Uses CSS custom properties for all colours to support theming.\n */\nexport const modalOverlayVariants = cva(\n [\n // Base overlay styles\n 'fixed',\n 'inset-0',\n 'z-50',\n 'flex',\n 'items-center',\n 'justify-center',\n 'bg-black/50',\n 'backdrop-blur-sm',\n // Reduced motion support\n ...REDUCED_MOTION,\n ],\n {\n variants: {\n animation: {\n 'fade-zoom': [...MODAL_OVERLAY_IN, ...MODAL_OVERLAY_OUT],\n fade: [...MODAL_OVERLAY_IN, ...MODAL_OVERLAY_OUT],\n slide: [...MODAL_OVERLAY_IN, ...MODAL_OVERLAY_OUT],\n none: '',\n },\n },\n defaultVariants: {\n animation: 'fade-zoom',\n },\n }\n);\n\n/**\n * Type exports for variant props\n * Allows TypeScript inference of variant combinations\n */\nexport type ModalContentVariantProps = VariantProps<typeof modalContentVariants>;\nexport type ModalOverlayVariantProps = VariantProps<typeof modalOverlayVariants>;\n","'use client';\n\n/**\n * Modal Component - Implementation\n *\n * Accessible modal dialog component combining React Aria primitives with ShadCN styling.\n * Follows Themis library patterns with compound component structure.\n *\n * @see PRD.md (Full requirements)\n * @see Modal.types.ts (Zod schemas)\n * @see RESEARCH.md (React Aria patterns)\n */\n\nimport {\n Children,\n isValidElement,\n cloneElement,\n useContext,\n createContext,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport {\n DialogTrigger as AriaDialogTrigger,\n Modal as AriaModal,\n ModalOverlay as AriaModalOverlay,\n Dialog as AriaDialog,\n Heading,\n Button as AriaButton,\n OverlayTriggerStateContext,\n} from 'react-aria-components';\nimport { X } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { modalContentVariants, modalOverlayVariants } from './Modal.styles';\nimport type {\n ModalProps,\n ModalTriggerProps,\n ModalContentProps,\n ModalOverlayProps,\n ModalHeaderProps,\n ModalTitleProps,\n ModalDescriptionProps,\n ModalFooterProps,\n ModalCloseProps,\n} from './Modal.types';\n\n/**\n * Modal Context\n * Passes role prop from Modal root to Modal.Content\n */\ninterface ModalContextValue {\n role?: 'dialog' | 'alertdialog';\n}\n\nconst ModalContext = createContext<ModalContextValue | undefined>(undefined);\n\n/**\n * Modal Root Component\n *\n * Manages modal open/close state and validates required children structure.\n * Must contain one Modal.Content. Modal.Trigger is optional — when omitted,\n * use controlled mode (isOpen + onOpenChange) to manage the modal.\n *\n * Unwraps compound component children and passes them to React Aria's DialogTrigger.\n *\n * @see PRD.md FR-001 (Modal Root Requirements)\n * @see PRD.md FR-012 (Controlled Mode)\n * @see PRD.md FR-013 (Uncontrolled Mode)\n */\nfunction ModalRoot({ children, defaultOpen, isOpen, onOpenChange, role = 'dialog' }: ModalProps): ReactElement {\n const childArray = Children.toArray(children);\n\n // Validate children structure in development only\n if (process.env.NODE_ENV !== 'production') {\n const hasContent = childArray.some(\n (child) =>\n isValidElement(child) &&\n (child.type === ModalContent ||\n (child.type as { displayName?: string }).displayName === 'ModalContent')\n );\n\n if (!hasContent) {\n throw new Error(\n 'Modal requires a Modal.Content as a child'\n );\n }\n }\n\n // Find trigger and content children\n const triggerChild = childArray.find(\n (child) =>\n isValidElement(child) &&\n (child.type === ModalTrigger ||\n (child.type as { displayName?: string }).displayName === 'ModalTrigger')\n );\n\n const contentChild = childArray.find(\n (child) =>\n isValidElement(child) &&\n (child.type === ModalContent ||\n (child.type as { displayName?: string }).displayName === 'ModalContent')\n );\n\n const unwrappedContent = contentChild as ReactElement;\n\n // When no trigger is provided, render content directly (controlled mode)\n if (!triggerChild) {\n return (\n <ModalContext.Provider value={{ role }}>\n <AriaDialogTrigger\n defaultOpen={defaultOpen}\n isOpen={isOpen}\n onOpenChange={onOpenChange}\n >\n {unwrappedContent}\n </AriaDialogTrigger>\n </ModalContext.Provider>\n );\n }\n\n // Extract the actual children from Modal.Trigger\n // React Aria's DialogTrigger expects direct Button and Modal children\n const triggerElement = triggerChild as ReactElement;\n const unwrappedTrigger = isValidElement(triggerElement)\n ? ((triggerElement.props as unknown as { children?: ReactNode }).children as ReactNode)\n : null;\n\n return (\n <ModalContext.Provider value={{ role }}>\n <AriaDialogTrigger\n defaultOpen={defaultOpen}\n isOpen={isOpen}\n onOpenChange={onOpenChange}\n >\n {unwrappedTrigger}\n {unwrappedContent}\n </AriaDialogTrigger>\n </ModalContext.Provider>\n );\n}\n\nModalRoot.displayName = 'Modal';\n\n/**\n * Modal.Trigger Component\n *\n * Wraps a single child element (typically Button) with modal trigger behavior.\n * Handles click events to open modal and manages ARIA attributes.\n *\n * React Aria's DialogTrigger (used in Modal root) automatically applies:\n * - aria-haspopup=\"dialog\"\n * - aria-expanded (true/false based on state)\n * - onClick handler to toggle modal\n *\n * @see PRD.md FR-002 (Trigger Requirements)\n * @see RESEARCH.md Section 2 (React Aria Integration)\n */\nfunction ModalTrigger({ children }: ModalTriggerProps): ReactElement {\n // React Aria's DialogTrigger (in Modal root) automatically adds ARIA attributes\n // to the first child component. We pass through the child element directly.\n return children as ReactElement;\n}\n\nModalTrigger.displayName = 'ModalTrigger';\n\n/**\n * Modal.Content Component\n *\n * Renders the modal content with overlay backdrop.\n * Uses React Aria's Modal and ModalOverlay for accessibility.\n * Applies CVA variants for size and animation.\n *\n * @see PRD.md FR-003 (Content Requirements)\n * @see PRD.md TR-001 (CVA Variant Styling)\n * @see RESEARCH.md Section 2 (React Aria Integration)\n */\nfunction ModalContent({\n children,\n size = 'md',\n animation = 'fade-zoom',\n animationDuration = 200,\n isDismissable = true,\n isKeyboardDismissDisabled = false,\n showClose = true,\n className,\n}: ModalContentProps): ReactElement {\n // Get role from context\n const context = useContext(ModalContext);\n const role = context?.role ?? 'dialog';\n\n // Generate overlay classes with animation variant\n const overlayClasses = modalOverlayVariants({ animation });\n\n // Generate modal classes with size and animation variants, merged with custom className\n const modalClasses = modalContentVariants({ size, animation });\n const mergedModalClasses = cn(modalClasses, className);\n\n return (\n <AriaModalOverlay\n isDismissable={isDismissable}\n isKeyboardDismissDisabled={isKeyboardDismissDisabled}\n className={overlayClasses}\n >\n <AriaModal\n className={mergedModalClasses}\n style={{\n transitionDuration: `${animationDuration}ms`,\n }}\n >\n <AriaDialog role={role} className=\"outline-none\">\n {({ close }) => (\n <>\n {showClose && (\n <AriaButton\n onPress={close}\n className=\"absolute right-0 top-0 flex items-center justify-center rounded-sm opacity-70 ring-offset-[var(--content-background)] transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-[var(--ring)] focus:ring-offset-2 disabled:pointer-events-none min-h-[44px] min-w-[44px]\"\n aria-label=\"Close modal\"\n >\n <X className=\"h-4 w-4\" aria-hidden=\"true\" />\n </AriaButton>\n )}\n {children}\n </>\n )}\n </AriaDialog>\n </AriaModal>\n </AriaModalOverlay>\n );\n}\n\nModalContent.displayName = 'ModalContent';\n\n/**\n * Modal.Overlay Component (Placeholder - not exposed in public API)\n * Overlay is managed internally by Modal.Content via AriaModalOverlay\n */\nfunction ModalOverlay(_props: ModalOverlayProps): null {\n return null;\n}\n\nModalOverlay.displayName = 'ModalOverlay';\n\n/**\n * Modal.Header Component\n *\n * Container for modal header content (typically Title and Description).\n * Applies consistent spacing and layout.\n *\n * @see PRD.md FR-005 (Header Requirements)\n */\nfunction ModalHeader({ children, className }: ModalHeaderProps): ReactElement {\n return <div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)}>{children}</div>;\n}\n\nModalHeader.displayName = 'ModalHeader';\n\n/**\n * Modal.Title Component\n *\n * Renders modal title using React Aria's Heading component.\n * Automatically links to modal via aria-labelledby.\n *\n * @see PRD.md FR-006 (Title Requirements)\n * @see PRD.md AR-002 (ARIA Attributes - auto-labelledby)\n */\nfunction ModalTitle({ children, as = 'h2', className }: ModalTitleProps): ReactElement {\n return (\n <Heading slot=\"title\" level={parseInt(as[1] ?? '2')} className={cn('text-lg font-semibold leading-none tracking-tight', className)}>\n {children}\n </Heading>\n );\n}\n\nModalTitle.displayName = 'ModalTitle';\n\n/**\n * Modal.Description Component\n *\n * Renders description text that automatically links to modal via aria-describedby.\n * Uses muted text color for visual hierarchy.\n *\n * @see PRD.md FR-007 (Description Requirements)\n * @see PRD.md AR-002 (ARIA Attributes - auto-describedby)\n */\nfunction ModalDescription({ children, className }: ModalDescriptionProps): ReactElement {\n return (\n <p slot=\"description\" className={cn('text-sm text-[var(--menu-muted)]', className)}>\n {children}\n </p>\n );\n}\n\nModalDescription.displayName = 'ModalDescription';\n\n/**\n * Modal.Footer Component\n *\n * Container for modal action buttons (Cancel, Confirm, etc.).\n * Aligns buttons to the right with consistent spacing.\n * Responsive: stacks vertically on mobile, horizontal on desktop.\n *\n * @see PRD.md FR-008 (Footer Requirements)\n * @see PRD.md DS-003 (Spacing - footer button gap)\n */\nfunction ModalFooter({ children, className }: ModalFooterProps): ReactElement {\n return <div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end gap-2', className)}>{children}</div>;\n}\n\nModalFooter.displayName = 'ModalFooter';\n\n/**\n * Modal.Close Component\n *\n * Wraps a child element (typically Button) with modal close behavior.\n * Uses OverlayTriggerStateContext from React Aria to access close function.\n *\n * @see PRD.md FR-009 (Close Requirements)\n */\nfunction ModalClose({ children }: ModalCloseProps): ReactElement {\n // Access the overlay trigger state from React Aria context\n const state = useContext(OverlayTriggerStateContext);\n\n if (!state) {\n throw new Error('Modal.Close must be used inside Modal.Content');\n }\n\n // Clone the child element and add/merge the onPress handler to close the modal\n if (!isValidElement(children)) {\n throw new Error('Modal.Close requires a valid React element as a child');\n }\n\n // Cast to ReactElement after validation\n const childElement = children as ReactElement;\n\n const handlePress = (): void => {\n state.close();\n };\n\n // Merge with existing onPress if present\n const existingOnPress = (childElement.props as unknown as { onPress?: () => void })?.onPress;\n const mergedOnPress = existingOnPress\n ? (): void => {\n existingOnPress();\n handlePress();\n }\n : handlePress;\n\n // cloneElement with onPress override - use unknown for flexible typing\n return cloneElement(childElement, { onPress: mergedOnPress } as unknown as Partial<typeof childElement.props>);\n}\n\nModalClose.displayName = 'ModalClose';\n\n/**\n * Re-export CVA variants from Modal.styles.ts for backwards compatibility.\n * Consumers importing { modalContentVariants, modalOverlayVariants } from './Modal'\n * will continue to work.\n */\nexport { modalContentVariants, modalOverlayVariants } from './Modal.styles';\n\n/**\n * Compound Component Export\n *\n * Follows Themis library pattern using Object.assign() for compound components.\n * Enables usage like Modal.Trigger, Modal.Content, etc.\n *\n * @deprecated The Object.assign compound pattern will be removed in v2.\n * Use the direct named exports (ModalTrigger, ModalContent, etc.) instead.\n * This pattern is kept for backwards compatibility only.\n *\n * @see RESEARCH.md Section 1 (Compound Component Pattern)\n */\nexport const Modal = Object.assign(ModalRoot, {\n Trigger: ModalTrigger,\n Content: ModalContent,\n Overlay: ModalOverlay,\n Header: ModalHeader,\n Title: ModalTitle,\n Description: ModalDescription,\n Footer: ModalFooter,\n Close: ModalClose,\n});\n\n// Named exports for individual components\nexport {\n ModalRoot,\n ModalTrigger,\n ModalContent,\n ModalOverlay,\n ModalHeader,\n ModalTitle,\n ModalDescription,\n ModalFooter,\n ModalClose,\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/styles/shared-variants.ts","../../../src/elements/Modal/Modal.styles.ts","../../../src/elements/Modal/Modal.tsx"],"names":["cn","inputs","twMerge","clsx","MODAL_ANIMATION_IN","MODAL_ANIMATION_OUT","MODAL_SLIDE_IN","MODAL_SLIDE_OUT","MODAL_OVERLAY_IN","MODAL_OVERLAY_OUT","REDUCED_MOTION","modalContentVariants","cva","modalOverlayVariants","ModalContext","createContext","ModalRoot","children","defaultOpen","isOpen","onOpenChange","role","childArray","Children","child","isValidElement","ModalContent","triggerChild","ModalTrigger","unwrappedContent","jsx","AriaDialogTrigger","triggerElement","unwrappedTrigger","jsxs","size","animation","animationDuration","isDismissable","isKeyboardDismissDisabled","showClose","className","useContext","overlayClasses","modalClasses","mergedModalClasses","AriaModalOverlay","AriaModal","AriaDialog","close","Fragment","AriaButton","X","ModalOverlay","_props","ModalHeader","ModalTitle","as","Heading","ModalDescription","ModalFooter","ModalClose","state","OverlayTriggerStateContext","childElement","handlePress","existingOnPress","cloneElement","Modal"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Class Name Utility\n * Merges Tailwind CSS classes with conflict resolution\n *\n * Combines clsx for conditional classes and tailwind-merge for deduplication\n *\n * @example\n * cn('px-2 py-1', 'px-4') // => 'py-1 px-4' (px-4 overrides px-2)\n * cn('text-red-500', condition && 'text-blue-500') // => conditional application\n */\n\nimport { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * Shared CVA Variant Utilities\n *\n * Common patterns extracted from component variants for consistency and reduced bundle size.\n * Use these constants in CVA definitions to ensure consistent styling across Themis.\n *\n * @see interaction-states.ts for interaction-specific styles (focus, hover, pressed)\n */\n\n// =============================================================================\n// Focus Ring Patterns\n// =============================================================================\n\n/**\n * Focus-within ring (for container elements with focusable children)\n * Use when the container should show focus when any child is focused\n */\nexport const FOCUS_WITHIN_RING = [\n 'focus-within:outline-none',\n 'focus-within:ring-2',\n 'focus-within:ring-[var(--ring)]',\n 'focus-within:ring-offset-2',\n] as const;\n\n/**\n * Focus-visible ring (for directly focusable elements)\n * Use for buttons, inputs, and other interactive elements\n */\nexport const FOCUS_VISIBLE_RING = [\n 'focus-visible:outline-none',\n 'focus-visible:ring-2',\n 'focus-visible:ring-[var(--ring)]',\n 'focus-visible:ring-offset-2',\n] as const;\n\n/**\n * Standard focus ring (for elements using :focus pseudo-class)\n * Prefer focus-visible when possible for better UX\n */\nexport const FOCUS_RING = [\n 'focus:outline-none',\n 'focus:ring-2',\n 'focus:ring-[var(--ring)]',\n] as const;\n\n/**\n * Focus with background change (for segments, cells, menu items)\n */\nexport const FOCUS_HIGHLIGHT = [\n 'focus:outline-none',\n 'focus:bg-[var(--accent)]',\n 'focus:text-[var(--accent-foreground)]',\n] as const;\n\n// =============================================================================\n// Disabled State Patterns\n// =============================================================================\n\n/**\n * Standard disabled state using disabled attribute\n */\nexport const DISABLED_STANDARD = [\n 'disabled:pointer-events-none',\n 'disabled:opacity-50',\n] as const;\n\n/**\n * Disabled state using data attribute (React Aria pattern)\n */\nexport const DISABLED_DATA_ATTR = [\n 'data-[disabled]:pointer-events-none',\n 'data-[disabled]:opacity-50',\n 'data-[disabled]:cursor-not-allowed',\n] as const;\n\n// =============================================================================\n// Size-Based Text Variants\n// =============================================================================\n\n/**\n * Small text size scale (xs -> sm -> base)\n */\nexport const TEXT_SIZE_SMALL_SCALE = {\n sm: 'text-xs',\n default: 'text-sm',\n lg: 'text-base',\n} as const;\n\n/**\n * Medium text size scale (sm -> base -> lg)\n */\nexport const TEXT_SIZE_MEDIUM_SCALE = {\n sm: 'text-sm',\n default: 'text-base',\n lg: 'text-lg',\n} as const;\n\n// =============================================================================\n// Touch Target Utilities\n// =============================================================================\n\n/**\n * WCAG 2.2 AAA minimum touch target (44x44px)\n */\nexport const TOUCH_TARGET_MIN = [\n 'min-h-[44px]',\n 'min-w-[44px]',\n] as const;\n\n/**\n * Common button/cell sizes with touch target compliance\n */\nexport const INTERACTIVE_SIZES = {\n sm: 'h-9 w-9', // 36px - desktop only, NOT AAA compliant\n default: 'h-11 w-11', // 44px - AAA compliant\n lg: 'h-14 w-14', // 56px - AAA compliant, enhanced\n} as const;\n\n/**\n * Height-only sizes for fields and inputs\n */\nexport const FIELD_HEIGHTS = {\n sm: 'h-9', // 36px\n default: 'h-10', // 40px\n lg: 'h-11', // 44px\n} as const;\n\n/**\n * Layer 2: Visual sizing for form field inputs (height + padding + font).\n * Use on the <input>, <select>, or trigger element itself.\n *\n * Components may append additional classes (e.g. gap-* for segmented fields, py-* for text inputs).\n */\nexport const FIELD_INPUT_SIZES = {\n sm: `${FIELD_HEIGHTS.sm} px-2 text-xs`,\n default: `${FIELD_HEIGHTS.default} px-3 text-sm`,\n lg: `${FIELD_HEIGHTS.lg} px-4 text-base`,\n} as const;\n\n/**\n * Layer 1: Invisible touch target wrapper for form fields.\n * Apply to a <label> (or wrapper div) around the input element.\n * The <label> natively routes clicks/taps to the contained input\n * without JavaScript — achieving a 44px touch target while the\n * input stays at its visual height (h-9/h-10/h-11).\n */\nexport const FIELD_TOUCH_TARGET = 'min-h-[44px] flex items-center' as const;\n\n// =============================================================================\n// Message/Feedback Patterns\n// =============================================================================\n\n/**\n * Error message styling\n */\nexport const ERROR_MESSAGE_BASE = [\n 'flex',\n 'items-center',\n 'gap-1.5',\n 'text-[var(--destructive-background)]',\n] as const;\n\n/**\n * Success message styling\n */\nexport const SUCCESS_MESSAGE_BASE = [\n 'flex',\n 'items-center',\n 'gap-1.5',\n 'text-[var(--success-background)]',\n] as const;\n\n/**\n * Description/helper text styling\n */\nexport const DESCRIPTION_BASE = [\n 'text-[var(--menu-muted)]',\n] as const;\n\n/**\n * Label base styling\n */\nexport const LABEL_BASE = [\n 'font-medium',\n 'text-[var(--content-foreground)]',\n] as const;\n\n/**\n * Required indicator pattern\n */\nexport const REQUIRED_INDICATOR = \"after:content-['*'] after:ml-0.5 after:text-[var(--destructive-background)]\";\n\n// =============================================================================\n// Animation Patterns\n// =============================================================================\n\n/**\n * Popover/dropdown entry animation\n */\nexport const POPOVER_ANIMATION_IN = [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:zoom-in-95',\n] as const;\n\n/**\n * Popover/dropdown exit animation\n */\nexport const POPOVER_ANIMATION_OUT = [\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:zoom-out-95',\n] as const;\n\n/**\n * Modal content enter animation (fade + zoom)\n * Uses React Aria data-[entering]/data-[exiting] attributes\n */\nexport const MODAL_ANIMATION_IN = [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:zoom-in-95',\n 'data-[entering]:duration-200',\n 'data-[entering]:ease-out',\n] as const;\n\n/**\n * Modal content exit animation (fade + zoom)\n * Uses React Aria data-[entering]/data-[exiting] attributes\n */\nexport const MODAL_ANIMATION_OUT = [\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:zoom-out-95',\n 'data-[exiting]:duration-150',\n 'data-[exiting]:ease-in',\n] as const;\n\n/**\n * Modal slide enter animation (fade + slide up)\n */\nexport const MODAL_SLIDE_IN = [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:slide-in-from-bottom-4',\n 'data-[entering]:duration-200',\n 'data-[entering]:ease-out',\n] as const;\n\n/**\n * Modal slide exit animation (fade + slide down)\n */\nexport const MODAL_SLIDE_OUT = [\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:slide-out-to-bottom-4',\n 'data-[exiting]:duration-150',\n 'data-[exiting]:ease-in',\n] as const;\n\n/**\n * Modal overlay enter/exit animation (fade only)\n */\nexport const MODAL_OVERLAY_IN = [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:duration-200',\n] as const;\n\nexport const MODAL_OVERLAY_OUT = [\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:duration-150',\n] as const;\n\n/**\n * Reduced motion support (WCAG 2.2)\n */\nexport const REDUCED_MOTION = [\n 'motion-reduce:transition-none',\n 'motion-reduce:animate-none',\n] as const;\n\n/**\n * Standard transition for colors\n */\nexport const TRANSITION_COLORS = [\n 'transition-colors',\n 'duration-200',\n] as const;\n\n/**\n * Fast transition for interactions\n */\nexport const TRANSITION_FAST = [\n 'transition-colors',\n 'duration-150',\n] as const;\n\n// =============================================================================\n// Hover State Patterns\n// =============================================================================\n\n/**\n * Accent background on hover (for interactive items)\n */\nexport const HOVER_ACCENT = [\n 'hover:bg-[var(--accent)]',\n 'hover:text-[var(--accent-foreground)]',\n] as const;\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Combines multiple style arrays into a flat array for CVA base styles\n */\nexport function combineStyles(...styles: (readonly string[] | string)[]): string[] {\n return styles.flatMap(s => Array.isArray(s) ? [...s] : [s]);\n}\n","import { cva, type VariantProps } from 'class-variance-authority';\nimport {\n MODAL_ANIMATION_IN,\n MODAL_ANIMATION_OUT,\n MODAL_SLIDE_IN,\n MODAL_SLIDE_OUT,\n MODAL_OVERLAY_IN,\n MODAL_OVERLAY_OUT,\n REDUCED_MOTION,\n} from '../../styles/shared-variants';\n\n/**\n * CVA Variants for Modal.Content\n *\n * Size and animation variant definitions for the modal content container.\n * Uses React Aria data-[entering]/data-[exiting] attributes for enter+exit animations.\n * Uses CSS custom properties for all colours to support theming.\n *\n * @see Modal.types.ts (ModalSize, ModalAnimation)\n */\nexport const modalContentVariants = cva(\n [\n // Base styles\n 'relative',\n 'bg-[var(--content-background)]',\n 'text-[var(--content-foreground)]',\n 'rounded-lg',\n 'shadow-lg',\n 'p-6',\n 'w-full',\n 'outline-none',\n // Responsive: full-width on mobile with padding, constrained on desktop\n 'mx-4',\n 'sm:mx-0',\n // Reduced motion support\n ...REDUCED_MOTION,\n ],\n {\n variants: {\n size: {\n sm: 'max-w-sm', // 300px\n md: 'max-w-md', // 425px\n lg: 'max-w-lg', // 600px\n xl: 'max-w-2xl', // 800px\n full: 'max-w-full min-h-screen rounded-none', // Full screen\n },\n animation: {\n 'fade-zoom': [\n ...MODAL_ANIMATION_IN,\n ...MODAL_ANIMATION_OUT,\n ],\n fade: [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:duration-200',\n 'data-[entering]:ease-out',\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:duration-150',\n 'data-[exiting]:ease-in',\n ],\n slide: [\n ...MODAL_SLIDE_IN,\n ...MODAL_SLIDE_OUT,\n ],\n none: '',\n },\n },\n defaultVariants: {\n size: 'md',\n animation: 'fade-zoom',\n },\n }\n);\n\n/**\n * CVA Variants for Modal Overlay\n *\n * Animation variant definitions for the modal backdrop overlay.\n * Uses React Aria data-[entering]/data-[exiting] attributes for enter+exit animations.\n * Uses CSS custom properties for all colours to support theming.\n */\nexport const modalOverlayVariants = cva(\n [\n // Base overlay styles\n 'fixed',\n 'inset-0',\n 'z-50',\n 'flex',\n 'items-center',\n 'justify-center',\n 'bg-black/50',\n 'backdrop-blur-sm',\n // Reduced motion support\n ...REDUCED_MOTION,\n ],\n {\n variants: {\n animation: {\n 'fade-zoom': [...MODAL_OVERLAY_IN, ...MODAL_OVERLAY_OUT],\n fade: [...MODAL_OVERLAY_IN, ...MODAL_OVERLAY_OUT],\n slide: [...MODAL_OVERLAY_IN, ...MODAL_OVERLAY_OUT],\n none: '',\n },\n },\n defaultVariants: {\n animation: 'fade-zoom',\n },\n }\n);\n\n/**\n * Type exports for variant props\n * Allows TypeScript inference of variant combinations\n */\nexport type ModalContentVariantProps = VariantProps<typeof modalContentVariants>;\nexport type ModalOverlayVariantProps = VariantProps<typeof modalOverlayVariants>;\n","'use client';\n\n/**\n * Modal Component - Implementation\n *\n * Accessible modal dialog component combining React Aria primitives with ShadCN styling.\n * Follows Themis library patterns with compound component structure.\n *\n * @see PRD.md (Full requirements)\n * @see Modal.types.ts (Zod schemas)\n * @see RESEARCH.md (React Aria patterns)\n */\n\nimport {\n Children,\n isValidElement,\n cloneElement,\n useContext,\n createContext,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport {\n DialogTrigger as AriaDialogTrigger,\n Modal as AriaModal,\n ModalOverlay as AriaModalOverlay,\n Dialog as AriaDialog,\n Heading,\n Button as AriaButton,\n OverlayTriggerStateContext,\n} from 'react-aria-components';\nimport { X } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { modalContentVariants, modalOverlayVariants } from './Modal.styles';\nimport type {\n ModalProps,\n ModalTriggerProps,\n ModalContentProps,\n ModalOverlayProps,\n ModalHeaderProps,\n ModalTitleProps,\n ModalDescriptionProps,\n ModalFooterProps,\n ModalCloseProps,\n} from './Modal.types';\n\n/**\n * Modal Context\n * Passes role prop from Modal root to Modal.Content\n */\ninterface ModalContextValue {\n role?: 'dialog' | 'alertdialog';\n}\n\nconst ModalContext = createContext<ModalContextValue | undefined>(undefined);\n\n/**\n * Modal Root Component\n *\n * Manages modal open/close state and validates required children structure.\n * Must contain one Modal.Content. Modal.Trigger is optional — when omitted,\n * use controlled mode (isOpen + onOpenChange) to manage the modal.\n *\n * Unwraps compound component children and passes them to React Aria's DialogTrigger.\n *\n * @see PRD.md FR-001 (Modal Root Requirements)\n * @see PRD.md FR-012 (Controlled Mode)\n * @see PRD.md FR-013 (Uncontrolled Mode)\n */\nfunction ModalRoot({ children, defaultOpen, isOpen, onOpenChange, role = 'dialog' }: ModalProps): ReactElement {\n const childArray = Children.toArray(children);\n\n // Validate children structure in development only\n if (process.env.NODE_ENV !== 'production') {\n const hasContent = childArray.some(\n (child) =>\n isValidElement(child) &&\n (child.type === ModalContent ||\n (child.type as { displayName?: string }).displayName === 'ModalContent')\n );\n\n if (!hasContent) {\n throw new Error(\n 'Modal requires a Modal.Content as a child'\n );\n }\n }\n\n // Find trigger and content children\n const triggerChild = childArray.find(\n (child) =>\n isValidElement(child) &&\n (child.type === ModalTrigger ||\n (child.type as { displayName?: string }).displayName === 'ModalTrigger')\n );\n\n const contentChild = childArray.find(\n (child) =>\n isValidElement(child) &&\n (child.type === ModalContent ||\n (child.type as { displayName?: string }).displayName === 'ModalContent')\n );\n\n const unwrappedContent = contentChild as ReactElement;\n\n // When no trigger is provided, render content directly (controlled mode)\n if (!triggerChild) {\n return (\n <ModalContext.Provider value={{ role }}>\n <AriaDialogTrigger\n defaultOpen={defaultOpen}\n isOpen={isOpen}\n onOpenChange={onOpenChange}\n >\n {unwrappedContent}\n </AriaDialogTrigger>\n </ModalContext.Provider>\n );\n }\n\n // Extract the actual children from Modal.Trigger\n // React Aria's DialogTrigger expects direct Button and Modal children\n const triggerElement = triggerChild as ReactElement;\n const unwrappedTrigger = isValidElement(triggerElement)\n ? ((triggerElement.props as unknown as { children?: ReactNode }).children as ReactNode)\n : null;\n\n return (\n <ModalContext.Provider value={{ role }}>\n <AriaDialogTrigger\n defaultOpen={defaultOpen}\n isOpen={isOpen}\n onOpenChange={onOpenChange}\n >\n {unwrappedTrigger}\n {unwrappedContent}\n </AriaDialogTrigger>\n </ModalContext.Provider>\n );\n}\n\nModalRoot.displayName = 'Modal';\n\n/**\n * Modal.Trigger Component\n *\n * Wraps a single child element (typically Button) with modal trigger behavior.\n * Handles click events to open modal and manages ARIA attributes.\n *\n * React Aria's DialogTrigger (used in Modal root) automatically applies:\n * - aria-haspopup=\"dialog\"\n * - aria-expanded (true/false based on state)\n * - onClick handler to toggle modal\n *\n * @see PRD.md FR-002 (Trigger Requirements)\n * @see RESEARCH.md Section 2 (React Aria Integration)\n */\nfunction ModalTrigger({ children }: ModalTriggerProps): ReactElement {\n // React Aria's DialogTrigger (in Modal root) automatically adds ARIA attributes\n // to the first child component. We pass through the child element directly.\n return children as ReactElement;\n}\n\nModalTrigger.displayName = 'ModalTrigger';\n\n/**\n * Modal.Content Component\n *\n * Renders the modal content with overlay backdrop.\n * Uses React Aria's Modal and ModalOverlay for accessibility.\n * Applies CVA variants for size and animation.\n *\n * @see PRD.md FR-003 (Content Requirements)\n * @see PRD.md TR-001 (CVA Variant Styling)\n * @see RESEARCH.md Section 2 (React Aria Integration)\n */\nfunction ModalContent({\n children,\n size = 'md',\n animation = 'fade-zoom',\n animationDuration = 250,\n isDismissable = true,\n isKeyboardDismissDisabled = false,\n showClose = true,\n className,\n}: ModalContentProps): ReactElement {\n // Get role from context\n const context = useContext(ModalContext);\n const role = context?.role ?? 'dialog';\n\n // Generate overlay classes with animation variant\n const overlayClasses = modalOverlayVariants({ animation });\n\n // Generate modal classes with size and animation variants, merged with custom className\n const modalClasses = modalContentVariants({ size, animation });\n const mergedModalClasses = cn(modalClasses, className);\n\n return (\n <AriaModalOverlay\n isDismissable={isDismissable}\n isKeyboardDismissDisabled={isKeyboardDismissDisabled}\n className={overlayClasses}\n >\n <AriaModal\n className={mergedModalClasses}\n style={{\n transitionDuration: `${animationDuration}ms`,\n }}\n >\n <AriaDialog role={role} className=\"outline-none\">\n {({ close }) => (\n <>\n {showClose && (\n <AriaButton\n onPress={close}\n className=\"absolute right-0 top-0 flex items-center justify-center rounded-sm opacity-70 ring-offset-[var(--content-background)] transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-[var(--ring)] focus:ring-offset-2 disabled:pointer-events-none min-h-[44px] min-w-[44px]\"\n aria-label=\"Close modal\"\n >\n <X className=\"h-4 w-4\" aria-hidden=\"true\" />\n </AriaButton>\n )}\n {children}\n </>\n )}\n </AriaDialog>\n </AriaModal>\n </AriaModalOverlay>\n );\n}\n\nModalContent.displayName = 'ModalContent';\n\n/**\n * Modal.Overlay Component (Placeholder - not exposed in public API)\n * Overlay is managed internally by Modal.Content via AriaModalOverlay\n */\nfunction ModalOverlay(_props: ModalOverlayProps): null {\n return null;\n}\n\nModalOverlay.displayName = 'ModalOverlay';\n\n/**\n * Modal.Header Component\n *\n * Container for modal header content (typically Title and Description).\n * Applies consistent spacing and layout.\n *\n * @see PRD.md FR-005 (Header Requirements)\n */\nfunction ModalHeader({ children, className }: ModalHeaderProps): ReactElement {\n return <div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)}>{children}</div>;\n}\n\nModalHeader.displayName = 'ModalHeader';\n\n/**\n * Modal.Title Component\n *\n * Renders modal title using React Aria's Heading component.\n * Automatically links to modal via aria-labelledby.\n *\n * @see PRD.md FR-006 (Title Requirements)\n * @see PRD.md AR-002 (ARIA Attributes - auto-labelledby)\n */\nfunction ModalTitle({ children, as = 'h2', className }: ModalTitleProps): ReactElement {\n return (\n <Heading slot=\"title\" level={parseInt(as[1] ?? '2')} className={cn('text-lg font-semibold leading-none tracking-tight', className)}>\n {children}\n </Heading>\n );\n}\n\nModalTitle.displayName = 'ModalTitle';\n\n/**\n * Modal.Description Component\n *\n * Renders description text that automatically links to modal via aria-describedby.\n * Uses muted text color for visual hierarchy.\n *\n * @see PRD.md FR-007 (Description Requirements)\n * @see PRD.md AR-002 (ARIA Attributes - auto-describedby)\n */\nfunction ModalDescription({ children, className }: ModalDescriptionProps): ReactElement {\n return (\n <p slot=\"description\" className={cn('text-sm text-[var(--menu-muted)]', className)}>\n {children}\n </p>\n );\n}\n\nModalDescription.displayName = 'ModalDescription';\n\n/**\n * Modal.Footer Component\n *\n * Container for modal action buttons (Cancel, Confirm, etc.).\n * Aligns buttons to the right with consistent spacing.\n * Responsive: stacks vertically on mobile, horizontal on desktop.\n *\n * @see PRD.md FR-008 (Footer Requirements)\n * @see PRD.md DS-003 (Spacing - footer button gap)\n */\nfunction ModalFooter({ children, className }: ModalFooterProps): ReactElement {\n return <div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end gap-2', className)}>{children}</div>;\n}\n\nModalFooter.displayName = 'ModalFooter';\n\n/**\n * Modal.Close Component\n *\n * Wraps a child element (typically Button) with modal close behavior.\n * Uses OverlayTriggerStateContext from React Aria to access close function.\n *\n * @see PRD.md FR-009 (Close Requirements)\n */\nfunction ModalClose({ children }: ModalCloseProps): ReactElement {\n // Access the overlay trigger state from React Aria context\n const state = useContext(OverlayTriggerStateContext);\n\n if (!state) {\n throw new Error('Modal.Close must be used inside Modal.Content');\n }\n\n // Clone the child element and add/merge the onPress handler to close the modal\n if (!isValidElement(children)) {\n throw new Error('Modal.Close requires a valid React element as a child');\n }\n\n // Cast to ReactElement after validation\n const childElement = children as ReactElement;\n\n const handlePress = (): void => {\n state.close();\n };\n\n // Merge with existing onPress if present\n const existingOnPress = (childElement.props as unknown as { onPress?: () => void })?.onPress;\n const mergedOnPress = existingOnPress\n ? (): void => {\n existingOnPress();\n handlePress();\n }\n : handlePress;\n\n // cloneElement with onPress override - use unknown for flexible typing\n return cloneElement(childElement, { onPress: mergedOnPress } as unknown as Partial<typeof childElement.props>);\n}\n\nModalClose.displayName = 'ModalClose';\n\n/**\n * Re-export CVA variants from Modal.styles.ts for backwards compatibility.\n * Consumers importing { modalContentVariants, modalOverlayVariants } from './Modal'\n * will continue to work.\n */\nexport { modalContentVariants, modalOverlayVariants } from './Modal.styles';\n\n/**\n * Compound Component Export\n *\n * Follows Themis library pattern using Object.assign() for compound components.\n * Enables usage like Modal.Trigger, Modal.Content, etc.\n *\n * @deprecated The Object.assign compound pattern will be removed in v2.\n * Use the direct named exports (ModalTrigger, ModalContent, etc.) instead.\n * This pattern is kept for backwards compatibility only.\n *\n * @see RESEARCH.md Section 1 (Compound Component Pattern)\n */\nexport const Modal = Object.assign(ModalRoot, {\n Trigger: ModalTrigger,\n Content: ModalContent,\n Overlay: ModalOverlay,\n Header: ModalHeader,\n Title: ModalTitle,\n Description: ModalDescription,\n Footer: ModalFooter,\n Close: ModalClose,\n});\n\n// Named exports for individual components\nexport {\n ModalRoot,\n ModalTrigger,\n ModalContent,\n ModalOverlay,\n ModalHeader,\n ModalTitle,\n ModalDescription,\n ModalFooter,\n ModalClose,\n};\n"]}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {createContext,Children,isValidElement,useContext,cloneElement}from'react';import {DialogTrigger,OverlayTriggerStateContext,Heading,ModalOverlay,Modal,Dialog,Button}from'react-aria-components';import {X}from'lucide-react';import {clsx}from'clsx';import {twMerge}from'tailwind-merge';import {cva}from'class-variance-authority';import {jsx,jsxs,Fragment}from'react/jsx-runtime';function l(...t){return twMerge(clsx(t))}var S=["data-[entering]:animate-in","data-[entering]:fade-in-0","data-[entering]:zoom-in-95","data-[entering]:duration-200","data-[entering]:ease-out"],b=["data-[exiting]:animate-out","data-[exiting]:fade-out-0","data-[exiting]:zoom-out-95","data-[exiting]:duration-150","data-[exiting]:ease-in"],h=["data-[entering]:animate-in","data-[entering]:fade-in-0","data-[entering]:slide-in-from-bottom-4","data-[entering]:duration-200","data-[entering]:ease-out"],L=["data-[exiting]:animate-out","data-[exiting]:fade-out-0","data-[exiting]:slide-out-to-bottom-4","data-[exiting]:duration-150","data-[exiting]:ease-in"],u=["data-[entering]:animate-in","data-[entering]:fade-in-0","data-[entering]:duration-200"],x=["data-[exiting]:animate-out","data-[exiting]:fade-out-0","data-[exiting]:duration-150"],A=["motion-reduce:transition-none","motion-reduce:animate-none"];var f=cva(["relative","bg-[var(--content-background)]","text-[var(--content-foreground)]","rounded-lg","shadow-lg","p-6","w-full","outline-none","mx-4","sm:mx-0",...A],{variants:{size:{sm:"max-w-sm",md:"max-w-md",lg:"max-w-lg",xl:"max-w-2xl",full:"max-w-full min-h-screen rounded-none"},animation:{"fade-zoom":[...S,...b],fade:["data-[entering]:animate-in","data-[entering]:fade-in-0","data-[entering]:duration-200","data-[entering]:ease-out","data-[exiting]:animate-out","data-[exiting]:fade-out-0","data-[exiting]:duration-150","data-[exiting]:ease-in"],slide:[...h,...L],none:""}},defaultVariants:{size:"md",animation:"fade-zoom"}}),M=cva(["fixed","inset-0","z-50","flex","items-center","justify-center","bg-black/50","backdrop-blur-sm",...A],{variants:{animation:{"fade-zoom":[...u,...x],fade:[...u,...x],slide:[...u,...x],none:""}},defaultVariants:{animation:"fade-zoom"}});var C=createContext(void 0);function H({children:t,defaultOpen:e,isOpen:o,onOpenChange:r,role:s="dialog"}){let i=Children.toArray(t);if(process.env.NODE_ENV!=="production"&&!i.some(d=>isValidElement(d)&&(d.type===p||d.type.displayName==="ModalContent")))throw new Error("Modal requires a Modal.Content as a child");let m=i.find(n=>isValidElement(n)&&(n.type===O||n.type.displayName==="ModalTrigger")),E=i.find(n=>isValidElement(n)&&(n.type===p||n.type.displayName==="ModalContent"));if(!m)return jsx(C.Provider,{value:{role:s},children:jsx(DialogTrigger,{defaultOpen:e,isOpen:o,onOpenChange:r,children:E})});let g=m,y=isValidElement(g)?g.props.children:null;return jsx(C.Provider,{value:{role:s},children:jsxs(DialogTrigger,{defaultOpen:e,isOpen:o,onOpenChange:r,children:[y,E]})})}H.displayName="Modal";function O({children:t}){return t}O.displayName="ModalTrigger";function p({children:t,size:e="md",animation:o="fade-zoom",animationDuration:r=
|
|
2
|
+
import {createContext,Children,isValidElement,useContext,cloneElement}from'react';import {DialogTrigger,OverlayTriggerStateContext,Heading,ModalOverlay,Modal,Dialog,Button}from'react-aria-components';import {X}from'lucide-react';import {clsx}from'clsx';import {twMerge}from'tailwind-merge';import {cva}from'class-variance-authority';import {jsx,jsxs,Fragment}from'react/jsx-runtime';function l(...t){return twMerge(clsx(t))}var S=["data-[entering]:animate-in","data-[entering]:fade-in-0","data-[entering]:zoom-in-95","data-[entering]:duration-200","data-[entering]:ease-out"],b=["data-[exiting]:animate-out","data-[exiting]:fade-out-0","data-[exiting]:zoom-out-95","data-[exiting]:duration-150","data-[exiting]:ease-in"],h=["data-[entering]:animate-in","data-[entering]:fade-in-0","data-[entering]:slide-in-from-bottom-4","data-[entering]:duration-200","data-[entering]:ease-out"],L=["data-[exiting]:animate-out","data-[exiting]:fade-out-0","data-[exiting]:slide-out-to-bottom-4","data-[exiting]:duration-150","data-[exiting]:ease-in"],u=["data-[entering]:animate-in","data-[entering]:fade-in-0","data-[entering]:duration-200"],x=["data-[exiting]:animate-out","data-[exiting]:fade-out-0","data-[exiting]:duration-150"],A=["motion-reduce:transition-none","motion-reduce:animate-none"];var f=cva(["relative","bg-[var(--content-background)]","text-[var(--content-foreground)]","rounded-lg","shadow-lg","p-6","w-full","outline-none","mx-4","sm:mx-0",...A],{variants:{size:{sm:"max-w-sm",md:"max-w-md",lg:"max-w-lg",xl:"max-w-2xl",full:"max-w-full min-h-screen rounded-none"},animation:{"fade-zoom":[...S,...b],fade:["data-[entering]:animate-in","data-[entering]:fade-in-0","data-[entering]:duration-200","data-[entering]:ease-out","data-[exiting]:animate-out","data-[exiting]:fade-out-0","data-[exiting]:duration-150","data-[exiting]:ease-in"],slide:[...h,...L],none:""}},defaultVariants:{size:"md",animation:"fade-zoom"}}),M=cva(["fixed","inset-0","z-50","flex","items-center","justify-center","bg-black/50","backdrop-blur-sm",...A],{variants:{animation:{"fade-zoom":[...u,...x],fade:[...u,...x],slide:[...u,...x],none:""}},defaultVariants:{animation:"fade-zoom"}});var C=createContext(void 0);function H({children:t,defaultOpen:e,isOpen:o,onOpenChange:r,role:s="dialog"}){let i=Children.toArray(t);if(process.env.NODE_ENV!=="production"&&!i.some(d=>isValidElement(d)&&(d.type===p||d.type.displayName==="ModalContent")))throw new Error("Modal requires a Modal.Content as a child");let m=i.find(n=>isValidElement(n)&&(n.type===O||n.type.displayName==="ModalTrigger")),E=i.find(n=>isValidElement(n)&&(n.type===p||n.type.displayName==="ModalContent"));if(!m)return jsx(C.Provider,{value:{role:s},children:jsx(DialogTrigger,{defaultOpen:e,isOpen:o,onOpenChange:r,children:E})});let g=m,y=isValidElement(g)?g.props.children:null;return jsx(C.Provider,{value:{role:s},children:jsxs(DialogTrigger,{defaultOpen:e,isOpen:o,onOpenChange:r,children:[y,E]})})}H.displayName="Modal";function O({children:t}){return t}O.displayName="ModalTrigger";function p({children:t,size:e="md",animation:o="fade-zoom",animationDuration:r=250,isDismissable:s=true,isKeyboardDismissDisabled:i=false,showClose:m=true,className:R}){let g=useContext(C)?.role??"dialog",y=M({animation:o}),n=f({size:e,animation:o}),d=l(n,R);return jsx(ModalOverlay,{isDismissable:s,isKeyboardDismissDisabled:i,className:y,children:jsx(Modal,{className:d,style:{transitionDuration:`${r}ms`},children:jsx(Dialog,{role:g,className:"outline-none",children:({close:z})=>jsxs(Fragment,{children:[m&&jsx(Button,{onPress:z,className:"absolute right-0 top-0 flex items-center justify-center rounded-sm opacity-70 ring-offset-[var(--content-background)] transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-[var(--ring)] focus:ring-offset-2 disabled:pointer-events-none min-h-[44px] min-w-[44px]","aria-label":"Close modal",children:jsx(X,{className:"h-4 w-4","aria-hidden":"true"})}),t]})})})})}p.displayName="ModalContent";function N(t){return null}N.displayName="ModalOverlay";function _({children:t,className:e}){return jsx("div",{className:l("flex flex-col space-y-1.5 text-center sm:text-left",e),children:t})}_.displayName="ModalHeader";function T({children:t,as:e="h2",className:o}){return jsx(Heading,{slot:"title",level:parseInt(e[1]??"2"),className:l("text-lg font-semibold leading-none tracking-tight",o),children:t})}T.displayName="ModalTitle";function I({children:t,className:e}){return jsx("p",{slot:"description",className:l("text-sm text-[var(--menu-muted)]",e),children:t})}I.displayName="ModalDescription";function D({children:t,className:e}){return jsx("div",{className:l("flex flex-col-reverse sm:flex-row sm:justify-end gap-2",e),children:t})}D.displayName="ModalFooter";function P({children:t}){let e=useContext(OverlayTriggerStateContext);if(!e)throw new Error("Modal.Close must be used inside Modal.Content");if(!isValidElement(t))throw new Error("Modal.Close requires a valid React element as a child");let o=t,r=()=>{e.close();},s=o.props?.onPress;return cloneElement(o,{onPress:s?()=>{s(),r();}:r})}P.displayName="ModalClose";var J=Object.assign(H,{Trigger:O,Content:p,Overlay:N,Header:_,Title:T,Description:I,Footer:D,Close:P});export{J as Modal,P as ModalClose,p as ModalContent,I as ModalDescription,D as ModalFooter,_ as ModalHeader,N as ModalOverlay,T as ModalTitle,O as ModalTrigger,f as modalContentVariants,M as modalOverlayVariants};//# sourceMappingURL=index.mjs.map
|
|
3
3
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/styles/shared-variants.ts","../../../src/elements/Modal/Modal.styles.ts","../../../src/elements/Modal/Modal.tsx"],"names":["cn","inputs","twMerge","clsx","MODAL_ANIMATION_IN","MODAL_ANIMATION_OUT","MODAL_SLIDE_IN","MODAL_SLIDE_OUT","MODAL_OVERLAY_IN","MODAL_OVERLAY_OUT","REDUCED_MOTION","modalContentVariants","cva","modalOverlayVariants","ModalContext","createContext","ModalRoot","children","defaultOpen","isOpen","onOpenChange","role","childArray","Children","child","isValidElement","ModalContent","triggerChild","ModalTrigger","unwrappedContent","jsx","AriaDialogTrigger","triggerElement","unwrappedTrigger","jsxs","size","animation","animationDuration","isDismissable","isKeyboardDismissDisabled","showClose","className","useContext","overlayClasses","modalClasses","mergedModalClasses","AriaModalOverlay","AriaModal","AriaDialog","close","Fragment","AriaButton","X","ModalOverlay","_props","ModalHeader","ModalTitle","as","Heading","ModalDescription","ModalFooter","ModalClose","state","OverlayTriggerStateContext","childElement","handlePress","existingOnPress","cloneElement","Modal"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Class Name Utility\n * Merges Tailwind CSS classes with conflict resolution\n *\n * Combines clsx for conditional classes and tailwind-merge for deduplication\n *\n * @example\n * cn('px-2 py-1', 'px-4') // => 'py-1 px-4' (px-4 overrides px-2)\n * cn('text-red-500', condition && 'text-blue-500') // => conditional application\n */\n\nimport { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * Shared CVA Variant Utilities\n *\n * Common patterns extracted from component variants for consistency and reduced bundle size.\n * Use these constants in CVA definitions to ensure consistent styling across Themis.\n *\n * @see interaction-states.ts for interaction-specific styles (focus, hover, pressed)\n */\n\n// =============================================================================\n// Focus Ring Patterns\n// =============================================================================\n\n/**\n * Focus-within ring (for container elements with focusable children)\n * Use when the container should show focus when any child is focused\n */\nexport const FOCUS_WITHIN_RING = [\n 'focus-within:outline-none',\n 'focus-within:ring-2',\n 'focus-within:ring-[var(--ring)]',\n 'focus-within:ring-offset-2',\n] as const;\n\n/**\n * Focus-visible ring (for directly focusable elements)\n * Use for buttons, inputs, and other interactive elements\n */\nexport const FOCUS_VISIBLE_RING = [\n 'focus-visible:outline-none',\n 'focus-visible:ring-2',\n 'focus-visible:ring-[var(--ring)]',\n 'focus-visible:ring-offset-2',\n] as const;\n\n/**\n * Standard focus ring (for elements using :focus pseudo-class)\n * Prefer focus-visible when possible for better UX\n */\nexport const FOCUS_RING = [\n 'focus:outline-none',\n 'focus:ring-2',\n 'focus:ring-[var(--ring)]',\n] as const;\n\n/**\n * Focus with background change (for segments, cells, menu items)\n */\nexport const FOCUS_HIGHLIGHT = [\n 'focus:outline-none',\n 'focus:bg-[var(--accent)]',\n 'focus:text-[var(--accent-foreground)]',\n] as const;\n\n// =============================================================================\n// Disabled State Patterns\n// =============================================================================\n\n/**\n * Standard disabled state using disabled attribute\n */\nexport const DISABLED_STANDARD = [\n 'disabled:pointer-events-none',\n 'disabled:opacity-50',\n] as const;\n\n/**\n * Disabled state using data attribute (React Aria pattern)\n */\nexport const DISABLED_DATA_ATTR = [\n 'data-[disabled]:pointer-events-none',\n 'data-[disabled]:opacity-50',\n 'data-[disabled]:cursor-not-allowed',\n] as const;\n\n// =============================================================================\n// Size-Based Text Variants\n// =============================================================================\n\n/**\n * Small text size scale (xs -> sm -> base)\n */\nexport const TEXT_SIZE_SMALL_SCALE = {\n sm: 'text-xs',\n default: 'text-sm',\n lg: 'text-base',\n} as const;\n\n/**\n * Medium text size scale (sm -> base -> lg)\n */\nexport const TEXT_SIZE_MEDIUM_SCALE = {\n sm: 'text-sm',\n default: 'text-base',\n lg: 'text-lg',\n} as const;\n\n// =============================================================================\n// Touch Target Utilities\n// =============================================================================\n\n/**\n * WCAG 2.2 AAA minimum touch target (44x44px)\n */\nexport const TOUCH_TARGET_MIN = [\n 'min-h-[44px]',\n 'min-w-[44px]',\n] as const;\n\n/**\n * Common button/cell sizes with touch target compliance\n */\nexport const INTERACTIVE_SIZES = {\n sm: 'h-9 w-9', // 36px - desktop only, NOT AAA compliant\n default: 'h-11 w-11', // 44px - AAA compliant\n lg: 'h-14 w-14', // 56px - AAA compliant, enhanced\n} as const;\n\n/**\n * Height-only sizes for fields and inputs\n */\nexport const FIELD_HEIGHTS = {\n sm: 'h-9', // 36px\n default: 'h-10', // 40px\n lg: 'h-11', // 44px\n} as const;\n\n/**\n * Layer 2: Visual sizing for form field inputs (height + padding + font).\n * Use on the <input>, <select>, or trigger element itself.\n *\n * Components may append additional classes (e.g. gap-* for segmented fields, py-* for text inputs).\n */\nexport const FIELD_INPUT_SIZES = {\n sm: `${FIELD_HEIGHTS.sm} px-2 text-xs`,\n default: `${FIELD_HEIGHTS.default} px-3 text-sm`,\n lg: `${FIELD_HEIGHTS.lg} px-4 text-base`,\n} as const;\n\n/**\n * Layer 1: Invisible touch target wrapper for form fields.\n * Apply to a <label> (or wrapper div) around the input element.\n * The <label> natively routes clicks/taps to the contained input\n * without JavaScript — achieving a 44px touch target while the\n * input stays at its visual height (h-9/h-10/h-11).\n */\nexport const FIELD_TOUCH_TARGET = 'min-h-[44px] flex items-center' as const;\n\n// =============================================================================\n// Message/Feedback Patterns\n// =============================================================================\n\n/**\n * Error message styling\n */\nexport const ERROR_MESSAGE_BASE = [\n 'flex',\n 'items-center',\n 'gap-1.5',\n 'text-[var(--destructive-background)]',\n] as const;\n\n/**\n * Success message styling\n */\nexport const SUCCESS_MESSAGE_BASE = [\n 'flex',\n 'items-center',\n 'gap-1.5',\n 'text-[var(--success-background)]',\n] as const;\n\n/**\n * Description/helper text styling\n */\nexport const DESCRIPTION_BASE = [\n 'text-[var(--menu-muted)]',\n] as const;\n\n/**\n * Label base styling\n */\nexport const LABEL_BASE = [\n 'font-medium',\n 'text-[var(--content-foreground)]',\n] as const;\n\n/**\n * Required indicator pattern\n */\nexport const REQUIRED_INDICATOR = \"after:content-['*'] after:ml-0.5 after:text-[var(--destructive-background)]\";\n\n// =============================================================================\n// Animation Patterns\n// =============================================================================\n\n/**\n * Popover/dropdown entry animation\n */\nexport const POPOVER_ANIMATION_IN = [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:zoom-in-95',\n] as const;\n\n/**\n * Popover/dropdown exit animation\n */\nexport const POPOVER_ANIMATION_OUT = [\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:zoom-out-95',\n] as const;\n\n/**\n * Modal content enter animation (fade + zoom)\n * Uses React Aria data-[entering]/data-[exiting] attributes\n */\nexport const MODAL_ANIMATION_IN = [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:zoom-in-95',\n 'data-[entering]:duration-200',\n 'data-[entering]:ease-out',\n] as const;\n\n/**\n * Modal content exit animation (fade + zoom)\n * Uses React Aria data-[entering]/data-[exiting] attributes\n */\nexport const MODAL_ANIMATION_OUT = [\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:zoom-out-95',\n 'data-[exiting]:duration-150',\n 'data-[exiting]:ease-in',\n] as const;\n\n/**\n * Modal slide enter animation (fade + slide up)\n */\nexport const MODAL_SLIDE_IN = [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:slide-in-from-bottom-4',\n 'data-[entering]:duration-200',\n 'data-[entering]:ease-out',\n] as const;\n\n/**\n * Modal slide exit animation (fade + slide down)\n */\nexport const MODAL_SLIDE_OUT = [\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:slide-out-to-bottom-4',\n 'data-[exiting]:duration-150',\n 'data-[exiting]:ease-in',\n] as const;\n\n/**\n * Modal overlay enter/exit animation (fade only)\n */\nexport const MODAL_OVERLAY_IN = [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:duration-200',\n] as const;\n\nexport const MODAL_OVERLAY_OUT = [\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:duration-150',\n] as const;\n\n/**\n * Reduced motion support (WCAG 2.2)\n */\nexport const REDUCED_MOTION = [\n 'motion-reduce:transition-none',\n 'motion-reduce:animate-none',\n] as const;\n\n/**\n * Standard transition for colors\n */\nexport const TRANSITION_COLORS = [\n 'transition-colors',\n 'duration-200',\n] as const;\n\n/**\n * Fast transition for interactions\n */\nexport const TRANSITION_FAST = [\n 'transition-colors',\n 'duration-150',\n] as const;\n\n// =============================================================================\n// Hover State Patterns\n// =============================================================================\n\n/**\n * Accent background on hover (for interactive items)\n */\nexport const HOVER_ACCENT = [\n 'hover:bg-[var(--accent)]',\n 'hover:text-[var(--accent-foreground)]',\n] as const;\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Combines multiple style arrays into a flat array for CVA base styles\n */\nexport function combineStyles(...styles: (readonly string[] | string)[]): string[] {\n return styles.flatMap(s => Array.isArray(s) ? [...s] : [s]);\n}\n","import { cva, type VariantProps } from 'class-variance-authority';\nimport {\n MODAL_ANIMATION_IN,\n MODAL_ANIMATION_OUT,\n MODAL_SLIDE_IN,\n MODAL_SLIDE_OUT,\n MODAL_OVERLAY_IN,\n MODAL_OVERLAY_OUT,\n REDUCED_MOTION,\n} from '../../styles/shared-variants';\n\n/**\n * CVA Variants for Modal.Content\n *\n * Size and animation variant definitions for the modal content container.\n * Uses React Aria data-[entering]/data-[exiting] attributes for enter+exit animations.\n * Uses CSS custom properties for all colours to support theming.\n *\n * @see Modal.types.ts (ModalSize, ModalAnimation)\n */\nexport const modalContentVariants = cva(\n [\n // Base styles\n 'relative',\n 'bg-[var(--content-background)]',\n 'text-[var(--content-foreground)]',\n 'rounded-lg',\n 'shadow-lg',\n 'p-6',\n 'w-full',\n 'outline-none',\n // Responsive: full-width on mobile with padding, constrained on desktop\n 'mx-4',\n 'sm:mx-0',\n // Reduced motion support\n ...REDUCED_MOTION,\n ],\n {\n variants: {\n size: {\n sm: 'max-w-sm', // 300px\n md: 'max-w-md', // 425px\n lg: 'max-w-lg', // 600px\n xl: 'max-w-2xl', // 800px\n full: 'max-w-full min-h-screen rounded-none', // Full screen\n },\n animation: {\n 'fade-zoom': [\n ...MODAL_ANIMATION_IN,\n ...MODAL_ANIMATION_OUT,\n ],\n fade: [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:duration-200',\n 'data-[entering]:ease-out',\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:duration-150',\n 'data-[exiting]:ease-in',\n ],\n slide: [\n ...MODAL_SLIDE_IN,\n ...MODAL_SLIDE_OUT,\n ],\n none: '',\n },\n },\n defaultVariants: {\n size: 'md',\n animation: 'fade-zoom',\n },\n }\n);\n\n/**\n * CVA Variants for Modal Overlay\n *\n * Animation variant definitions for the modal backdrop overlay.\n * Uses React Aria data-[entering]/data-[exiting] attributes for enter+exit animations.\n * Uses CSS custom properties for all colours to support theming.\n */\nexport const modalOverlayVariants = cva(\n [\n // Base overlay styles\n 'fixed',\n 'inset-0',\n 'z-50',\n 'flex',\n 'items-center',\n 'justify-center',\n 'bg-black/50',\n 'backdrop-blur-sm',\n // Reduced motion support\n ...REDUCED_MOTION,\n ],\n {\n variants: {\n animation: {\n 'fade-zoom': [...MODAL_OVERLAY_IN, ...MODAL_OVERLAY_OUT],\n fade: [...MODAL_OVERLAY_IN, ...MODAL_OVERLAY_OUT],\n slide: [...MODAL_OVERLAY_IN, ...MODAL_OVERLAY_OUT],\n none: '',\n },\n },\n defaultVariants: {\n animation: 'fade-zoom',\n },\n }\n);\n\n/**\n * Type exports for variant props\n * Allows TypeScript inference of variant combinations\n */\nexport type ModalContentVariantProps = VariantProps<typeof modalContentVariants>;\nexport type ModalOverlayVariantProps = VariantProps<typeof modalOverlayVariants>;\n","'use client';\n\n/**\n * Modal Component - Implementation\n *\n * Accessible modal dialog component combining React Aria primitives with ShadCN styling.\n * Follows Themis library patterns with compound component structure.\n *\n * @see PRD.md (Full requirements)\n * @see Modal.types.ts (Zod schemas)\n * @see RESEARCH.md (React Aria patterns)\n */\n\nimport {\n Children,\n isValidElement,\n cloneElement,\n useContext,\n createContext,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport {\n DialogTrigger as AriaDialogTrigger,\n Modal as AriaModal,\n ModalOverlay as AriaModalOverlay,\n Dialog as AriaDialog,\n Heading,\n Button as AriaButton,\n OverlayTriggerStateContext,\n} from 'react-aria-components';\nimport { X } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { modalContentVariants, modalOverlayVariants } from './Modal.styles';\nimport type {\n ModalProps,\n ModalTriggerProps,\n ModalContentProps,\n ModalOverlayProps,\n ModalHeaderProps,\n ModalTitleProps,\n ModalDescriptionProps,\n ModalFooterProps,\n ModalCloseProps,\n} from './Modal.types';\n\n/**\n * Modal Context\n * Passes role prop from Modal root to Modal.Content\n */\ninterface ModalContextValue {\n role?: 'dialog' | 'alertdialog';\n}\n\nconst ModalContext = createContext<ModalContextValue | undefined>(undefined);\n\n/**\n * Modal Root Component\n *\n * Manages modal open/close state and validates required children structure.\n * Must contain one Modal.Content. Modal.Trigger is optional — when omitted,\n * use controlled mode (isOpen + onOpenChange) to manage the modal.\n *\n * Unwraps compound component children and passes them to React Aria's DialogTrigger.\n *\n * @see PRD.md FR-001 (Modal Root Requirements)\n * @see PRD.md FR-012 (Controlled Mode)\n * @see PRD.md FR-013 (Uncontrolled Mode)\n */\nfunction ModalRoot({ children, defaultOpen, isOpen, onOpenChange, role = 'dialog' }: ModalProps): ReactElement {\n const childArray = Children.toArray(children);\n\n // Validate children structure in development only\n if (process.env.NODE_ENV !== 'production') {\n const hasContent = childArray.some(\n (child) =>\n isValidElement(child) &&\n (child.type === ModalContent ||\n (child.type as { displayName?: string }).displayName === 'ModalContent')\n );\n\n if (!hasContent) {\n throw new Error(\n 'Modal requires a Modal.Content as a child'\n );\n }\n }\n\n // Find trigger and content children\n const triggerChild = childArray.find(\n (child) =>\n isValidElement(child) &&\n (child.type === ModalTrigger ||\n (child.type as { displayName?: string }).displayName === 'ModalTrigger')\n );\n\n const contentChild = childArray.find(\n (child) =>\n isValidElement(child) &&\n (child.type === ModalContent ||\n (child.type as { displayName?: string }).displayName === 'ModalContent')\n );\n\n const unwrappedContent = contentChild as ReactElement;\n\n // When no trigger is provided, render content directly (controlled mode)\n if (!triggerChild) {\n return (\n <ModalContext.Provider value={{ role }}>\n <AriaDialogTrigger\n defaultOpen={defaultOpen}\n isOpen={isOpen}\n onOpenChange={onOpenChange}\n >\n {unwrappedContent}\n </AriaDialogTrigger>\n </ModalContext.Provider>\n );\n }\n\n // Extract the actual children from Modal.Trigger\n // React Aria's DialogTrigger expects direct Button and Modal children\n const triggerElement = triggerChild as ReactElement;\n const unwrappedTrigger = isValidElement(triggerElement)\n ? ((triggerElement.props as unknown as { children?: ReactNode }).children as ReactNode)\n : null;\n\n return (\n <ModalContext.Provider value={{ role }}>\n <AriaDialogTrigger\n defaultOpen={defaultOpen}\n isOpen={isOpen}\n onOpenChange={onOpenChange}\n >\n {unwrappedTrigger}\n {unwrappedContent}\n </AriaDialogTrigger>\n </ModalContext.Provider>\n );\n}\n\nModalRoot.displayName = 'Modal';\n\n/**\n * Modal.Trigger Component\n *\n * Wraps a single child element (typically Button) with modal trigger behavior.\n * Handles click events to open modal and manages ARIA attributes.\n *\n * React Aria's DialogTrigger (used in Modal root) automatically applies:\n * - aria-haspopup=\"dialog\"\n * - aria-expanded (true/false based on state)\n * - onClick handler to toggle modal\n *\n * @see PRD.md FR-002 (Trigger Requirements)\n * @see RESEARCH.md Section 2 (React Aria Integration)\n */\nfunction ModalTrigger({ children }: ModalTriggerProps): ReactElement {\n // React Aria's DialogTrigger (in Modal root) automatically adds ARIA attributes\n // to the first child component. We pass through the child element directly.\n return children as ReactElement;\n}\n\nModalTrigger.displayName = 'ModalTrigger';\n\n/**\n * Modal.Content Component\n *\n * Renders the modal content with overlay backdrop.\n * Uses React Aria's Modal and ModalOverlay for accessibility.\n * Applies CVA variants for size and animation.\n *\n * @see PRD.md FR-003 (Content Requirements)\n * @see PRD.md TR-001 (CVA Variant Styling)\n * @see RESEARCH.md Section 2 (React Aria Integration)\n */\nfunction ModalContent({\n children,\n size = 'md',\n animation = 'fade-zoom',\n animationDuration = 200,\n isDismissable = true,\n isKeyboardDismissDisabled = false,\n showClose = true,\n className,\n}: ModalContentProps): ReactElement {\n // Get role from context\n const context = useContext(ModalContext);\n const role = context?.role ?? 'dialog';\n\n // Generate overlay classes with animation variant\n const overlayClasses = modalOverlayVariants({ animation });\n\n // Generate modal classes with size and animation variants, merged with custom className\n const modalClasses = modalContentVariants({ size, animation });\n const mergedModalClasses = cn(modalClasses, className);\n\n return (\n <AriaModalOverlay\n isDismissable={isDismissable}\n isKeyboardDismissDisabled={isKeyboardDismissDisabled}\n className={overlayClasses}\n >\n <AriaModal\n className={mergedModalClasses}\n style={{\n transitionDuration: `${animationDuration}ms`,\n }}\n >\n <AriaDialog role={role} className=\"outline-none\">\n {({ close }) => (\n <>\n {showClose && (\n <AriaButton\n onPress={close}\n className=\"absolute right-0 top-0 flex items-center justify-center rounded-sm opacity-70 ring-offset-[var(--content-background)] transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-[var(--ring)] focus:ring-offset-2 disabled:pointer-events-none min-h-[44px] min-w-[44px]\"\n aria-label=\"Close modal\"\n >\n <X className=\"h-4 w-4\" aria-hidden=\"true\" />\n </AriaButton>\n )}\n {children}\n </>\n )}\n </AriaDialog>\n </AriaModal>\n </AriaModalOverlay>\n );\n}\n\nModalContent.displayName = 'ModalContent';\n\n/**\n * Modal.Overlay Component (Placeholder - not exposed in public API)\n * Overlay is managed internally by Modal.Content via AriaModalOverlay\n */\nfunction ModalOverlay(_props: ModalOverlayProps): null {\n return null;\n}\n\nModalOverlay.displayName = 'ModalOverlay';\n\n/**\n * Modal.Header Component\n *\n * Container for modal header content (typically Title and Description).\n * Applies consistent spacing and layout.\n *\n * @see PRD.md FR-005 (Header Requirements)\n */\nfunction ModalHeader({ children, className }: ModalHeaderProps): ReactElement {\n return <div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)}>{children}</div>;\n}\n\nModalHeader.displayName = 'ModalHeader';\n\n/**\n * Modal.Title Component\n *\n * Renders modal title using React Aria's Heading component.\n * Automatically links to modal via aria-labelledby.\n *\n * @see PRD.md FR-006 (Title Requirements)\n * @see PRD.md AR-002 (ARIA Attributes - auto-labelledby)\n */\nfunction ModalTitle({ children, as = 'h2', className }: ModalTitleProps): ReactElement {\n return (\n <Heading slot=\"title\" level={parseInt(as[1] ?? '2')} className={cn('text-lg font-semibold leading-none tracking-tight', className)}>\n {children}\n </Heading>\n );\n}\n\nModalTitle.displayName = 'ModalTitle';\n\n/**\n * Modal.Description Component\n *\n * Renders description text that automatically links to modal via aria-describedby.\n * Uses muted text color for visual hierarchy.\n *\n * @see PRD.md FR-007 (Description Requirements)\n * @see PRD.md AR-002 (ARIA Attributes - auto-describedby)\n */\nfunction ModalDescription({ children, className }: ModalDescriptionProps): ReactElement {\n return (\n <p slot=\"description\" className={cn('text-sm text-[var(--menu-muted)]', className)}>\n {children}\n </p>\n );\n}\n\nModalDescription.displayName = 'ModalDescription';\n\n/**\n * Modal.Footer Component\n *\n * Container for modal action buttons (Cancel, Confirm, etc.).\n * Aligns buttons to the right with consistent spacing.\n * Responsive: stacks vertically on mobile, horizontal on desktop.\n *\n * @see PRD.md FR-008 (Footer Requirements)\n * @see PRD.md DS-003 (Spacing - footer button gap)\n */\nfunction ModalFooter({ children, className }: ModalFooterProps): ReactElement {\n return <div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end gap-2', className)}>{children}</div>;\n}\n\nModalFooter.displayName = 'ModalFooter';\n\n/**\n * Modal.Close Component\n *\n * Wraps a child element (typically Button) with modal close behavior.\n * Uses OverlayTriggerStateContext from React Aria to access close function.\n *\n * @see PRD.md FR-009 (Close Requirements)\n */\nfunction ModalClose({ children }: ModalCloseProps): ReactElement {\n // Access the overlay trigger state from React Aria context\n const state = useContext(OverlayTriggerStateContext);\n\n if (!state) {\n throw new Error('Modal.Close must be used inside Modal.Content');\n }\n\n // Clone the child element and add/merge the onPress handler to close the modal\n if (!isValidElement(children)) {\n throw new Error('Modal.Close requires a valid React element as a child');\n }\n\n // Cast to ReactElement after validation\n const childElement = children as ReactElement;\n\n const handlePress = (): void => {\n state.close();\n };\n\n // Merge with existing onPress if present\n const existingOnPress = (childElement.props as unknown as { onPress?: () => void })?.onPress;\n const mergedOnPress = existingOnPress\n ? (): void => {\n existingOnPress();\n handlePress();\n }\n : handlePress;\n\n // cloneElement with onPress override - use unknown for flexible typing\n return cloneElement(childElement, { onPress: mergedOnPress } as unknown as Partial<typeof childElement.props>);\n}\n\nModalClose.displayName = 'ModalClose';\n\n/**\n * Re-export CVA variants from Modal.styles.ts for backwards compatibility.\n * Consumers importing { modalContentVariants, modalOverlayVariants } from './Modal'\n * will continue to work.\n */\nexport { modalContentVariants, modalOverlayVariants } from './Modal.styles';\n\n/**\n * Compound Component Export\n *\n * Follows Themis library pattern using Object.assign() for compound components.\n * Enables usage like Modal.Trigger, Modal.Content, etc.\n *\n * @deprecated The Object.assign compound pattern will be removed in v2.\n * Use the direct named exports (ModalTrigger, ModalContent, etc.) instead.\n * This pattern is kept for backwards compatibility only.\n *\n * @see RESEARCH.md Section 1 (Compound Component Pattern)\n */\nexport const Modal = Object.assign(ModalRoot, {\n Trigger: ModalTrigger,\n Content: ModalContent,\n Overlay: ModalOverlay,\n Header: ModalHeader,\n Title: ModalTitle,\n Description: ModalDescription,\n Footer: ModalFooter,\n Close: ModalClose,\n});\n\n// Named exports for individual components\nexport {\n ModalRoot,\n ModalTrigger,\n ModalContent,\n ModalOverlay,\n ModalHeader,\n ModalTitle,\n ModalDescription,\n ModalFooter,\n ModalClose,\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/styles/shared-variants.ts","../../../src/elements/Modal/Modal.styles.ts","../../../src/elements/Modal/Modal.tsx"],"names":["cn","inputs","twMerge","clsx","MODAL_ANIMATION_IN","MODAL_ANIMATION_OUT","MODAL_SLIDE_IN","MODAL_SLIDE_OUT","MODAL_OVERLAY_IN","MODAL_OVERLAY_OUT","REDUCED_MOTION","modalContentVariants","cva","modalOverlayVariants","ModalContext","createContext","ModalRoot","children","defaultOpen","isOpen","onOpenChange","role","childArray","Children","child","isValidElement","ModalContent","triggerChild","ModalTrigger","unwrappedContent","jsx","AriaDialogTrigger","triggerElement","unwrappedTrigger","jsxs","size","animation","animationDuration","isDismissable","isKeyboardDismissDisabled","showClose","className","useContext","overlayClasses","modalClasses","mergedModalClasses","AriaModalOverlay","AriaModal","AriaDialog","close","Fragment","AriaButton","X","ModalOverlay","_props","ModalHeader","ModalTitle","as","Heading","ModalDescription","ModalFooter","ModalClose","state","OverlayTriggerStateContext","childElement","handlePress","existingOnPress","cloneElement","Modal"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Class Name Utility\n * Merges Tailwind CSS classes with conflict resolution\n *\n * Combines clsx for conditional classes and tailwind-merge for deduplication\n *\n * @example\n * cn('px-2 py-1', 'px-4') // => 'py-1 px-4' (px-4 overrides px-2)\n * cn('text-red-500', condition && 'text-blue-500') // => conditional application\n */\n\nimport { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * Shared CVA Variant Utilities\n *\n * Common patterns extracted from component variants for consistency and reduced bundle size.\n * Use these constants in CVA definitions to ensure consistent styling across Themis.\n *\n * @see interaction-states.ts for interaction-specific styles (focus, hover, pressed)\n */\n\n// =============================================================================\n// Focus Ring Patterns\n// =============================================================================\n\n/**\n * Focus-within ring (for container elements with focusable children)\n * Use when the container should show focus when any child is focused\n */\nexport const FOCUS_WITHIN_RING = [\n 'focus-within:outline-none',\n 'focus-within:ring-2',\n 'focus-within:ring-[var(--ring)]',\n 'focus-within:ring-offset-2',\n] as const;\n\n/**\n * Focus-visible ring (for directly focusable elements)\n * Use for buttons, inputs, and other interactive elements\n */\nexport const FOCUS_VISIBLE_RING = [\n 'focus-visible:outline-none',\n 'focus-visible:ring-2',\n 'focus-visible:ring-[var(--ring)]',\n 'focus-visible:ring-offset-2',\n] as const;\n\n/**\n * Standard focus ring (for elements using :focus pseudo-class)\n * Prefer focus-visible when possible for better UX\n */\nexport const FOCUS_RING = [\n 'focus:outline-none',\n 'focus:ring-2',\n 'focus:ring-[var(--ring)]',\n] as const;\n\n/**\n * Focus with background change (for segments, cells, menu items)\n */\nexport const FOCUS_HIGHLIGHT = [\n 'focus:outline-none',\n 'focus:bg-[var(--accent)]',\n 'focus:text-[var(--accent-foreground)]',\n] as const;\n\n// =============================================================================\n// Disabled State Patterns\n// =============================================================================\n\n/**\n * Standard disabled state using disabled attribute\n */\nexport const DISABLED_STANDARD = [\n 'disabled:pointer-events-none',\n 'disabled:opacity-50',\n] as const;\n\n/**\n * Disabled state using data attribute (React Aria pattern)\n */\nexport const DISABLED_DATA_ATTR = [\n 'data-[disabled]:pointer-events-none',\n 'data-[disabled]:opacity-50',\n 'data-[disabled]:cursor-not-allowed',\n] as const;\n\n// =============================================================================\n// Size-Based Text Variants\n// =============================================================================\n\n/**\n * Small text size scale (xs -> sm -> base)\n */\nexport const TEXT_SIZE_SMALL_SCALE = {\n sm: 'text-xs',\n default: 'text-sm',\n lg: 'text-base',\n} as const;\n\n/**\n * Medium text size scale (sm -> base -> lg)\n */\nexport const TEXT_SIZE_MEDIUM_SCALE = {\n sm: 'text-sm',\n default: 'text-base',\n lg: 'text-lg',\n} as const;\n\n// =============================================================================\n// Touch Target Utilities\n// =============================================================================\n\n/**\n * WCAG 2.2 AAA minimum touch target (44x44px)\n */\nexport const TOUCH_TARGET_MIN = [\n 'min-h-[44px]',\n 'min-w-[44px]',\n] as const;\n\n/**\n * Common button/cell sizes with touch target compliance\n */\nexport const INTERACTIVE_SIZES = {\n sm: 'h-9 w-9', // 36px - desktop only, NOT AAA compliant\n default: 'h-11 w-11', // 44px - AAA compliant\n lg: 'h-14 w-14', // 56px - AAA compliant, enhanced\n} as const;\n\n/**\n * Height-only sizes for fields and inputs\n */\nexport const FIELD_HEIGHTS = {\n sm: 'h-9', // 36px\n default: 'h-10', // 40px\n lg: 'h-11', // 44px\n} as const;\n\n/**\n * Layer 2: Visual sizing for form field inputs (height + padding + font).\n * Use on the <input>, <select>, or trigger element itself.\n *\n * Components may append additional classes (e.g. gap-* for segmented fields, py-* for text inputs).\n */\nexport const FIELD_INPUT_SIZES = {\n sm: `${FIELD_HEIGHTS.sm} px-2 text-xs`,\n default: `${FIELD_HEIGHTS.default} px-3 text-sm`,\n lg: `${FIELD_HEIGHTS.lg} px-4 text-base`,\n} as const;\n\n/**\n * Layer 1: Invisible touch target wrapper for form fields.\n * Apply to a <label> (or wrapper div) around the input element.\n * The <label> natively routes clicks/taps to the contained input\n * without JavaScript — achieving a 44px touch target while the\n * input stays at its visual height (h-9/h-10/h-11).\n */\nexport const FIELD_TOUCH_TARGET = 'min-h-[44px] flex items-center' as const;\n\n// =============================================================================\n// Message/Feedback Patterns\n// =============================================================================\n\n/**\n * Error message styling\n */\nexport const ERROR_MESSAGE_BASE = [\n 'flex',\n 'items-center',\n 'gap-1.5',\n 'text-[var(--destructive-background)]',\n] as const;\n\n/**\n * Success message styling\n */\nexport const SUCCESS_MESSAGE_BASE = [\n 'flex',\n 'items-center',\n 'gap-1.5',\n 'text-[var(--success-background)]',\n] as const;\n\n/**\n * Description/helper text styling\n */\nexport const DESCRIPTION_BASE = [\n 'text-[var(--menu-muted)]',\n] as const;\n\n/**\n * Label base styling\n */\nexport const LABEL_BASE = [\n 'font-medium',\n 'text-[var(--content-foreground)]',\n] as const;\n\n/**\n * Required indicator pattern\n */\nexport const REQUIRED_INDICATOR = \"after:content-['*'] after:ml-0.5 after:text-[var(--destructive-background)]\";\n\n// =============================================================================\n// Animation Patterns\n// =============================================================================\n\n/**\n * Popover/dropdown entry animation\n */\nexport const POPOVER_ANIMATION_IN = [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:zoom-in-95',\n] as const;\n\n/**\n * Popover/dropdown exit animation\n */\nexport const POPOVER_ANIMATION_OUT = [\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:zoom-out-95',\n] as const;\n\n/**\n * Modal content enter animation (fade + zoom)\n * Uses React Aria data-[entering]/data-[exiting] attributes\n */\nexport const MODAL_ANIMATION_IN = [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:zoom-in-95',\n 'data-[entering]:duration-200',\n 'data-[entering]:ease-out',\n] as const;\n\n/**\n * Modal content exit animation (fade + zoom)\n * Uses React Aria data-[entering]/data-[exiting] attributes\n */\nexport const MODAL_ANIMATION_OUT = [\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:zoom-out-95',\n 'data-[exiting]:duration-150',\n 'data-[exiting]:ease-in',\n] as const;\n\n/**\n * Modal slide enter animation (fade + slide up)\n */\nexport const MODAL_SLIDE_IN = [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:slide-in-from-bottom-4',\n 'data-[entering]:duration-200',\n 'data-[entering]:ease-out',\n] as const;\n\n/**\n * Modal slide exit animation (fade + slide down)\n */\nexport const MODAL_SLIDE_OUT = [\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:slide-out-to-bottom-4',\n 'data-[exiting]:duration-150',\n 'data-[exiting]:ease-in',\n] as const;\n\n/**\n * Modal overlay enter/exit animation (fade only)\n */\nexport const MODAL_OVERLAY_IN = [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:duration-200',\n] as const;\n\nexport const MODAL_OVERLAY_OUT = [\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:duration-150',\n] as const;\n\n/**\n * Reduced motion support (WCAG 2.2)\n */\nexport const REDUCED_MOTION = [\n 'motion-reduce:transition-none',\n 'motion-reduce:animate-none',\n] as const;\n\n/**\n * Standard transition for colors\n */\nexport const TRANSITION_COLORS = [\n 'transition-colors',\n 'duration-200',\n] as const;\n\n/**\n * Fast transition for interactions\n */\nexport const TRANSITION_FAST = [\n 'transition-colors',\n 'duration-150',\n] as const;\n\n// =============================================================================\n// Hover State Patterns\n// =============================================================================\n\n/**\n * Accent background on hover (for interactive items)\n */\nexport const HOVER_ACCENT = [\n 'hover:bg-[var(--accent)]',\n 'hover:text-[var(--accent-foreground)]',\n] as const;\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Combines multiple style arrays into a flat array for CVA base styles\n */\nexport function combineStyles(...styles: (readonly string[] | string)[]): string[] {\n return styles.flatMap(s => Array.isArray(s) ? [...s] : [s]);\n}\n","import { cva, type VariantProps } from 'class-variance-authority';\nimport {\n MODAL_ANIMATION_IN,\n MODAL_ANIMATION_OUT,\n MODAL_SLIDE_IN,\n MODAL_SLIDE_OUT,\n MODAL_OVERLAY_IN,\n MODAL_OVERLAY_OUT,\n REDUCED_MOTION,\n} from '../../styles/shared-variants';\n\n/**\n * CVA Variants for Modal.Content\n *\n * Size and animation variant definitions for the modal content container.\n * Uses React Aria data-[entering]/data-[exiting] attributes for enter+exit animations.\n * Uses CSS custom properties for all colours to support theming.\n *\n * @see Modal.types.ts (ModalSize, ModalAnimation)\n */\nexport const modalContentVariants = cva(\n [\n // Base styles\n 'relative',\n 'bg-[var(--content-background)]',\n 'text-[var(--content-foreground)]',\n 'rounded-lg',\n 'shadow-lg',\n 'p-6',\n 'w-full',\n 'outline-none',\n // Responsive: full-width on mobile with padding, constrained on desktop\n 'mx-4',\n 'sm:mx-0',\n // Reduced motion support\n ...REDUCED_MOTION,\n ],\n {\n variants: {\n size: {\n sm: 'max-w-sm', // 300px\n md: 'max-w-md', // 425px\n lg: 'max-w-lg', // 600px\n xl: 'max-w-2xl', // 800px\n full: 'max-w-full min-h-screen rounded-none', // Full screen\n },\n animation: {\n 'fade-zoom': [\n ...MODAL_ANIMATION_IN,\n ...MODAL_ANIMATION_OUT,\n ],\n fade: [\n 'data-[entering]:animate-in',\n 'data-[entering]:fade-in-0',\n 'data-[entering]:duration-200',\n 'data-[entering]:ease-out',\n 'data-[exiting]:animate-out',\n 'data-[exiting]:fade-out-0',\n 'data-[exiting]:duration-150',\n 'data-[exiting]:ease-in',\n ],\n slide: [\n ...MODAL_SLIDE_IN,\n ...MODAL_SLIDE_OUT,\n ],\n none: '',\n },\n },\n defaultVariants: {\n size: 'md',\n animation: 'fade-zoom',\n },\n }\n);\n\n/**\n * CVA Variants for Modal Overlay\n *\n * Animation variant definitions for the modal backdrop overlay.\n * Uses React Aria data-[entering]/data-[exiting] attributes for enter+exit animations.\n * Uses CSS custom properties for all colours to support theming.\n */\nexport const modalOverlayVariants = cva(\n [\n // Base overlay styles\n 'fixed',\n 'inset-0',\n 'z-50',\n 'flex',\n 'items-center',\n 'justify-center',\n 'bg-black/50',\n 'backdrop-blur-sm',\n // Reduced motion support\n ...REDUCED_MOTION,\n ],\n {\n variants: {\n animation: {\n 'fade-zoom': [...MODAL_OVERLAY_IN, ...MODAL_OVERLAY_OUT],\n fade: [...MODAL_OVERLAY_IN, ...MODAL_OVERLAY_OUT],\n slide: [...MODAL_OVERLAY_IN, ...MODAL_OVERLAY_OUT],\n none: '',\n },\n },\n defaultVariants: {\n animation: 'fade-zoom',\n },\n }\n);\n\n/**\n * Type exports for variant props\n * Allows TypeScript inference of variant combinations\n */\nexport type ModalContentVariantProps = VariantProps<typeof modalContentVariants>;\nexport type ModalOverlayVariantProps = VariantProps<typeof modalOverlayVariants>;\n","'use client';\n\n/**\n * Modal Component - Implementation\n *\n * Accessible modal dialog component combining React Aria primitives with ShadCN styling.\n * Follows Themis library patterns with compound component structure.\n *\n * @see PRD.md (Full requirements)\n * @see Modal.types.ts (Zod schemas)\n * @see RESEARCH.md (React Aria patterns)\n */\n\nimport {\n Children,\n isValidElement,\n cloneElement,\n useContext,\n createContext,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport {\n DialogTrigger as AriaDialogTrigger,\n Modal as AriaModal,\n ModalOverlay as AriaModalOverlay,\n Dialog as AriaDialog,\n Heading,\n Button as AriaButton,\n OverlayTriggerStateContext,\n} from 'react-aria-components';\nimport { X } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { modalContentVariants, modalOverlayVariants } from './Modal.styles';\nimport type {\n ModalProps,\n ModalTriggerProps,\n ModalContentProps,\n ModalOverlayProps,\n ModalHeaderProps,\n ModalTitleProps,\n ModalDescriptionProps,\n ModalFooterProps,\n ModalCloseProps,\n} from './Modal.types';\n\n/**\n * Modal Context\n * Passes role prop from Modal root to Modal.Content\n */\ninterface ModalContextValue {\n role?: 'dialog' | 'alertdialog';\n}\n\nconst ModalContext = createContext<ModalContextValue | undefined>(undefined);\n\n/**\n * Modal Root Component\n *\n * Manages modal open/close state and validates required children structure.\n * Must contain one Modal.Content. Modal.Trigger is optional — when omitted,\n * use controlled mode (isOpen + onOpenChange) to manage the modal.\n *\n * Unwraps compound component children and passes them to React Aria's DialogTrigger.\n *\n * @see PRD.md FR-001 (Modal Root Requirements)\n * @see PRD.md FR-012 (Controlled Mode)\n * @see PRD.md FR-013 (Uncontrolled Mode)\n */\nfunction ModalRoot({ children, defaultOpen, isOpen, onOpenChange, role = 'dialog' }: ModalProps): ReactElement {\n const childArray = Children.toArray(children);\n\n // Validate children structure in development only\n if (process.env.NODE_ENV !== 'production') {\n const hasContent = childArray.some(\n (child) =>\n isValidElement(child) &&\n (child.type === ModalContent ||\n (child.type as { displayName?: string }).displayName === 'ModalContent')\n );\n\n if (!hasContent) {\n throw new Error(\n 'Modal requires a Modal.Content as a child'\n );\n }\n }\n\n // Find trigger and content children\n const triggerChild = childArray.find(\n (child) =>\n isValidElement(child) &&\n (child.type === ModalTrigger ||\n (child.type as { displayName?: string }).displayName === 'ModalTrigger')\n );\n\n const contentChild = childArray.find(\n (child) =>\n isValidElement(child) &&\n (child.type === ModalContent ||\n (child.type as { displayName?: string }).displayName === 'ModalContent')\n );\n\n const unwrappedContent = contentChild as ReactElement;\n\n // When no trigger is provided, render content directly (controlled mode)\n if (!triggerChild) {\n return (\n <ModalContext.Provider value={{ role }}>\n <AriaDialogTrigger\n defaultOpen={defaultOpen}\n isOpen={isOpen}\n onOpenChange={onOpenChange}\n >\n {unwrappedContent}\n </AriaDialogTrigger>\n </ModalContext.Provider>\n );\n }\n\n // Extract the actual children from Modal.Trigger\n // React Aria's DialogTrigger expects direct Button and Modal children\n const triggerElement = triggerChild as ReactElement;\n const unwrappedTrigger = isValidElement(triggerElement)\n ? ((triggerElement.props as unknown as { children?: ReactNode }).children as ReactNode)\n : null;\n\n return (\n <ModalContext.Provider value={{ role }}>\n <AriaDialogTrigger\n defaultOpen={defaultOpen}\n isOpen={isOpen}\n onOpenChange={onOpenChange}\n >\n {unwrappedTrigger}\n {unwrappedContent}\n </AriaDialogTrigger>\n </ModalContext.Provider>\n );\n}\n\nModalRoot.displayName = 'Modal';\n\n/**\n * Modal.Trigger Component\n *\n * Wraps a single child element (typically Button) with modal trigger behavior.\n * Handles click events to open modal and manages ARIA attributes.\n *\n * React Aria's DialogTrigger (used in Modal root) automatically applies:\n * - aria-haspopup=\"dialog\"\n * - aria-expanded (true/false based on state)\n * - onClick handler to toggle modal\n *\n * @see PRD.md FR-002 (Trigger Requirements)\n * @see RESEARCH.md Section 2 (React Aria Integration)\n */\nfunction ModalTrigger({ children }: ModalTriggerProps): ReactElement {\n // React Aria's DialogTrigger (in Modal root) automatically adds ARIA attributes\n // to the first child component. We pass through the child element directly.\n return children as ReactElement;\n}\n\nModalTrigger.displayName = 'ModalTrigger';\n\n/**\n * Modal.Content Component\n *\n * Renders the modal content with overlay backdrop.\n * Uses React Aria's Modal and ModalOverlay for accessibility.\n * Applies CVA variants for size and animation.\n *\n * @see PRD.md FR-003 (Content Requirements)\n * @see PRD.md TR-001 (CVA Variant Styling)\n * @see RESEARCH.md Section 2 (React Aria Integration)\n */\nfunction ModalContent({\n children,\n size = 'md',\n animation = 'fade-zoom',\n animationDuration = 250,\n isDismissable = true,\n isKeyboardDismissDisabled = false,\n showClose = true,\n className,\n}: ModalContentProps): ReactElement {\n // Get role from context\n const context = useContext(ModalContext);\n const role = context?.role ?? 'dialog';\n\n // Generate overlay classes with animation variant\n const overlayClasses = modalOverlayVariants({ animation });\n\n // Generate modal classes with size and animation variants, merged with custom className\n const modalClasses = modalContentVariants({ size, animation });\n const mergedModalClasses = cn(modalClasses, className);\n\n return (\n <AriaModalOverlay\n isDismissable={isDismissable}\n isKeyboardDismissDisabled={isKeyboardDismissDisabled}\n className={overlayClasses}\n >\n <AriaModal\n className={mergedModalClasses}\n style={{\n transitionDuration: `${animationDuration}ms`,\n }}\n >\n <AriaDialog role={role} className=\"outline-none\">\n {({ close }) => (\n <>\n {showClose && (\n <AriaButton\n onPress={close}\n className=\"absolute right-0 top-0 flex items-center justify-center rounded-sm opacity-70 ring-offset-[var(--content-background)] transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-[var(--ring)] focus:ring-offset-2 disabled:pointer-events-none min-h-[44px] min-w-[44px]\"\n aria-label=\"Close modal\"\n >\n <X className=\"h-4 w-4\" aria-hidden=\"true\" />\n </AriaButton>\n )}\n {children}\n </>\n )}\n </AriaDialog>\n </AriaModal>\n </AriaModalOverlay>\n );\n}\n\nModalContent.displayName = 'ModalContent';\n\n/**\n * Modal.Overlay Component (Placeholder - not exposed in public API)\n * Overlay is managed internally by Modal.Content via AriaModalOverlay\n */\nfunction ModalOverlay(_props: ModalOverlayProps): null {\n return null;\n}\n\nModalOverlay.displayName = 'ModalOverlay';\n\n/**\n * Modal.Header Component\n *\n * Container for modal header content (typically Title and Description).\n * Applies consistent spacing and layout.\n *\n * @see PRD.md FR-005 (Header Requirements)\n */\nfunction ModalHeader({ children, className }: ModalHeaderProps): ReactElement {\n return <div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)}>{children}</div>;\n}\n\nModalHeader.displayName = 'ModalHeader';\n\n/**\n * Modal.Title Component\n *\n * Renders modal title using React Aria's Heading component.\n * Automatically links to modal via aria-labelledby.\n *\n * @see PRD.md FR-006 (Title Requirements)\n * @see PRD.md AR-002 (ARIA Attributes - auto-labelledby)\n */\nfunction ModalTitle({ children, as = 'h2', className }: ModalTitleProps): ReactElement {\n return (\n <Heading slot=\"title\" level={parseInt(as[1] ?? '2')} className={cn('text-lg font-semibold leading-none tracking-tight', className)}>\n {children}\n </Heading>\n );\n}\n\nModalTitle.displayName = 'ModalTitle';\n\n/**\n * Modal.Description Component\n *\n * Renders description text that automatically links to modal via aria-describedby.\n * Uses muted text color for visual hierarchy.\n *\n * @see PRD.md FR-007 (Description Requirements)\n * @see PRD.md AR-002 (ARIA Attributes - auto-describedby)\n */\nfunction ModalDescription({ children, className }: ModalDescriptionProps): ReactElement {\n return (\n <p slot=\"description\" className={cn('text-sm text-[var(--menu-muted)]', className)}>\n {children}\n </p>\n );\n}\n\nModalDescription.displayName = 'ModalDescription';\n\n/**\n * Modal.Footer Component\n *\n * Container for modal action buttons (Cancel, Confirm, etc.).\n * Aligns buttons to the right with consistent spacing.\n * Responsive: stacks vertically on mobile, horizontal on desktop.\n *\n * @see PRD.md FR-008 (Footer Requirements)\n * @see PRD.md DS-003 (Spacing - footer button gap)\n */\nfunction ModalFooter({ children, className }: ModalFooterProps): ReactElement {\n return <div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end gap-2', className)}>{children}</div>;\n}\n\nModalFooter.displayName = 'ModalFooter';\n\n/**\n * Modal.Close Component\n *\n * Wraps a child element (typically Button) with modal close behavior.\n * Uses OverlayTriggerStateContext from React Aria to access close function.\n *\n * @see PRD.md FR-009 (Close Requirements)\n */\nfunction ModalClose({ children }: ModalCloseProps): ReactElement {\n // Access the overlay trigger state from React Aria context\n const state = useContext(OverlayTriggerStateContext);\n\n if (!state) {\n throw new Error('Modal.Close must be used inside Modal.Content');\n }\n\n // Clone the child element and add/merge the onPress handler to close the modal\n if (!isValidElement(children)) {\n throw new Error('Modal.Close requires a valid React element as a child');\n }\n\n // Cast to ReactElement after validation\n const childElement = children as ReactElement;\n\n const handlePress = (): void => {\n state.close();\n };\n\n // Merge with existing onPress if present\n const existingOnPress = (childElement.props as unknown as { onPress?: () => void })?.onPress;\n const mergedOnPress = existingOnPress\n ? (): void => {\n existingOnPress();\n handlePress();\n }\n : handlePress;\n\n // cloneElement with onPress override - use unknown for flexible typing\n return cloneElement(childElement, { onPress: mergedOnPress } as unknown as Partial<typeof childElement.props>);\n}\n\nModalClose.displayName = 'ModalClose';\n\n/**\n * Re-export CVA variants from Modal.styles.ts for backwards compatibility.\n * Consumers importing { modalContentVariants, modalOverlayVariants } from './Modal'\n * will continue to work.\n */\nexport { modalContentVariants, modalOverlayVariants } from './Modal.styles';\n\n/**\n * Compound Component Export\n *\n * Follows Themis library pattern using Object.assign() for compound components.\n * Enables usage like Modal.Trigger, Modal.Content, etc.\n *\n * @deprecated The Object.assign compound pattern will be removed in v2.\n * Use the direct named exports (ModalTrigger, ModalContent, etc.) instead.\n * This pattern is kept for backwards compatibility only.\n *\n * @see RESEARCH.md Section 1 (Compound Component Pattern)\n */\nexport const Modal = Object.assign(ModalRoot, {\n Trigger: ModalTrigger,\n Content: ModalContent,\n Overlay: ModalOverlay,\n Header: ModalHeader,\n Title: ModalTitle,\n Description: ModalDescription,\n Footer: ModalFooter,\n Close: ModalClose,\n});\n\n// Named exports for individual components\nexport {\n ModalRoot,\n ModalTrigger,\n ModalContent,\n ModalOverlay,\n ModalHeader,\n ModalTitle,\n ModalDescription,\n ModalFooter,\n ModalClose,\n};\n"]}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
'use strict';var react=require('react'),clsx=require('clsx'),tailwindMerge=require('tailwind-merge'),zod=require('zod'),classVarianceAuthority=require('class-variance-authority'),lucideReact=require('lucide-react'),reactAriaComponents=require('react-aria-components'),jsxRuntime=require('react/jsx-runtime');function l(...e){return tailwindMerge.twMerge(clsx.clsx(e))}var j=react.createContext(null);function v(){let e=react.useContext(j);if(!e)throw new Error("PageTitle compound components must be used within <PageTitle>");return e}var h=zod.z.object({className:zod.z.string().optional(),children:zod.z.any().optional(),id:zod.z.string().optional(),"aria-label":zod.z.string().optional(),"aria-labelledby":zod.z.string().optional(),"aria-describedby":zod.z.string().optional(),"aria-live":zod.z.enum(["off","polite","assertive"]).optional(),"aria-hidden":zod.z.boolean().optional(),"data-testid":zod.z.string().optional()});var Z=zod.z.object({id:zod.z.union([zod.z.string(),zod.z.number()]),label:zod.z.string(),href:zod.z.string().optional(),icon:zod.z.custom().optional(),isDisabled:zod.z.boolean().optional().default(false)}),rt=zod.z.union([zod.z.literal("chevron"),zod.z.literal("slash"),zod.z.custom()]);h.extend({items:zod.z.array(Z),separator:rt.optional().default("chevron"),maxItems:zod.z.number().int().positive().optional().default(4),itemsBeforeCollapse:zod.z.number().int().nonnegative().optional().default(1),itemsAfterCollapse:zod.z.number().int().positive().optional().default(2),linkComponent:zod.z.custom().optional(),isDisabled:zod.z.boolean().optional().default(false)});var J=classVarianceAuthority.cva("z-50 min-w-[8rem] overflow-hidden rounded-md border border-[var(--menu-border)] bg-[var(--menu-background)] text-[var(--menu-foreground)] shadow-md p-1",{variants:{variant:{default:""}},defaultVariants:{variant:"default"}}),k=classVarianceAuthority.cva("relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 min-h-[44px] data-[focused]:bg-[var(--menu-accent)] data-[focused]:text-[var(--menu-accent-foreground)] data-[pressed]:bg-[var(--menu-accent)] [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",{variants:{variant:{default:""}},defaultVariants:{variant:"default"}});function Q(e){if(typeof e=="function"||typeof e=="object")return e?.displayName}var Ie=react.forwardRef(({defaultOpen:e=false,isOpen:t,onOpenChange:a,children:r},o)=>{if(process.env.NODE_ENV!=="production"){let n=react.Children.toArray(r),s=n.some(p=>react.isValidElement(p)&&(p.type===H||Q(p.type)==="DropdownTrigger")),u=n.some(p=>react.isValidElement(p)&&(p.type===C||Q(p.type)==="DropdownMenu"));if(!s||!u)throw new Error("Dropdown requires exactly one Dropdown.Trigger and one Dropdown.Menu as children")}return jsxRuntime.jsx(reactAriaComponents.MenuTrigger,{defaultOpen:e,isOpen:t,onOpenChange:a,children:r})});Ie.displayName="Dropdown";var H=react.forwardRef(({children:e,...t},a)=>react.isValidElement(e)?react.cloneElement(e,{...t,slot:"trigger"}):jsxRuntime.jsx(reactAriaComponents.Button,{ref:a,...t,children:e}));H.displayName="DropdownTrigger";var C=react.forwardRef(({align:e="start",side:t="bottom",className:a,children:r,...o},n)=>{let s=`${t} ${e}`;return jsxRuntime.jsx(reactAriaComponents.Popover,{placement:s,className:"data-[entering]:motion-safe:animate-in data-[entering]:motion-safe:fade-in-0 data-[entering]:motion-safe:slide-in-from-top-2 data-[entering]:[animation-duration:250ms] data-[entering]:[animation-timing-function:cubic-bezier(0.16,1,0.3,1)] data-[exiting]:motion-safe:animate-out data-[exiting]:motion-safe:fade-out-0 data-[exiting]:motion-safe:slide-out-to-top-2 data-[exiting]:[animation-duration:150ms]",children:jsxRuntime.jsx(reactAriaComponents.Menu,{className:l(J({variant:"default"}),a),...o,children:r})})});C.displayName="DropdownMenu";var X=react.forwardRef(({children:e,onAction:t,isDisabled:a=false,shortcut:r,icon:o,iconRight:n,className:s,"aria-label":u,...p},m)=>{let f=react.Children.toArray(e),c=x=>react.isValidElement(x)&&(x.type===C||Q(x.type)==="DropdownMenu"),g=f.some(c),P=f.filter(x=>!c(x)),w=f.find(c);return g&&w?jsxRuntime.jsxs(reactAriaComponents.SubmenuTrigger,{children:[jsxRuntime.jsxs(reactAriaComponents.MenuItem,{"aria-label":u,className:l(k({variant:"default"}),s),...p,children:[o&&jsxRuntime.jsx("span",{className:"mr-2",children:o}),jsxRuntime.jsx("span",{className:"flex-1",children:P}),jsxRuntime.jsx(lucideReact.ChevronRight,{className:"ml-auto h-4 w-4","aria-hidden":"true"})]}),w]}):jsxRuntime.jsxs(reactAriaComponents.MenuItem,{onAction:t,isDisabled:a,"aria-label":u,className:l(k({variant:"default"}),s),...p,children:[o&&jsxRuntime.jsx("span",{className:"mr-2",children:o}),jsxRuntime.jsx("span",{className:"flex-1",children:e}),n&&jsxRuntime.jsx("span",{className:"ml-auto",children:n}),r&&jsxRuntime.jsx("kbd",{className:"ml-auto pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-[var(--border)] bg-[var(--accent-background)] px-1.5 font-mono text-[10px] font-medium text-[var(--menu-muted)] opacity-100",children:r})]})});X.displayName="DropdownItem";var ee=react.memo(react.forwardRef(({className:e,...t},a)=>jsxRuntime.jsx(reactAriaComponents.Separator,{className:l("-mx-1 my-1 h-px bg-[var(--accent-background)]",e),...t})));ee.displayName="DropdownSeparator";var te=react.memo(react.forwardRef(({children:e,className:t,...a},r)=>jsxRuntime.jsx(reactAriaComponents.Header,{className:l("px-2 py-1.5 text-sm font-semibold text-[var(--menu-muted)]",t),...a,children:e})));te.displayName="DropdownLabel";var S=Object.assign(Ie,{Trigger:H,Menu:C,Item:X,Separator:ee,Label:te});var ae=classVarianceAuthority.cva("inline-flex justify-center min-h-[44px] min-w-[44px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50",{variants:{fullWidth:{true:"w-full",false:""},inVerticalGroup:{true:"items-stretch",false:"items-center"}},defaultVariants:{fullWidth:false,inVerticalGroup:false}}),re=classVarianceAuthority.cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background motion-safe:transition-transform motion-safe:duration-200 motion-safe:[transition-timing-function:cubic-bezier(0.34,1.56,0.64,1)] motion-reduce:transition-none [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 relative cursor-pointer",{variants:{variant:{default:"bg-[var(--primary-action)] text-[var(--primary-action-foreground)] shadow-md hover:bg-[var(--primary-action-hover)] hover:-translate-y-1",destructive:"bg-[var(--destructive-background)] text-[var(--destructive-foreground)] shadow-md hover:bg-[var(--destructive-background-hover,var(--destructive-background))] motion-safe:hover:btn-warn-shake",outline:"border border-[var(--input-border)] bg-[var(--page-background)] hover:bg-[var(--input-border)]",secondary:"bg-[var(--secondary)] text-[var(--secondary-foreground)] shadow-md hover:bg-[var(--secondary-hover,var(--secondary))]",ghost:"hover:bg-[var(--accent)] hover:text-[var(--accent-foreground)]",link:"text-[var(--text-link)] underline underline-offset-4 hover:bg-[var(--accent)] hover:text-[var(--text-link-hover)]"},fullWidth:{true:"w-full",false:""},visualSize:{default:"h-10 px-4 py-2",sm:"h-9 rounded-md px-3 text-xs",lg:"h-11 rounded-md px-8",icon:"h-10 w-10",dot:"h-5 w-5 rounded-full p-0 min-h-0 min-w-0"},paywall:{true:"!bg-[var(--paywall)] !text-[var(--paywall-foreground)] !shadow-md hover:!bg-[var(--paywall)]/90 !cursor-not-allowed !border-transparent",false:""}},defaultVariants:{variant:"default",visualSize:"default",paywall:false}});var oe="data-[pressed]:scale-[0.97]";var ne="data-[hovered]:shadow-md";var L="hc:data-[hovered]:outline hc:data-[hovered]:outline-2 hc:data-[hovered]:outline-foreground",_="hc:data-[pressed]:outline hc:data-[pressed]:outline-2 hc:data-[pressed]:outline-offset-1 hc:data-[pressed]:outline-foreground";var Le=react.createContext(null);Le.displayName="ButtonGroupContext";function _e(){return react.useContext(Le)}var Me=react.createContext(null);Me.displayName="ButtonGroupItemContext";function Ge(){return react.useContext(Me)}classVarianceAuthority.cva("inline-flex items-center gap-0",{variants:{orientation:{horizontal:"flex-row",vertical:"flex-col w-full"}},defaultVariants:{orientation:"horizontal"}});var ze=classVarianceAuthority.cva("",{variants:{orientation:{horizontal:"min-w-[44px]",vertical:"flex min-h-[44px]"},position:{first:"",middle:"",last:"",only:""}},compoundVariants:[{orientation:"horizontal",position:"first",className:"rounded-r-none border-r-0"},{orientation:"horizontal",position:"middle",className:"rounded-none border-r-0"},{orientation:"horizontal",position:"last",className:"rounded-l-none"},{orientation:"vertical",position:"first",className:"rounded-b-none border-b-0"},{orientation:"vertical",position:"middle",className:"rounded-none border-b-0"},{orientation:"vertical",position:"last",className:"rounded-t-none"}],defaultVariants:{orientation:"horizontal",position:"only"}});classVarianceAuthority.cva("bg-[var(--border)]",{variants:{orientation:{horizontal:"w-px h-6 mx-1",vertical:"h-px w-full my-1"}},defaultVariants:{orientation:"horizontal"}});var M=react.memo(react.forwardRef(({className:e,buttonVisualClassName:t,variant:a,size:r,visualSize:o,fullWidth:n,loading:s=false,loadingText:u="Loading...",shortcut:p,children:m,isDisabled:f,paywall:c=false,paywallRedirect:g,paywallDescription:P,onPress:w,...x},je)=>{let De=react.useId(),E=_e(),Ne=Ge(),Ze=a??E?.variant??"default",Je=r??E?.size,Ke=f??E?.isDisabled??false,we=E?.orientation==="vertical",Ve=n||we,Qe=Ne?ze({orientation:E?.orientation??"horizontal",position:Ne.position}):"",q=o??Je??"default";return process.env.NODE_ENV!=="production"&&(q==="dot"||q==="icon")&&!x["aria-label"]&&!m&&console.warn('[Button] visualSize="dot" or "icon" requires aria-label when no visible text is provided (WCAG 1.1.1)'),jsxRuntime.jsx(reactAriaComponents.Button,{ref:je,isDisabled:Ke||s||void 0,"aria-disabled":c?true:void 0,"aria-describedby":c?De:void 0,onPress:R=>{if(c){g&&window.open(g,"_blank","noopener,noreferrer");return}w?.(R);},className:l(ae({fullWidth:Ve,inVerticalGroup:we}),e),...x,children:R=>jsxRuntime.jsxs("span",{className:l(re({variant:Ze,visualSize:q,paywall:c,fullWidth:Ve}),Qe,t,oe,ne,L,_),"data-pressed":R.isPressed||void 0,children:[s&&jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsx(lucideReact.Loader2,{className:"motion-safe:animate-spin","aria-hidden":"true"}),jsxRuntime.jsx("span",{className:"sr-only","aria-live":"polite",children:u})]}),!s&&m,c&&jsxRuntime.jsx(lucideReact.Zap,{"data-testid":"zap-icon","aria-hidden":"true",className:"ml-1"}),c&&jsxRuntime.jsxs("span",{id:De,className:"sr-only",children:["Premium feature: ",P||"Upgrade required to access this feature"]}),R.isFocusVisible&&p&&jsxRuntime.jsx("kbd",{className:"ml-auto hidden text-xs opacity-60 lg:inline",children:p})]})})}));M.displayName="Button";var Oe=classVarianceAuthority.cva("flex items-center flex-wrap",{variants:{},defaultVariants:{}}),$e=classVarianceAuthority.cva("flex flex-wrap items-center gap-1 break-words text-sm text-[var(--menu-muted)]",{variants:{},defaultVariants:{}}),G=classVarianceAuthority.cva("inline-flex items-center gap-1",{variants:{},defaultVariants:{}}),de=classVarianceAuthority.cva("inline-flex items-center gap-2 min-h-[44px] pr-2 rounded-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:ring-offset-2",{variants:{state:{default:["text-[var(--menu-muted)]","hover:text-[var(--content-foreground)]","hover:underline underline-offset-4"].join(" "),current:"text-[var(--content-foreground)] font-medium pointer-events-none",disabled:"text-[var(--menu-muted)] opacity-50 cursor-not-allowed pointer-events-none"}},defaultVariants:{state:"default"}}),me=classVarianceAuthority.cva("text-[var(--menu-muted)]",{variants:{type:{chevron:"[&>svg]:h-4 [&>svg]:w-4",slash:"mx-2",custom:""}},defaultVariants:{type:"chevron"}}),Nt=classVarianceAuthority.cva("inline-flex items-center justify-center min-h-[44px] min-w-[44px] rounded-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:ring-offset-2 hover:bg-[var(--accent)]",{variants:{},defaultVariants:{}});function Ye(e,t){return e===t-1}function wt(e,t,a,r){if(e.length<=t||a+r>=e.length)return null;let o=e.slice(0,a),n=e.slice(-r),s=e.slice(a,e.length-r);return {before:o,collapsed:s,after:n}}function z({separator:e}){return e==="chevron"?jsxRuntime.jsx("li",{role:"presentation","aria-hidden":"true",className:me({type:"chevron"}),children:jsxRuntime.jsx(lucideReact.ChevronRight,{})}):e==="slash"?jsxRuntime.jsx("li",{role:"presentation","aria-hidden":"true",className:me({type:"slash"}),children:"/"}):jsxRuntime.jsx("li",{role:"presentation","aria-hidden":"true",className:me({type:"custom"}),children:e})}function ce({item:e,isCurrent:t,linkComponent:a="a",isGroupDisabled:r}){let o=r||e.isDisabled,n=t?"current":o?"disabled":"default";return t?jsxRuntime.jsxs("span",{className:de({state:"current"}),"aria-current":"page",children:[e.icon&&jsxRuntime.jsx("span",{"aria-hidden":"true",children:e.icon}),e.label]}):e.href?jsxRuntime.jsxs(a,{href:e.href,className:de({state:n}),"aria-disabled":o||void 0,children:[e.icon&&jsxRuntime.jsx("span",{"aria-hidden":"true",children:e.icon}),e.label]}):jsxRuntime.jsxs("span",{className:de({state:o?"disabled":"default"}),"aria-disabled":o||void 0,children:[e.icon&&jsxRuntime.jsx("span",{"aria-hidden":"true",children:e.icon}),e.label]})}function Vt({items:e,linkComponent:t="a",isGroupDisabled:a}){return jsxRuntime.jsxs(S,{defaultOpen:false,children:[jsxRuntime.jsx(S.Trigger,{children:jsxRuntime.jsx(M,{variant:"ghost",size:"icon",className:Nt(),"aria-label":"More breadcrumbs",children:jsxRuntime.jsx(lucideReact.MoreHorizontal,{className:"h-4 w-4"})})}),jsxRuntime.jsx(S.Menu,{align:"start",side:"bottom",children:e.map(r=>jsxRuntime.jsx(S.Item,{isDisabled:a||r.isDisabled,children:jsxRuntime.jsxs(t,{href:r.href,className:"flex flex-1 items-center gap-2",children:[r.icon&&jsxRuntime.jsx("span",{"aria-hidden":"true",children:r.icon}),r.label]})},r.id))})]})}var O=react.forwardRef(({items:e,separator:t="chevron",maxItems:a=4,itemsBeforeCollapse:r=1,itemsAfterCollapse:o=2,linkComponent:n,isDisabled:s,className:u,"aria-label":p="Breadcrumb",...m},f)=>{if(!e||e.length===0)return null;let c=wt(e,a,r,o);return c?jsxRuntime.jsx("nav",{ref:f,"aria-label":p,className:l(Oe(),u),...m,children:jsxRuntime.jsxs("ol",{className:$e(),children:[c.before.map(g=>jsxRuntime.jsxs(react.Fragment,{children:[jsxRuntime.jsx("li",{className:G(),children:jsxRuntime.jsx(ce,{item:g,isCurrent:false,linkComponent:n,isGroupDisabled:s})}),jsxRuntime.jsx(z,{separator:t})]},g.id)),jsxRuntime.jsx("li",{className:G(),children:jsxRuntime.jsx(Vt,{items:c.collapsed,linkComponent:n,isGroupDisabled:s})}),jsxRuntime.jsx(z,{separator:t}),c.after.map((g,P)=>{let w=P===c.after.length-1,x=e.length-c.after.length+P;return jsxRuntime.jsxs(react.Fragment,{children:[jsxRuntime.jsx("li",{className:G(),children:jsxRuntime.jsx(ce,{item:g,isCurrent:Ye(x,e.length),linkComponent:n,isGroupDisabled:s})}),!w&&jsxRuntime.jsx(z,{separator:t})]},g.id)})]})}):jsxRuntime.jsx("nav",{ref:f,"aria-label":p,className:l(Oe(),u),...m,children:jsxRuntime.jsx("ol",{className:$e(),children:e.map((g,P)=>jsxRuntime.jsxs(react.Fragment,{children:[jsxRuntime.jsx("li",{className:G(),children:jsxRuntime.jsx(ce,{item:g,isCurrent:Ye(P,e.length),linkComponent:n,isGroupDisabled:s})}),P<e.length-1&&jsxRuntime.jsx(z,{separator:t})]},g.id))})})});O.displayName="Breadcrumbs";var ue=classVarianceAuthority.cva("flex flex-col gap-5 border-b border-[var(--border)] p-4",{variants:{variant:{default:"bg-[var(--content-background)]",feature:["bg-[linear-gradient(to_right,var(--primary),color-mix(in_oklch,var(--primary),black_60%))]","rtl:bg-[linear-gradient(to_left,var(--primary),color-mix(in_oklch,var(--primary),black_60%))]"].join(" ")}},defaultVariants:{variant:"default"}}),fe=classVarianceAuthority.cva("flex w-full flex-col gap-5 sm:flex-row sm:items-center sm:justify-between"),ge=classVarianceAuthority.cva("flex min-w-0 flex-1 flex-col gap-2"),be=classVarianceAuthority.cva("text-[28px] font-semibold leading-none tracking-tight",{variants:{variant:{default:"text-[var(--content-foreground)]",feature:"text-[var(--primary-foreground)]"}},defaultVariants:{variant:"default"}}),ve=classVarianceAuthority.cva("text-base leading-normal",{variants:{variant:{default:"text-[var(--menu-muted)]",feature:"text-[var(--primary-foreground)]"}},defaultVariants:{variant:"default"}}),xe=classVarianceAuthority.cva("flex w-full",{variants:{variant:{default:"",feature:["[&_a]:text-[var(--primary-foreground)]","[&_a:hover]:text-[var(--primary-foreground)]","[&_[aria-current=page]]:text-[var(--primary-foreground)]","[&_[role=presentation]]:text-[var(--primary-foreground)]","[&_:focus-visible]:ring-[var(--primary-foreground)]"].join(" ")}},defaultVariants:{variant:"default"}}),Te=classVarianceAuthority.cva("flex shrink-0 flex-wrap items-center gap-2"),Pe=classVarianceAuthority.cva("flex w-full");var $=react.forwardRef(function({items:t,linkComponent:a,className:r,"data-testid":o,"aria-label":n},s){let{variant:u}=v();return jsxRuntime.jsx("div",{ref:s,className:l(xe({variant:u}),r),"data-testid":o??"page-title-breadcrumbs",children:jsxRuntime.jsx(O,{items:t,linkComponent:a,"aria-label":n})})});$.displayName="PageTitle.Breadcrumbs";var Y=react.forwardRef(function({as:t="h1",children:a,className:r,id:o,"data-testid":n},s){let{variant:u}=v();react.useEffect(()=>{if(process.env.NODE_ENV!=="production"&&t==="h1"){let f=document.querySelectorAll("h1").length;f>1&&console.warn(`[PageTitle] Multiple <h1> elements detected on the page (${f}). Each page should have exactly one <h1>. Pass \`as="h2"\` when PageTitle is not the primary page heading.`);}},[t]);let p=l(be({variant:u}),r),m={ref:s,id:o,className:p,"data-testid":n??"page-title-heading"};return t==="h2"?jsxRuntime.jsx("h2",{...m,children:a}):t==="h3"?jsxRuntime.jsx("h3",{...m,children:a}):jsxRuntime.jsx("h1",{...m,children:a})});Y.displayName="PageTitle.Heading";var F=react.forwardRef(function({children:t,className:a,id:r,"data-testid":o},n){let{variant:s}=v();return jsxRuntime.jsx("p",{ref:n,id:r,className:l(ve({variant:s}),a),"data-testid":o??"page-title-description",children:t})});F.displayName="PageTitle.Description";var W=react.forwardRef(function({children:t,className:a,"data-testid":r},o){return v(),jsxRuntime.jsx("div",{ref:o,className:l(Te(),a),"data-testid":r??"page-title-actions",children:t})});W.displayName="PageTitle.Actions";var U=react.forwardRef(function({children:t,className:a,"data-testid":r},o){return v(),jsxRuntime.jsx("div",{ref:o,className:l(Pe(),a),"data-testid":r??"page-title-tabs",children:t})});U.displayName="PageTitle.Tabs";var zt={"PageTitle.Breadcrumbs":"breadcrumbs","PageTitle.Heading":"heading","PageTitle.Description":"description","PageTitle.Actions":"actions","PageTitle.Tabs":"tabs"};function Ot(e){let t={breadcrumbs:null,heading:null,description:null,actions:null,tabs:null,other:[]};return react.Children.forEach(e,a=>{if(!react.isValidElement(a)){a!=null&&a!==false&&t.other.push(a);return}let r=a.type,o=typeof r=="string"?void 0:r?.displayName,n=o?zt[o]:void 0;n?t[n]=a:t.other.push(a);}),t}var We=react.forwardRef(function({children:t,variant:a="default",className:r,id:o,"aria-label":n,"aria-labelledby":s,"data-testid":u="page-title"},p){let m=Ot(t),f=react.useMemo(()=>({variant:a}),[a]);return jsxRuntime.jsx(j.Provider,{value:f,children:jsxRuntime.jsxs("header",{ref:p,id:o,className:l(ue({variant:a}),r),"aria-label":n,"aria-labelledby":s,"data-testid":u,"data-page-title-variant":a,children:[m.breadcrumbs,jsxRuntime.jsxs("div",{className:fe(),children:[jsxRuntime.jsxs("div",{className:ge(),children:[m.heading,m.description,m.other]}),m.actions]}),m.tabs]})})});We.displayName="PageTitle";var $t=Object.assign(We,{Breadcrumbs:$,Heading:Y,Description:F,Actions:W,Tabs:U});var Ue=zod.z.enum(["default","feature"]),qe=zod.z.enum(["h1","h2","h3"]),Ft=h.extend({children:zod.z.custom(),variant:Ue.optional()}),Wt=h.extend({children:zod.z.custom(),as:qe.optional()}),Ut=h.extend({children:zod.z.custom()}),qt=h.extend({items:zod.z.array(Z),linkComponent:zod.z.custom().optional()}),jt=h.extend({children:zod.z.custom()}),Zt=h.extend({children:zod.z.custom()});exports.PageTitle=$t;exports.PageTitleActions=W;exports.PageTitleActionsPropsSchema=jt;exports.PageTitleBreadcrumbs=$;exports.PageTitleBreadcrumbsPropsSchema=qt;exports.PageTitleDescription=F;exports.PageTitleDescriptionPropsSchema=Ut;exports.PageTitleHeading=Y;exports.PageTitleHeadingLevelSchema=qe;exports.PageTitleHeadingPropsSchema=Wt;exports.PageTitlePropsSchema=Ft;exports.PageTitleTabs=U;exports.PageTitleTabsPropsSchema=Zt;exports.PageTitleVariantSchema=Ue;exports.pageTitleActionsVariants=Te;exports.pageTitleBodyVariants=ge;exports.pageTitleBreadcrumbsVariants=xe;exports.pageTitleDescriptionVariants=ve;exports.pageTitleHeadingVariants=be;exports.pageTitleRootVariants=ue;exports.pageTitleRowVariants=fe;exports.pageTitleTabsVariants=Pe;exports.usePageTitleContext=v;//# sourceMappingURL=index.js.map
|
|
2
|
+
'use strict';var react=require('react'),clsx=require('clsx'),tailwindMerge=require('tailwind-merge'),zod=require('zod'),classVarianceAuthority=require('class-variance-authority'),lucideReact=require('lucide-react'),reactAriaComponents=require('react-aria-components'),jsxRuntime=require('react/jsx-runtime');function l(...e){return tailwindMerge.twMerge(clsx.clsx(e))}var j=react.createContext(null);function v(){let e=react.useContext(j);if(!e)throw new Error("PageTitle compound components must be used within <PageTitle>");return e}var h=zod.z.object({className:zod.z.string().optional(),children:zod.z.any().optional(),id:zod.z.string().optional(),"aria-label":zod.z.string().optional(),"aria-labelledby":zod.z.string().optional(),"aria-describedby":zod.z.string().optional(),"aria-live":zod.z.enum(["off","polite","assertive"]).optional(),"aria-hidden":zod.z.boolean().optional(),"data-testid":zod.z.string().optional()});var Z=zod.z.object({id:zod.z.union([zod.z.string(),zod.z.number()]),label:zod.z.string(),href:zod.z.string().optional(),icon:zod.z.custom().optional(),isDisabled:zod.z.boolean().optional().default(false)}),rt=zod.z.union([zod.z.literal("chevron"),zod.z.literal("slash"),zod.z.custom()]);h.extend({items:zod.z.array(Z),separator:rt.optional().default("chevron"),maxItems:zod.z.number().int().positive().optional().default(4),itemsBeforeCollapse:zod.z.number().int().nonnegative().optional().default(1),itemsAfterCollapse:zod.z.number().int().positive().optional().default(2),linkComponent:zod.z.custom().optional(),isDisabled:zod.z.boolean().optional().default(false)});var J=classVarianceAuthority.cva("z-50 min-w-[8rem] overflow-hidden rounded-md border border-[var(--menu-border)] bg-[var(--menu-background)] text-[var(--menu-foreground)] shadow-md p-1",{variants:{variant:{default:""}},defaultVariants:{variant:"default"}}),k=classVarianceAuthority.cva("relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 min-h-[44px] data-[focused]:bg-[var(--menu-accent)] data-[focused]:text-[var(--menu-accent-foreground)] data-[pressed]:bg-[var(--menu-accent)] [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",{variants:{variant:{default:""}},defaultVariants:{variant:"default"}});function Q(e){if(typeof e=="function"||typeof e=="object")return e?.displayName}var Ie=react.forwardRef(({defaultOpen:e=false,isOpen:t,onOpenChange:a,children:r},o)=>{if(process.env.NODE_ENV!=="production"){let n=react.Children.toArray(r),s=n.some(p=>react.isValidElement(p)&&(p.type===H||Q(p.type)==="DropdownTrigger")),u=n.some(p=>react.isValidElement(p)&&(p.type===C||Q(p.type)==="DropdownMenu"));if(!s||!u)throw new Error("Dropdown requires exactly one Dropdown.Trigger and one Dropdown.Menu as children")}return jsxRuntime.jsx(reactAriaComponents.MenuTrigger,{defaultOpen:e,isOpen:t,onOpenChange:a,children:r})});Ie.displayName="Dropdown";var H=react.forwardRef(({children:e,...t},a)=>react.isValidElement(e)?react.cloneElement(e,{...t,slot:"trigger"}):jsxRuntime.jsx(reactAriaComponents.Button,{ref:a,...t,children:e}));H.displayName="DropdownTrigger";var C=react.forwardRef(({align:e="start",side:t="bottom",className:a,children:r,...o},n)=>{let s=`${t} ${e}`;return jsxRuntime.jsx(reactAriaComponents.Popover,{placement:s,className:"data-[entering]:motion-safe:animate-in data-[entering]:motion-safe:fade-in-0 data-[entering]:motion-safe:slide-in-from-top-2 data-[entering]:[animation-duration:250ms] data-[entering]:[animation-timing-function:cubic-bezier(0.16,1,0.3,1)] data-[exiting]:motion-safe:animate-out data-[exiting]:motion-safe:fade-out-0 data-[exiting]:motion-safe:slide-out-to-top-2 data-[exiting]:[animation-duration:150ms]",children:jsxRuntime.jsx(reactAriaComponents.Menu,{className:l(J({variant:"default"}),a),...o,children:r})})});C.displayName="DropdownMenu";var X=react.forwardRef(({children:e,onAction:t,isDisabled:a=false,shortcut:r,icon:o,iconRight:n,className:s,"aria-label":u,...p},m)=>{let f=react.Children.toArray(e),c=x=>react.isValidElement(x)&&(x.type===C||Q(x.type)==="DropdownMenu"),g=f.some(c),P=f.filter(x=>!c(x)),w=f.find(c);return g&&w?jsxRuntime.jsxs(reactAriaComponents.SubmenuTrigger,{children:[jsxRuntime.jsxs(reactAriaComponents.MenuItem,{"aria-label":u,className:l(k({variant:"default"}),s),...p,children:[o&&jsxRuntime.jsx("span",{className:"mr-2",children:o}),jsxRuntime.jsx("span",{className:"flex-1",children:P}),jsxRuntime.jsx(lucideReact.ChevronRight,{className:"ml-auto h-4 w-4","aria-hidden":"true"})]}),w]}):jsxRuntime.jsxs(reactAriaComponents.MenuItem,{onAction:t,isDisabled:a,"aria-label":u,className:l(k({variant:"default"}),s),...p,children:[o&&jsxRuntime.jsx("span",{className:"mr-2",children:o}),jsxRuntime.jsx("span",{className:"flex-1",children:e}),n&&jsxRuntime.jsx("span",{className:"ml-auto",children:n}),r&&jsxRuntime.jsx("kbd",{className:"ml-auto pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-[var(--border)] bg-[var(--accent-background)] px-1.5 font-mono text-[10px] font-medium text-[var(--menu-muted)] opacity-100",children:r})]})});X.displayName="DropdownItem";var ee=react.memo(react.forwardRef(({className:e,...t},a)=>jsxRuntime.jsx(reactAriaComponents.Separator,{className:l("-mx-1 my-1 h-px bg-[var(--accent-background)]",e),...t})));ee.displayName="DropdownSeparator";var te=react.memo(react.forwardRef(({children:e,className:t,...a},r)=>jsxRuntime.jsx(reactAriaComponents.Header,{className:l("px-2 py-1.5 text-sm font-semibold text-[var(--menu-muted)]",t),...a,children:e})));te.displayName="DropdownLabel";var S=Object.assign(Ie,{Trigger:H,Menu:C,Item:X,Separator:ee,Label:te});var ae=classVarianceAuthority.cva("inline-flex justify-center min-h-[44px] min-w-[44px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50",{variants:{fullWidth:{true:"w-full",false:""},inVerticalGroup:{true:"items-stretch",false:"items-center"}},defaultVariants:{fullWidth:false,inVerticalGroup:false}}),re=classVarianceAuthority.cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background motion-safe:transition-transform motion-safe:duration-200 motion-safe:[transition-timing-function:cubic-bezier(0.34,1.56,0.64,1)] motion-reduce:transition-none [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 relative cursor-pointer",{variants:{variant:{default:"bg-[var(--primary-action)] text-[var(--primary-action-foreground)] shadow-md hover:bg-[var(--primary-action-hover)] hover:-translate-y-1",destructive:"bg-[var(--destructive-background)] text-[var(--destructive-foreground)] shadow-md hover:bg-[var(--destructive-background-hover,var(--destructive-background))] motion-safe:hover:btn-warn-shake",outline:"border border-[var(--input-border)] bg-[var(--page-background)] hover:bg-[var(--input-border)]",secondary:"bg-[var(--secondary)] text-[var(--secondary-foreground)] shadow-md hover:bg-[var(--secondary-hover,var(--secondary))]",ghost:"hover:bg-[var(--accent)] hover:text-[var(--accent-foreground)]",link:"text-[var(--text-link)] underline underline-offset-4 hover:bg-[var(--accent)] hover:text-[var(--text-link-hover)]"},fullWidth:{true:"w-full",false:""},visualSize:{default:"h-10 px-4 py-2",sm:"h-9 rounded-md px-3 text-xs",lg:"h-11 rounded-md px-8",icon:"h-10 w-10",dot:"h-5 w-5 rounded-full p-0 min-h-0 min-w-0"},paywall:{true:"!bg-[var(--paywall)] !text-[var(--paywall-foreground)] !shadow-md hover:!bg-[var(--paywall)]/90 !cursor-not-allowed !border-transparent",false:""}},defaultVariants:{variant:"default",visualSize:"default",paywall:false}});var oe="data-[pressed]:scale-[0.97]";var ne="data-[hovered]:shadow-md";var L="hc:data-[hovered]:outline hc:data-[hovered]:outline-2 hc:data-[hovered]:outline-foreground",_="hc:data-[pressed]:outline hc:data-[pressed]:outline-2 hc:data-[pressed]:outline-offset-1 hc:data-[pressed]:outline-foreground";var Le=react.createContext(null);Le.displayName="ButtonGroupContext";function _e(){return react.useContext(Le)}var Me=react.createContext(null);Me.displayName="ButtonGroupItemContext";function Ge(){return react.useContext(Me)}classVarianceAuthority.cva("inline-flex items-center gap-0",{variants:{orientation:{horizontal:"flex-row",vertical:"flex-col w-full"}},defaultVariants:{orientation:"horizontal"}});var ze=classVarianceAuthority.cva("",{variants:{orientation:{horizontal:"min-w-[44px]",vertical:"flex min-h-[44px]"},position:{first:"",middle:"",last:"",only:""}},compoundVariants:[{orientation:"horizontal",position:"first",className:"rounded-r-none border-r-0"},{orientation:"horizontal",position:"middle",className:"rounded-none border-r-0"},{orientation:"horizontal",position:"last",className:"rounded-l-none"},{orientation:"vertical",position:"first",className:"rounded-b-none border-b-0"},{orientation:"vertical",position:"middle",className:"rounded-none border-b-0"},{orientation:"vertical",position:"last",className:"rounded-t-none"}],defaultVariants:{orientation:"horizontal",position:"only"}});classVarianceAuthority.cva("bg-[var(--border)]",{variants:{orientation:{horizontal:"w-px h-6 mx-1",vertical:"h-px w-full my-1"}},defaultVariants:{orientation:"horizontal"}});var M=react.memo(react.forwardRef(({className:e,buttonVisualClassName:t,variant:a,size:r,visualSize:o,fullWidth:n,loading:s=false,loadingText:u="Loading...",shortcut:p,children:m,isDisabled:f,paywall:c=false,paywallRedirect:g,paywallDescription:P,onPress:w,...x},je)=>{let De=react.useId(),E=_e(),Ne=Ge(),Ze=a??E?.variant??"default",Je=r??E?.size,Ke=f??E?.isDisabled??false,we=E?.orientation==="vertical",Ve=n||we,Qe=Ne?ze({orientation:E?.orientation??"horizontal",position:Ne.position}):"",q=o??Je??"default";return process.env.NODE_ENV!=="production"&&(q==="dot"||q==="icon")&&!x["aria-label"]&&!m&&console.warn('[Button] visualSize="dot" or "icon" requires aria-label when no visible text is provided (WCAG 1.1.1)'),jsxRuntime.jsx(reactAriaComponents.Button,{ref:je,isDisabled:Ke||s||void 0,"aria-disabled":c?true:void 0,"aria-describedby":c?De:void 0,onPress:R=>{if(c){g&&window.open(g,"_blank","noopener,noreferrer");return}w?.(R);},className:l(ae({fullWidth:Ve,inVerticalGroup:we}),e),...x,children:R=>jsxRuntime.jsxs("span",{className:l(re({variant:Ze,visualSize:q,paywall:c,fullWidth:Ve}),Qe,t,oe,ne,L,_),"data-pressed":R.isPressed||void 0,children:[s&&jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsx(lucideReact.Loader2,{className:"motion-safe:animate-spin","aria-hidden":"true"}),jsxRuntime.jsx("span",{className:"sr-only","aria-live":"polite",children:u})]}),!s&&m,c&&jsxRuntime.jsx(lucideReact.Zap,{"data-testid":"zap-icon","aria-hidden":"true",className:"ml-1"}),c&&jsxRuntime.jsxs("span",{id:De,className:"sr-only",children:["Premium feature: ",P||"Upgrade required to access this feature"]}),R.isFocusVisible&&p&&jsxRuntime.jsx("kbd",{className:"ml-auto hidden text-xs opacity-60 lg:inline",children:p})]})})}));M.displayName="Button";var Oe=classVarianceAuthority.cva("flex items-center flex-wrap",{variants:{},defaultVariants:{}}),$e=classVarianceAuthority.cva("flex flex-wrap items-center gap-1 break-words text-sm text-[var(--menu-muted)]",{variants:{},defaultVariants:{}}),G=classVarianceAuthority.cva("inline-flex items-center gap-1",{variants:{},defaultVariants:{}}),de=classVarianceAuthority.cva("inline-flex items-center gap-2 min-h-[44px] pr-2 rounded-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:ring-offset-2",{variants:{state:{default:["text-[var(--menu-muted)]","hover:text-[var(--content-foreground)]","hover:underline underline-offset-4"].join(" "),current:"text-[var(--content-foreground)] font-medium pointer-events-none",disabled:"text-[var(--menu-muted)] opacity-50 cursor-not-allowed pointer-events-none"}},defaultVariants:{state:"default"}}),me=classVarianceAuthority.cva("text-[var(--menu-muted)]",{variants:{type:{chevron:"[&>svg]:h-4 [&>svg]:w-4",slash:"mx-2",custom:""}},defaultVariants:{type:"chevron"}}),Nt=classVarianceAuthority.cva("inline-flex items-center justify-center min-h-[44px] min-w-[44px] rounded-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:ring-offset-2 hover:bg-[var(--accent)]",{variants:{},defaultVariants:{}});function Ye(e,t){return e===t-1}function wt(e,t,a,r){if(e.length<=t||a+r>=e.length)return null;let o=e.slice(0,a),n=e.slice(-r),s=e.slice(a,e.length-r);return {before:o,collapsed:s,after:n}}function z({separator:e}){return e==="chevron"?jsxRuntime.jsx("li",{role:"presentation","aria-hidden":"true",className:me({type:"chevron"}),children:jsxRuntime.jsx(lucideReact.ChevronRight,{})}):e==="slash"?jsxRuntime.jsx("li",{role:"presentation","aria-hidden":"true",className:me({type:"slash"}),children:"/"}):jsxRuntime.jsx("li",{role:"presentation","aria-hidden":"true",className:me({type:"custom"}),children:e})}function ce({item:e,isCurrent:t,linkComponent:a="a",isGroupDisabled:r}){let o=r||e.isDisabled,n=t?"current":o?"disabled":"default";return t?jsxRuntime.jsxs("span",{className:de({state:"current"}),"aria-current":"page",children:[e.icon&&jsxRuntime.jsx("span",{"aria-hidden":"true",children:e.icon}),e.label]}):e.href?jsxRuntime.jsxs(a,{href:e.href,className:de({state:n}),"aria-disabled":o||void 0,children:[e.icon&&jsxRuntime.jsx("span",{"aria-hidden":"true",children:e.icon}),e.label]}):jsxRuntime.jsxs("span",{className:de({state:o?"disabled":"default"}),"aria-disabled":o||void 0,children:[e.icon&&jsxRuntime.jsx("span",{"aria-hidden":"true",children:e.icon}),e.label]})}function Vt({items:e,linkComponent:t="a",isGroupDisabled:a}){return jsxRuntime.jsxs(S,{defaultOpen:false,children:[jsxRuntime.jsx(S.Trigger,{children:jsxRuntime.jsx(M,{variant:"ghost",size:"icon",className:Nt(),"aria-label":"More breadcrumbs",children:jsxRuntime.jsx(lucideReact.MoreHorizontal,{className:"h-4 w-4"})})}),jsxRuntime.jsx(S.Menu,{align:"start",side:"bottom",children:e.map(r=>jsxRuntime.jsx(S.Item,{isDisabled:a||r.isDisabled,children:jsxRuntime.jsxs(t,{href:r.href,className:"flex flex-1 items-center gap-2",children:[r.icon&&jsxRuntime.jsx("span",{"aria-hidden":"true",children:r.icon}),r.label]})},r.id))})]})}var O=react.forwardRef(({items:e,separator:t="chevron",maxItems:a=4,itemsBeforeCollapse:r=1,itemsAfterCollapse:o=2,linkComponent:n,isDisabled:s,className:u,"aria-label":p="Breadcrumb",...m},f)=>{if(!e||e.length===0)return null;let c=wt(e,a,r,o);return c?jsxRuntime.jsx("nav",{ref:f,"aria-label":p,className:l(Oe(),u),...m,children:jsxRuntime.jsxs("ol",{className:$e(),children:[c.before.map(g=>jsxRuntime.jsxs(react.Fragment,{children:[jsxRuntime.jsx("li",{className:G(),children:jsxRuntime.jsx(ce,{item:g,isCurrent:false,linkComponent:n,isGroupDisabled:s})}),jsxRuntime.jsx(z,{separator:t})]},g.id)),jsxRuntime.jsx("li",{className:G(),children:jsxRuntime.jsx(Vt,{items:c.collapsed,linkComponent:n,isGroupDisabled:s})}),jsxRuntime.jsx(z,{separator:t}),c.after.map((g,P)=>{let w=P===c.after.length-1,x=e.length-c.after.length+P;return jsxRuntime.jsxs(react.Fragment,{children:[jsxRuntime.jsx("li",{className:G(),children:jsxRuntime.jsx(ce,{item:g,isCurrent:Ye(x,e.length),linkComponent:n,isGroupDisabled:s})}),!w&&jsxRuntime.jsx(z,{separator:t})]},g.id)})]})}):jsxRuntime.jsx("nav",{ref:f,"aria-label":p,className:l(Oe(),u),...m,children:jsxRuntime.jsx("ol",{className:$e(),children:e.map((g,P)=>jsxRuntime.jsxs(react.Fragment,{children:[jsxRuntime.jsx("li",{className:G(),children:jsxRuntime.jsx(ce,{item:g,isCurrent:Ye(P,e.length),linkComponent:n,isGroupDisabled:s})}),P<e.length-1&&jsxRuntime.jsx(z,{separator:t})]},g.id))})})});O.displayName="Breadcrumbs";var ue=classVarianceAuthority.cva("flex flex-col gap-4 border-b border-[var(--border)] p-4",{variants:{variant:{default:"bg-[var(--content-background)]",feature:["bg-[linear-gradient(to_right,var(--primary),color-mix(in_oklch,var(--primary),black_60%))]","rtl:bg-[linear-gradient(to_left,var(--primary),color-mix(in_oklch,var(--primary),black_60%))]"].join(" ")}},defaultVariants:{variant:"default"}}),fe=classVarianceAuthority.cva("flex w-full flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"),ge=classVarianceAuthority.cva("flex min-w-0 flex-1 flex-col gap-2"),be=classVarianceAuthority.cva("text-2xl font-semibold leading-none tracking-tight",{variants:{variant:{default:"text-[var(--content-foreground)]",feature:"text-[var(--primary-foreground)]"}},defaultVariants:{variant:"default"}}),ve=classVarianceAuthority.cva("text-base leading-normal",{variants:{variant:{default:"text-[var(--menu-muted)]",feature:"text-[var(--primary-foreground)]"}},defaultVariants:{variant:"default"}}),xe=classVarianceAuthority.cva("flex w-full",{variants:{variant:{default:"",feature:["[&_a]:text-[var(--primary-foreground)]","[&_a:hover]:text-[var(--primary-foreground)]","[&_[aria-current=page]]:text-[var(--primary-foreground)]","[&_[role=presentation]]:text-[var(--primary-foreground)]","[&_:focus-visible]:ring-[var(--primary-foreground)]"].join(" ")}},defaultVariants:{variant:"default"}}),Te=classVarianceAuthority.cva("flex shrink-0 flex-wrap items-center gap-2"),Pe=classVarianceAuthority.cva("flex w-full");var $=react.forwardRef(function({items:t,linkComponent:a,className:r,"data-testid":o,"aria-label":n},s){let{variant:u}=v();return jsxRuntime.jsx("div",{ref:s,className:l(xe({variant:u}),r),"data-testid":o??"page-title-breadcrumbs",children:jsxRuntime.jsx(O,{items:t,linkComponent:a,"aria-label":n})})});$.displayName="PageTitle.Breadcrumbs";var Y=react.forwardRef(function({as:t="h1",children:a,className:r,id:o,"data-testid":n},s){let{variant:u}=v();react.useEffect(()=>{if(process.env.NODE_ENV!=="production"&&t==="h1"){let f=document.querySelectorAll("h1").length;f>1&&console.warn(`[PageTitle] Multiple <h1> elements detected on the page (${f}). Each page should have exactly one <h1>. Pass \`as="h2"\` when PageTitle is not the primary page heading.`);}},[t]);let p=l(be({variant:u}),r),m={ref:s,id:o,className:p,"data-testid":n??"page-title-heading"};return t==="h2"?jsxRuntime.jsx("h2",{...m,children:a}):t==="h3"?jsxRuntime.jsx("h3",{...m,children:a}):jsxRuntime.jsx("h1",{...m,children:a})});Y.displayName="PageTitle.Heading";var F=react.forwardRef(function({children:t,className:a,id:r,"data-testid":o},n){let{variant:s}=v();return jsxRuntime.jsx("p",{ref:n,id:r,className:l(ve({variant:s}),a),"data-testid":o??"page-title-description",children:t})});F.displayName="PageTitle.Description";var W=react.forwardRef(function({children:t,className:a,"data-testid":r},o){return v(),jsxRuntime.jsx("div",{ref:o,className:l(Te(),a),"data-testid":r??"page-title-actions",children:t})});W.displayName="PageTitle.Actions";var U=react.forwardRef(function({children:t,className:a,"data-testid":r},o){return v(),jsxRuntime.jsx("div",{ref:o,className:l(Pe(),a),"data-testid":r??"page-title-tabs",children:t})});U.displayName="PageTitle.Tabs";var zt={"PageTitle.Breadcrumbs":"breadcrumbs","PageTitle.Heading":"heading","PageTitle.Description":"description","PageTitle.Actions":"actions","PageTitle.Tabs":"tabs"};function Ot(e){let t={breadcrumbs:null,heading:null,description:null,actions:null,tabs:null,other:[]};return react.Children.forEach(e,a=>{if(!react.isValidElement(a)){a!=null&&a!==false&&t.other.push(a);return}let r=a.type,o=typeof r=="string"?void 0:r?.displayName,n=o?zt[o]:void 0;n?t[n]=a:t.other.push(a);}),t}var We=react.forwardRef(function({children:t,variant:a="default",className:r,id:o,"aria-label":n,"aria-labelledby":s,"data-testid":u="page-title"},p){let m=Ot(t),f=react.useMemo(()=>({variant:a}),[a]);return jsxRuntime.jsx(j.Provider,{value:f,children:jsxRuntime.jsxs("header",{ref:p,id:o,className:l(ue({variant:a}),r),"aria-label":n,"aria-labelledby":s,"data-testid":u,"data-page-title-variant":a,children:[m.breadcrumbs,jsxRuntime.jsxs("div",{className:fe(),children:[jsxRuntime.jsxs("div",{className:ge(),children:[m.heading,m.description,m.other]}),m.actions]}),m.tabs]})})});We.displayName="PageTitle";var $t=Object.assign(We,{Breadcrumbs:$,Heading:Y,Description:F,Actions:W,Tabs:U});var Ue=zod.z.enum(["default","feature"]),qe=zod.z.enum(["h1","h2","h3"]),Ft=h.extend({children:zod.z.custom(),variant:Ue.optional()}),Wt=h.extend({children:zod.z.custom(),as:qe.optional()}),Ut=h.extend({children:zod.z.custom()}),qt=h.extend({items:zod.z.array(Z),linkComponent:zod.z.custom().optional()}),jt=h.extend({children:zod.z.custom()}),Zt=h.extend({children:zod.z.custom()});exports.PageTitle=$t;exports.PageTitleActions=W;exports.PageTitleActionsPropsSchema=jt;exports.PageTitleBreadcrumbs=$;exports.PageTitleBreadcrumbsPropsSchema=qt;exports.PageTitleDescription=F;exports.PageTitleDescriptionPropsSchema=Ut;exports.PageTitleHeading=Y;exports.PageTitleHeadingLevelSchema=qe;exports.PageTitleHeadingPropsSchema=Wt;exports.PageTitlePropsSchema=Ft;exports.PageTitleTabs=U;exports.PageTitleTabsPropsSchema=Zt;exports.PageTitleVariantSchema=Ue;exports.pageTitleActionsVariants=Te;exports.pageTitleBodyVariants=ge;exports.pageTitleBreadcrumbsVariants=xe;exports.pageTitleDescriptionVariants=ve;exports.pageTitleHeadingVariants=be;exports.pageTitleRootVariants=ue;exports.pageTitleRowVariants=fe;exports.pageTitleTabsVariants=Pe;exports.usePageTitleContext=v;//# sourceMappingURL=index.js.map
|
|
3
3
|
//# sourceMappingURL=index.js.map
|