@tribepad/themis 1.0.7 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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/Chart/ChartContext.d.ts.map +1 -1
- package/dist/elements/Chart/ChartLineSeries.d.ts.map +1 -1
- package/dist/elements/Chart/index.js +1 -1
- package/dist/elements/Chart/index.js.map +1 -1
- package/dist/elements/Chart/index.mjs +1 -1
- package/dist/elements/Chart/index.mjs.map +1 -1
- package/dist/elements/DatePicker/index.js +1 -1
- package/dist/elements/DatePicker/index.js.map +1 -1
- package/dist/elements/DatePicker/index.mjs +1 -1
- package/dist/elements/DatePicker/index.mjs.map +1 -1
- package/dist/elements/FileField/index.js +1 -1
- package/dist/elements/FileField/index.js.map +1 -1
- package/dist/elements/FileField/index.mjs +1 -1
- package/dist/elements/FileField/index.mjs.map +1 -1
- package/dist/elements/Modal/Modal.styles.d.ts +2 -0
- package/dist/elements/Modal/Modal.styles.d.ts.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/NumberField/NumberField.d.ts.map +1 -1
- package/dist/elements/NumberField/NumberField.types.d.ts +12 -0
- package/dist/elements/NumberField/NumberField.types.d.ts.map +1 -1
- package/dist/elements/NumberField/index.js +1 -1
- package/dist/elements/NumberField/index.js.map +1 -1
- package/dist/elements/NumberField/index.mjs +1 -1
- package/dist/elements/NumberField/index.mjs.map +1 -1
- package/dist/elements/OTPInput/OTPInput.d.ts +1 -1
- package/dist/elements/Resizable/index.js +1 -1
- package/dist/elements/Resizable/index.js.map +1 -1
- package/dist/elements/Resizable/index.mjs +1 -1
- package/dist/elements/Resizable/index.mjs.map +1 -1
- package/dist/elements/Switch/Switch.d.ts +11 -4
- package/dist/elements/Switch/Switch.d.ts.map +1 -1
- package/dist/elements/Switch/Switch.types.d.ts +5 -0
- package/dist/elements/Switch/Switch.types.d.ts.map +1 -1
- package/dist/elements/Switch/index.js +1 -1
- package/dist/elements/Switch/index.js.map +1 -1
- package/dist/elements/Switch/index.mjs +1 -1
- package/dist/elements/Switch/index.mjs.map +1 -1
- package/dist/elements/Tabs/Tabs.d.ts +1 -1
- package/dist/elements/Tabs/Tabs.d.ts.map +1 -1
- package/dist/elements/Tabs/Tabs.types.d.ts +1 -0
- package/dist/elements/Tabs/Tabs.types.d.ts.map +1 -1
- package/dist/elements/Tabs/index.js +1 -1
- package/dist/elements/Tabs/index.js.map +1 -1
- package/dist/elements/Tabs/index.mjs +1 -1
- package/dist/elements/Tabs/index.mjs.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/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/index.mjs +2 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/hooks/useReducedMotion.d.ts +9 -0
- package/dist/hooks/useReducedMotion.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/styles/animations.css +172 -0
- package/dist/styles/index.js +1 -1
- package/dist/styles/index.js.map +1 -1
- package/dist/styles/index.mjs +1 -1
- package/dist/styles/index.mjs.map +1 -1
- package/dist/styles/shared-variants.d.ts +23 -0
- package/dist/styles/shared-variants.d.ts.map +1 -1
- package/dist/tailwind-source.css +1 -0
- package/dist/types/animation.d.ts +24 -0
- package/dist/types/animation.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/index.mjs +2 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs +1 -1
- package/dist/utils/index.mjs.map +1 -1
- package/dist/utils/shouldAnimate.d.ts +12 -0
- package/dist/utils/shouldAnimate.d.ts.map +1 -0
- package/package.json +34 -2
- package/src/elements/NumberField/NumberField.stories.tsx +100 -0
- package/src/elements/Switch/Switch.stories.tsx +60 -0
- package/src/elements/Tabs/Tabs.stories.tsx +101 -0
|
@@ -3,6 +3,7 @@ import { type VariantProps } from 'class-variance-authority';
|
|
|
3
3
|
* CVA Variants for Modal.Content
|
|
4
4
|
*
|
|
5
5
|
* Size and animation variant definitions for the modal content container.
|
|
6
|
+
* Uses React Aria data-[entering]/data-[exiting] attributes for enter+exit animations.
|
|
6
7
|
* Uses CSS custom properties for all colours to support theming.
|
|
7
8
|
*
|
|
8
9
|
* @see Modal.types.ts (ModalSize, ModalAnimation)
|
|
@@ -15,6 +16,7 @@ export declare const modalContentVariants: (props?: ({
|
|
|
15
16
|
* CVA Variants for Modal Overlay
|
|
16
17
|
*
|
|
17
18
|
* Animation variant definitions for the modal backdrop overlay.
|
|
19
|
+
* Uses React Aria data-[entering]/data-[exiting] attributes for enter+exit animations.
|
|
18
20
|
* Uses CSS custom properties for all colours to support theming.
|
|
19
21
|
*/
|
|
20
22
|
export declare const modalOverlayVariants: (props?: ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Modal.styles.d.ts","sourceRoot":"","sources":["../../../src/elements/Modal/Modal.styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"Modal.styles.d.ts","sourceRoot":"","sources":["../../../src/elements/Modal/Modal.styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAWlE;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB;;;8EAqDhC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB;;8EA2BhC,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAAG,YAAY,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACjF,MAAM,MAAM,wBAAwB,GAAG,YAAY,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
'use strict';var react=require('react'),reactAriaComponents=require('react-aria-components'),lucideReact=require('lucide-react'),clsx=require('clsx'),tailwindMerge=require('tailwind-merge'),classVarianceAuthority=require('class-variance-authority'),jsxRuntime=require('react/jsx-runtime');function d(...e){return tailwindMerge.twMerge(clsx.clsx(e))}var
|
|
2
|
+
'use strict';var react=require('react'),reactAriaComponents=require('react-aria-components'),lucideReact=require('lucide-react'),clsx=require('clsx'),tailwindMerge=require('tailwind-merge'),classVarianceAuthority=require('class-variance-authority'),jsxRuntime=require('react/jsx-runtime');function d(...e){return tailwindMerge.twMerge(clsx.clsx(e))}var R=["data-[entering]:animate-in","data-[entering]:fade-in-0","data-[entering]:zoom-in-95","data-[entering]:duration-200","data-[entering]:ease-out"],S=["data-[exiting]:animate-out","data-[exiting]:fade-out-0","data-[exiting]:zoom-out-95","data-[exiting]:duration-150","data-[exiting]:ease-in"],b=["data-[entering]:animate-in","data-[entering]:fade-in-0","data-[entering]:slide-in-from-bottom-4","data-[entering]:duration-200","data-[entering]:ease-out"],V=["data-[exiting]:animate-out","data-[exiting]:fade-out-0","data-[exiting]:slide-out-to-bottom-4","data-[exiting]:duration-150","data-[exiting]:ease-in"],g=["data-[entering]:animate-in","data-[entering]:fade-in-0","data-[entering]:duration-200"],u=["data-[exiting]:animate-out","data-[exiting]:fade-out-0","data-[exiting]:duration-150"],N=["motion-reduce:transition-none","motion-reduce:animate-none"];var f=classVarianceAuthority.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",...N],{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":[...R,...S],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:[...b,...V],none:""}},defaultVariants:{size:"md",animation:"fade-zoom"}}),x=classVarianceAuthority.cva(["fixed","inset-0","z-50","flex","items-center","justify-center","bg-black/50","backdrop-blur-sm",...N],{variants:{animation:{"fade-zoom":[...g,...u],fade:[...g,...u],slide:[...g,...u],none:""}},defaultVariants:{animation:"fade-zoom"}});var h=react.createContext(void 0);function H({children:e,defaultOpen:t,isOpen:n,onOpenChange:i,role:l="dialog"}){let s=react.Children.toArray(e);if(process.env.NODE_ENV!=="production"){let a=s.some(r=>react.isValidElement(r)&&(r.type===p||r.type.displayName==="ModalTrigger")),v=s.some(r=>react.isValidElement(r)&&(r.type===m||r.type.displayName==="ModalContent"));if(!a||!v)throw new Error("Modal requires exactly one Modal.Trigger and one Modal.Content as children")}let M=s.find(a=>react.isValidElement(a)&&(a.type===p||a.type.displayName==="ModalTrigger")),y=s.find(a=>react.isValidElement(a)&&(a.type===m||a.type.displayName==="ModalContent")),O=M,E=react.isValidElement(O)?O.props.children:null,A=y;return jsxRuntime.jsx(h.Provider,{value:{role:l},children:jsxRuntime.jsxs(reactAriaComponents.DialogTrigger,{defaultOpen:t,isOpen:n,onOpenChange:i,children:[E,A]})})}H.displayName="Modal";function p({children:e}){return e}p.displayName="ModalTrigger";function m({children:e,size:t="md",animation:n="fade-zoom",animationDuration:i=200,isDismissable:l=true,isKeyboardDismissDisabled:s=false,showClose:M=true,className:y}){let E=react.useContext(h)?.role??"dialog",A=x({animation:n}),a=f({size:t,animation:n}),v=d(a,y);return jsxRuntime.jsx(reactAriaComponents.ModalOverlay,{isDismissable:l,isKeyboardDismissDisabled:s,className:A,children:jsxRuntime.jsx(reactAriaComponents.Modal,{className:v,style:{transitionDuration:`${i}ms`},children:jsxRuntime.jsx(reactAriaComponents.Dialog,{role:E,className:"outline-none",children:({close:r})=>jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[M&&jsxRuntime.jsx(reactAriaComponents.Button,{onPress:r,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:jsxRuntime.jsx(lucideReact.X,{className:"h-4 w-4","aria-hidden":"true"})}),e]})})})})}m.displayName="ModalContent";function C(e){return null}C.displayName="ModalOverlay";function T({children:e,className:t}){return jsxRuntime.jsx("div",{className:d("flex flex-col space-y-1.5 text-center sm:text-left",t),children:e})}T.displayName="ModalHeader";function _({children:e,as:t="h2",className:n}){return jsxRuntime.jsx(reactAriaComponents.Heading,{slot:"title",level:parseInt(t[1]??"2"),className:d("text-lg font-semibold leading-none tracking-tight",n),children:e})}_.displayName="ModalTitle";function I({children:e,className:t}){return jsxRuntime.jsx("p",{slot:"description",className:d("text-sm text-[var(--menu-muted)]",t),children:e})}I.displayName="ModalDescription";function D({children:e,className:t}){return jsxRuntime.jsx("div",{className:d("flex flex-col-reverse sm:flex-row sm:justify-end gap-2",t),children:e})}D.displayName="ModalFooter";function P({children:e}){let t=react.useContext(reactAriaComponents.OverlayTriggerStateContext);if(!t)throw new Error("Modal.Close must be used inside Modal.Content");if(!react.isValidElement(e))throw new Error("Modal.Close requires a valid React element as a child");let n=e,i=()=>{t.close();},l=n.props?.onPress;return react.cloneElement(n,{onPress:l?()=>{l(),i();}:i})}P.displayName="ModalClose";var $=Object.assign(H,{Trigger:p,Content:m,Overlay:C,Header:T,Title:_,Description:I,Footer:D,Close:P});exports.Modal=$;exports.ModalClose=P;exports.ModalContent=m;exports.ModalDescription=I;exports.ModalFooter=D;exports.ModalHeader=T;exports.ModalOverlay=C;exports.ModalTitle=_;exports.ModalTrigger=p;exports.modalContentVariants=f;exports.modalOverlayVariants=x;//# sourceMappingURL=index.js.map
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/elements/Modal/Modal.styles.ts","../../../src/elements/Modal/Modal.tsx"],"names":["cn","inputs","twMerge","clsx","modalContentVariants","cva","modalOverlayVariants","ModalContext","createContext","ModalRoot","children","defaultOpen","isOpen","onOpenChange","role","childArray","Children","hasTrigger","child","isValidElement","ModalTrigger","hasContent","ModalContent","triggerChild","contentChild","triggerElement","unwrappedTrigger","unwrappedContent","jsx","jsxs","AriaDialogTrigger","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","import { cva, type VariantProps } from 'class-variance-authority';\n\n/**\n * CVA Variants for Modal.Content\n *\n * Size and animation variant definitions for the modal content container.\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 ],\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': 'animate-fadeIn', // Fade in with scale\n fade: 'animate-fadeIn', // Fade in only\n slide: 'animate-slideUp', // Slide up\n none: '', // No animation\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 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 ],\n {\n variants: {\n animation: {\n 'fade-zoom': 'animate-fadeIn',\n fade: 'animate-fadeIn',\n slide: 'animate-fadeIn',\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 exactly one Modal.Trigger and one Modal.Content.\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 hasTrigger = childArray.some(\n (child) =>\n isValidElement(child) &&\n (child.type === ModalTrigger ||\n (child.type as { displayName?: string }).displayName === 'ModalTrigger')\n );\n\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 (!hasTrigger || !hasContent) {\n throw new Error(\n 'Modal requires exactly one Modal.Trigger and one Modal.Content as children'\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 // Extract the actual children from Modal.Trigger and Modal.Content\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 const unwrappedContent = contentChild as ReactElement;\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-4 top-4 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","hasTrigger","child","isValidElement","ModalTrigger","hasContent","ModalContent","triggerChild","contentChild","triggerElement","unwrappedTrigger","unwrappedContent","jsx","jsxs","AriaDialogTrigger","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-11', // 44px\n lg: 'h-14', // 56px\n} 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 exactly one Modal.Trigger and one Modal.Content.\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 hasTrigger = childArray.some(\n (child) =>\n isValidElement(child) &&\n (child.type === ModalTrigger ||\n (child.type as { displayName?: string }).displayName === 'ModalTrigger')\n );\n\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 (!hasTrigger || !hasContent) {\n throw new Error(\n 'Modal requires exactly one Modal.Trigger and one Modal.Content as children'\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 // Extract the actual children from Modal.Trigger and Modal.Content\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 const unwrappedContent = contentChild as ReactElement;\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,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
|
|
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 d(...e){return twMerge(clsx(e))}var R=["data-[entering]:animate-in","data-[entering]:fade-in-0","data-[entering]:zoom-in-95","data-[entering]:duration-200","data-[entering]:ease-out"],S=["data-[exiting]:animate-out","data-[exiting]:fade-out-0","data-[exiting]:zoom-out-95","data-[exiting]:duration-150","data-[exiting]:ease-in"],b=["data-[entering]:animate-in","data-[entering]:fade-in-0","data-[entering]:slide-in-from-bottom-4","data-[entering]:duration-200","data-[entering]:ease-out"],V=["data-[exiting]:animate-out","data-[exiting]:fade-out-0","data-[exiting]:slide-out-to-bottom-4","data-[exiting]:duration-150","data-[exiting]:ease-in"],g=["data-[entering]:animate-in","data-[entering]:fade-in-0","data-[entering]:duration-200"],u=["data-[exiting]:animate-out","data-[exiting]:fade-out-0","data-[exiting]:duration-150"],N=["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",...N],{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":[...R,...S],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:[...b,...V],none:""}},defaultVariants:{size:"md",animation:"fade-zoom"}}),x=cva(["fixed","inset-0","z-50","flex","items-center","justify-center","bg-black/50","backdrop-blur-sm",...N],{variants:{animation:{"fade-zoom":[...g,...u],fade:[...g,...u],slide:[...g,...u],none:""}},defaultVariants:{animation:"fade-zoom"}});var h=createContext(void 0);function H({children:e,defaultOpen:t,isOpen:n,onOpenChange:i,role:l="dialog"}){let s=Children.toArray(e);if(process.env.NODE_ENV!=="production"){let a=s.some(r=>isValidElement(r)&&(r.type===p||r.type.displayName==="ModalTrigger")),v=s.some(r=>isValidElement(r)&&(r.type===m||r.type.displayName==="ModalContent"));if(!a||!v)throw new Error("Modal requires exactly one Modal.Trigger and one Modal.Content as children")}let M=s.find(a=>isValidElement(a)&&(a.type===p||a.type.displayName==="ModalTrigger")),y=s.find(a=>isValidElement(a)&&(a.type===m||a.type.displayName==="ModalContent")),O=M,E=isValidElement(O)?O.props.children:null,A=y;return jsx(h.Provider,{value:{role:l},children:jsxs(DialogTrigger,{defaultOpen:t,isOpen:n,onOpenChange:i,children:[E,A]})})}H.displayName="Modal";function p({children:e}){return e}p.displayName="ModalTrigger";function m({children:e,size:t="md",animation:n="fade-zoom",animationDuration:i=200,isDismissable:l=true,isKeyboardDismissDisabled:s=false,showClose:M=true,className:y}){let E=useContext(h)?.role??"dialog",A=x({animation:n}),a=f({size:t,animation:n}),v=d(a,y);return jsx(ModalOverlay,{isDismissable:l,isKeyboardDismissDisabled:s,className:A,children:jsx(Modal,{className:v,style:{transitionDuration:`${i}ms`},children:jsx(Dialog,{role:E,className:"outline-none",children:({close:r})=>jsxs(Fragment,{children:[M&&jsx(Button,{onPress:r,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"})}),e]})})})})}m.displayName="ModalContent";function C(e){return null}C.displayName="ModalOverlay";function T({children:e,className:t}){return jsx("div",{className:d("flex flex-col space-y-1.5 text-center sm:text-left",t),children:e})}T.displayName="ModalHeader";function _({children:e,as:t="h2",className:n}){return jsx(Heading,{slot:"title",level:parseInt(t[1]??"2"),className:d("text-lg font-semibold leading-none tracking-tight",n),children:e})}_.displayName="ModalTitle";function I({children:e,className:t}){return jsx("p",{slot:"description",className:d("text-sm text-[var(--menu-muted)]",t),children:e})}I.displayName="ModalDescription";function D({children:e,className:t}){return jsx("div",{className:d("flex flex-col-reverse sm:flex-row sm:justify-end gap-2",t),children:e})}D.displayName="ModalFooter";function P({children:e}){let t=useContext(OverlayTriggerStateContext);if(!t)throw new Error("Modal.Close must be used inside Modal.Content");if(!isValidElement(e))throw new Error("Modal.Close requires a valid React element as a child");let n=e,i=()=>{t.close();},l=n.props?.onPress;return cloneElement(n,{onPress:l?()=>{l(),i();}:i})}P.displayName="ModalClose";var $=Object.assign(H,{Trigger:p,Content:m,Overlay:C,Header:T,Title:_,Description:I,Footer:D,Close:P});export{$ as Modal,P as ModalClose,m as ModalContent,I as ModalDescription,D as ModalFooter,T as ModalHeader,C as ModalOverlay,_ as ModalTitle,p as ModalTrigger,f as modalContentVariants,x as modalOverlayVariants};//# sourceMappingURL=index.mjs.map
|
|
3
3
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/elements/Modal/Modal.styles.ts","../../../src/elements/Modal/Modal.tsx"],"names":["cn","inputs","twMerge","clsx","modalContentVariants","cva","modalOverlayVariants","ModalContext","createContext","ModalRoot","children","defaultOpen","isOpen","onOpenChange","role","childArray","Children","hasTrigger","child","isValidElement","ModalTrigger","hasContent","ModalContent","triggerChild","contentChild","triggerElement","unwrappedTrigger","unwrappedContent","jsx","jsxs","AriaDialogTrigger","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":"sYAcO,SAASA,KAAMC,CAAAA,CAA8B,CAClD,OAAOC,OAAAA,CAAQC,KAAKF,CAAM,CAAC,CAC7B,CCNO,IAAMG,EAAuBC,GAAAA,CAClC,CAEE,UAAA,CACA,gCAAA,CACA,mCACA,YAAA,CACA,WAAA,CACA,KAAA,CACA,QAAA,CACA,eAEA,MAAA,CACA,SACF,CAAA,CACA,CACE,SAAU,CACR,IAAA,CAAM,CACJ,EAAA,CAAI,WACJ,EAAA,CAAI,UAAA,CACJ,EAAA,CAAI,UAAA,CACJ,GAAI,WAAA,CACJ,IAAA,CAAM,sCACR,CAAA,CACA,UAAW,CACT,WAAA,CAAa,gBAAA,CACb,IAAA,CAAM,iBACN,KAAA,CAAO,iBAAA,CACP,IAAA,CAAM,EACR,CACF,CAAA,CACA,eAAA,CAAiB,CACf,IAAA,CAAM,KACN,SAAA,CAAW,WACb,CACF,CACF,EAQaC,CAAAA,CAAuBD,GAAAA,CAClC,CAEE,OAAA,CACA,UACA,MAAA,CACA,MAAA,CACA,cAAA,CACA,gBAAA,CACA,cACA,kBACF,CAAA,CACA,CACE,QAAA,CAAU,CACR,SAAA,CAAW,CACT,WAAA,CAAa,gBAAA,CACb,KAAM,gBAAA,CACN,KAAA,CAAO,gBAAA,CACP,IAAA,CAAM,EACR,CACF,CAAA,CACA,eAAA,CAAiB,CACf,UAAW,WACb,CACF,CACF,ECzBA,IAAME,CAAAA,CAAeC,cAA6C,MAAS,CAAA,CAc3E,SAASC,CAAAA,CAAU,CAAE,QAAA,CAAAC,CAAAA,CAAU,WAAA,CAAAC,CAAAA,CAAa,OAAAC,CAAAA,CAAQ,YAAA,CAAAC,CAAAA,CAAc,IAAA,CAAAC,EAAO,QAAS,CAAA,CAA6B,CAC7G,IAAMC,EAAaC,QAAAA,CAAS,OAAA,CAAQN,CAAQ,CAAA,CAG5C,GAAI,OAAA,CAAQ,GAAA,CAAI,QAAA,GAAa,YAAA,CAAc,CACzC,IAAMO,CAAAA,CAAaF,CAAAA,CAAW,IAAA,CAC3BG,GACCC,cAAAA,CAAeD,CAAK,CAAA,GACnBA,CAAAA,CAAM,OAASE,CAAAA,EACbF,CAAAA,CAAM,IAAA,CAAkC,WAAA,GAAgB,eAC/D,CAAA,CAEMG,CAAAA,CAAaN,CAAAA,CAAW,IAAA,CAC3BG,GACCC,cAAAA,CAAeD,CAAK,CAAA,GACnBA,CAAAA,CAAM,OAASI,CAAAA,EACbJ,CAAAA,CAAM,IAAA,CAAkC,WAAA,GAAgB,eAC/D,CAAA,CAEA,GAAI,CAACD,CAAAA,EAAc,CAACI,CAAAA,CAClB,MAAM,IAAI,KAAA,CACR,4EACF,CAEJ,CAGA,IAAME,CAAAA,CAAeR,EAAW,IAAA,CAC7BG,CAAAA,EACCC,cAAAA,CAAeD,CAAK,IACnBA,CAAAA,CAAM,IAAA,GAASE,CAAAA,EACbF,CAAAA,CAAM,KAAkC,WAAA,GAAgB,cAAA,CAC/D,CAAA,CAEMM,CAAAA,CAAeT,EAAW,IAAA,CAC7BG,CAAAA,EACCC,cAAAA,CAAeD,CAAK,IACnBA,CAAAA,CAAM,IAAA,GAASI,CAAAA,EACbJ,CAAAA,CAAM,KAAkC,WAAA,GAAgB,cAAA,CAC/D,CAAA,CAIMO,CAAAA,CAAiBF,EACjBG,CAAAA,CAAmBP,cAAAA,CAAeM,CAAc,CAAA,CAChDA,EAAe,KAAA,CAA8C,QAAA,CAC/D,IAAA,CACEE,CAAAA,CAAmBH,EAEzB,OACEI,GAAAA,CAACrB,CAAAA,CAAa,QAAA,CAAb,CAAsB,KAAA,CAAO,CAAE,IAAA,CAAAO,CAAK,EACnC,QAAA,CAAAe,IAAAA,CAACC,aAAAA,CAAA,CACC,YAAanB,CAAAA,CACb,MAAA,CAAQC,CAAAA,CACR,YAAA,CAAcC,EAEb,QAAA,CAAA,CAAAa,CAAAA,CACAC,CAAAA,CAAAA,CACH,CAAA,CACF,CAEJ,CAEAlB,CAAAA,CAAU,WAAA,CAAc,OAAA,CAgBxB,SAASW,CAAAA,CAAa,CAAE,QAAA,CAAAV,CAAS,EAAoC,CAGnE,OAAOA,CACT,CAEAU,EAAa,WAAA,CAAc,cAAA,CAa3B,SAASE,CAAAA,CAAa,CACpB,QAAA,CAAAZ,CAAAA,CACA,KAAAqB,CAAAA,CAAO,IAAA,CACP,UAAAC,CAAAA,CAAY,WAAA,CACZ,iBAAA,CAAAC,CAAAA,CAAoB,IACpB,aAAA,CAAAC,CAAAA,CAAgB,IAAA,CAChB,yBAAA,CAAAC,EAA4B,KAAA,CAC5B,SAAA,CAAAC,CAAAA,CAAY,IAAA,CACZ,UAAAC,CACF,CAAA,CAAoC,CAGlC,IAAMvB,EADUwB,UAAAA,CAAW/B,CAAY,CAAA,EACjB,IAAA,EAAQ,SAGxBgC,CAAAA,CAAiBjC,CAAAA,CAAqB,CAAE,SAAA,CAAA0B,CAAU,CAAC,CAAA,CAGnDQ,CAAAA,CAAepC,CAAAA,CAAqB,CAAE,IAAA,CAAA2B,CAAAA,CAAM,SAAA,CAAAC,CAAU,CAAC,CAAA,CACvDS,CAAAA,CAAqBzC,CAAAA,CAAGwC,CAAAA,CAAcH,CAAS,CAAA,CAErD,OACET,GAAAA,CAACc,YAAAA,CAAA,CACC,aAAA,CAAeR,CAAAA,CACf,yBAAA,CAA2BC,CAAAA,CAC3B,UAAWI,CAAAA,CAEX,QAAA,CAAAX,GAAAA,CAACe,KAAAA,CAAA,CACC,SAAA,CAAWF,CAAAA,CACX,KAAA,CAAO,CACL,mBAAoB,CAAA,EAAGR,CAAiB,CAAA,EAAA,CAC1C,CAAA,CAEA,SAAAL,GAAAA,CAACgB,MAAAA,CAAA,CAAW,IAAA,CAAM9B,EAAM,SAAA,CAAU,cAAA,CAC/B,QAAA,CAAA,CAAC,CAAE,MAAA+B,CAAM,CAAA,GACRhB,IAAAA,CAAAiB,QAAAA,CAAA,CACG,QAAA,CAAA,CAAAV,CAAAA,EACCR,GAAAA,CAACmB,MAAAA,CAAA,CACC,OAAA,CAASF,CAAAA,CACT,SAAA,CAAU,+PAAA,CACV,aAAW,aAAA,CAEX,QAAA,CAAAjB,GAAAA,CAACoB,GAAAA,CAAA,CAAE,SAAA,CAAU,SAAA,CAAU,aAAA,CAAY,MAAA,CAAO,EAC5C,CAAA,CAEDtC,CAAAA,CAAAA,CACH,CAAA,CAEJ,CAAA,CACF,EACF,CAEJ,CAEAY,CAAAA,CAAa,WAAA,CAAc,eAM3B,SAAS2B,CAAAA,CAAaC,CAAAA,CAAiC,CACrD,OAAO,IACT,CAEAD,CAAAA,CAAa,WAAA,CAAc,eAU3B,SAASE,CAAAA,CAAY,CAAE,QAAA,CAAAzC,EAAU,SAAA,CAAA2B,CAAU,CAAA,CAAmC,CAC5E,OAAOT,GAAAA,CAAC,KAAA,CAAA,CAAI,SAAA,CAAW5B,CAAAA,CAAG,qDAAsDqC,CAAS,CAAA,CAAI,QAAA,CAAA3B,CAAAA,CAAS,CACxG,CAEAyC,CAAAA,CAAY,WAAA,CAAc,aAAA,CAW1B,SAASC,CAAAA,CAAW,CAAE,QAAA,CAAA1C,CAAAA,CAAU,GAAA2C,CAAAA,CAAK,IAAA,CAAM,UAAAhB,CAAU,CAAA,CAAkC,CACrF,OACET,GAAAA,CAAC0B,OAAAA,CAAA,CAAQ,KAAK,OAAA,CAAQ,KAAA,CAAO,QAAA,CAASD,CAAAA,CAAG,CAAC,CAAA,EAAK,GAAG,CAAA,CAAG,SAAA,CAAWrD,EAAG,mDAAA,CAAqDqC,CAAS,CAAA,CAC9H,QAAA,CAAA3B,EACH,CAEJ,CAEA0C,CAAAA,CAAW,WAAA,CAAc,aAWzB,SAASG,CAAAA,CAAiB,CAAE,QAAA,CAAA7C,EAAU,SAAA,CAAA2B,CAAU,CAAA,CAAwC,CACtF,OACET,GAAAA,CAAC,GAAA,CAAA,CAAE,IAAA,CAAK,aAAA,CAAc,UAAW5B,CAAAA,CAAG,kCAAA,CAAoCqC,CAAS,CAAA,CAC9E,SAAA3B,CAAAA,CACH,CAEJ,CAEA6C,CAAAA,CAAiB,YAAc,kBAAA,CAY/B,SAASC,CAAAA,CAAY,CAAE,SAAA9C,CAAAA,CAAU,SAAA,CAAA2B,CAAU,CAAA,CAAmC,CAC5E,OAAOT,GAAAA,CAAC,KAAA,CAAA,CAAI,SAAA,CAAW5B,EAAG,wDAAA,CAA0DqC,CAAS,CAAA,CAAI,QAAA,CAAA3B,EAAS,CAC5G,CAEA8C,CAAAA,CAAY,WAAA,CAAc,cAU1B,SAASC,CAAAA,CAAW,CAAE,QAAA,CAAA/C,CAAS,CAAA,CAAkC,CAE/D,IAAMgD,CAAAA,CAAQpB,WAAWqB,0BAA0B,CAAA,CAEnD,GAAI,CAACD,EACH,MAAM,IAAI,KAAA,CAAM,+CAA+C,EAIjE,GAAI,CAACvC,cAAAA,CAAeT,CAAQ,EAC1B,MAAM,IAAI,KAAA,CAAM,uDAAuD,EAIzE,IAAMkD,CAAAA,CAAelD,CAAAA,CAEfmD,CAAAA,CAAc,IAAY,CAC9BH,CAAAA,CAAM,KAAA,GACR,EAGMI,CAAAA,CAAmBF,CAAAA,CAAa,KAAA,EAA+C,OAAA,CASrF,OAAOG,YAAAA,CAAaH,CAAAA,CAAc,CAAE,OAAA,CARdE,EAClB,IAAY,CACVA,CAAAA,EAAgB,CAChBD,IACF,CAAA,CACAA,CAGuD,CAAkD,CAC/G,CAEAJ,CAAAA,CAAW,WAAA,CAAc,YAAA,KAqBZO,CAAAA,CAAQ,MAAA,CAAO,MAAA,CAAOvD,CAAAA,CAAW,CAC5C,OAAA,CAASW,CAAAA,CACT,OAAA,CAASE,CAAAA,CACT,QAAS2B,CAAAA,CACT,MAAA,CAAQE,CAAAA,CACR,KAAA,CAAOC,EACP,WAAA,CAAaG,CAAAA,CACb,OAAQC,CAAAA,CACR,KAAA,CAAOC,CACT,CAAC","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","import { cva, type VariantProps } from 'class-variance-authority';\n\n/**\n * CVA Variants for Modal.Content\n *\n * Size and animation variant definitions for the modal content container.\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 ],\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': 'animate-fadeIn', // Fade in with scale\n fade: 'animate-fadeIn', // Fade in only\n slide: 'animate-slideUp', // Slide up\n none: '', // No animation\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 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 ],\n {\n variants: {\n animation: {\n 'fade-zoom': 'animate-fadeIn',\n fade: 'animate-fadeIn',\n slide: 'animate-fadeIn',\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 exactly one Modal.Trigger and one Modal.Content.\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 hasTrigger = childArray.some(\n (child) =>\n isValidElement(child) &&\n (child.type === ModalTrigger ||\n (child.type as { displayName?: string }).displayName === 'ModalTrigger')\n );\n\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 (!hasTrigger || !hasContent) {\n throw new Error(\n 'Modal requires exactly one Modal.Trigger and one Modal.Content as children'\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 // Extract the actual children from Modal.Trigger and Modal.Content\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 const unwrappedContent = contentChild as ReactElement;\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-4 top-4 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","hasTrigger","child","isValidElement","ModalTrigger","hasContent","ModalContent","triggerChild","contentChild","triggerElement","unwrappedTrigger","unwrappedContent","jsx","jsxs","AriaDialogTrigger","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-11', // 44px\n lg: 'h-14', // 56px\n} 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 exactly one Modal.Trigger and one Modal.Content.\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 hasTrigger = childArray.some(\n (child) =>\n isValidElement(child) &&\n (child.type === ModalTrigger ||\n (child.type as { displayName?: string }).displayName === 'ModalTrigger')\n );\n\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 (!hasTrigger || !hasContent) {\n throw new Error(\n 'Modal requires exactly one Modal.Trigger and one Modal.Content as children'\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 // Extract the actual children from Modal.Trigger and Modal.Content\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 const unwrappedContent = contentChild as ReactElement;\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NumberField.d.ts","sourceRoot":"","sources":["../../../src/elements/NumberField/NumberField.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"NumberField.d.ts","sourceRoot":"","sources":["../../../src/elements/NumberField/NumberField.tsx"],"names":[],"mappings":"AA4GA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,eAAO,MAAM,WAAW,4NAiQvB,CAAC"}
|
|
@@ -80,6 +80,11 @@ export declare const NumberFieldPropsSchema: z.ZodObject<{
|
|
|
80
80
|
onFocus: z.ZodOptional<z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>>;
|
|
81
81
|
onBlur: z.ZodOptional<z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>>;
|
|
82
82
|
onFocusChange: z.ZodOptional<z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>>;
|
|
83
|
+
animated: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
84
|
+
springConfig: z.ZodOptional<z.ZodObject<{
|
|
85
|
+
stiffness: z.ZodNumber;
|
|
86
|
+
damping: z.ZodNumber;
|
|
87
|
+
}, z.core.$strip>>;
|
|
83
88
|
className: z.ZodOptional<z.ZodString>;
|
|
84
89
|
autoFocus: z.ZodOptional<z.ZodBoolean>;
|
|
85
90
|
}, z.core.$strip>;
|
|
@@ -103,6 +108,13 @@ export interface ThemisNumberFieldCustomProps {
|
|
|
103
108
|
decrementAriaLabel?: string;
|
|
104
109
|
/** Allow negative numbers (sets minValue to -2147483647 if not explicitly set) */
|
|
105
110
|
allowNegative?: boolean;
|
|
111
|
+
/** Enable/disable spring animation on stepper press. Default: true */
|
|
112
|
+
animated?: boolean;
|
|
113
|
+
/** Custom spring configuration for stepper animation */
|
|
114
|
+
springConfig?: {
|
|
115
|
+
stiffness: number;
|
|
116
|
+
damping: number;
|
|
117
|
+
};
|
|
106
118
|
}
|
|
107
119
|
/**
|
|
108
120
|
* Props for the NumberField component.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NumberField.types.d.ts","sourceRoot":"","sources":["../../../src/elements/NumberField/NumberField.types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EACV,gBAAgB,IAAI,oBAAoB,EACxC,gBAAgB,EACjB,MAAM,uBAAuB,CAAC;AAM/B;;GAEG;AACH,eAAO,MAAM,iBAAiB,aAAa,CAAC;AAE5C;;GAEG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC;;GAEG;AACH,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAM9C;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAAC;AAMtD;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;AAM3D;;;GAGG;AACH,eAAO,MAAM,sBAAsB
|
|
1
|
+
{"version":3,"file":"NumberField.types.d.ts","sourceRoot":"","sources":["../../../src/elements/NumberField/NumberField.types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EACV,gBAAgB,IAAI,oBAAoB,EACxC,gBAAgB,EACjB,MAAM,uBAAuB,CAAC;AAM/B;;GAEG;AACH,eAAO,MAAM,iBAAiB,aAAa,CAAC;AAE5C;;GAEG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC;;GAEG;AACH,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAM9C;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAAC;AAMtD;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;AAM3D;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAuFjC,CAAC;AAMH;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C,6CAA6C;IAC7C,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,UAAU,EAAE,gBAAgB,KAAK,MAAM,CAAC,CAAC;IACnE,iCAAiC;IACjC,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,6CAA6C;IAC7C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,6CAA6C;IAC7C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kFAAkF;IAClF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,YAAY,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACvD;AAED;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC,GACnE,4BAA4B,CAAC"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
'use strict';var react=require('react'),reactAriaComponents=require('react-aria-components'),lucideReact=require('lucide-react'),clsx=require('clsx'),tailwindMerge=require('tailwind-merge'),classVarianceAuthority=require('class-variance-authority'),jsxRuntime=require('react/jsx-runtime'),zod=require('zod');function t(...c){return tailwindMerge.twMerge(clsx.clsx(c))}var C=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}}),L=classVarianceAuthority.cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors [&_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)] data-[pressed]:bg-[var(--primary-action)]/80",destructive:"bg-[var(--destructive-background)] text-[var(--destructive-foreground)] shadow-md hover:bg-[var(--destructive-background)]/90 data-[pressed]:bg-[var(--destructive-background)]/80",outline:"border border-[var(--input-border)] bg-[var(--page-background)] hover:bg-[var(--input-border)] data-[pressed]:bg-[var(--input-border)]",secondary:"bg-[var(--secondary)] text-[var(--secondary-foreground)] shadow-md hover:bg-[var(--secondary)]/80 data-[pressed]:bg-[var(--secondary)]/70",ghost:"hover:bg-[var(--accent)] hover:text-[var(--accent-foreground)] data-[pressed]:bg-[var(--accent)]",link:"text-[var(--text-link)] underline-offset-4 hover:underline data-[pressed]: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 P="data-[pressed]:scale-[0.97]";var G="data-[hovered]:shadow-md";var S="hc:data-[hovered]:outline hc:data-[hovered]:outline-2 hc:data-[hovered]:outline-foreground",y="hc:data-[pressed]:outline hc:data-[pressed]:outline-2 hc:data-[pressed]:outline-offset-1 hc:data-[pressed]:outline-foreground";var ee=react.createContext(null);ee.displayName="ButtonGroupContext";function te(){return react.useContext(ee)}var re=react.createContext(null);re.displayName="ButtonGroupItemContext";function oe(){return react.useContext(re)}classVarianceAuthority.cva("inline-flex items-center gap-0",{variants:{orientation:{horizontal:"flex-row",vertical:"flex-col w-full"}},defaultVariants:{orientation:"horizontal"}});var ae=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 i=react.memo(react.forwardRef(({className:c,buttonVisualClassName:v,variant:A,size:o,visualSize:x,fullWidth:I,loading:s=false,loadingText:h="Loading...",shortcut:g,children:V,isDisabled:w,paywall:a=false,paywallRedirect:b,paywallDescription:_,onPress:N,...F},T)=>{let E=react.useId(),n=te(),l=oe(),u=A??n?.variant??"default",le=o??n?.size,ue=w??n?.isDisabled??false,X=n?.orientation==="vertical",j=I||X,de=l?ae({orientation:n?.orientation??"horizontal",position:l.position}):"",B=x??le??"default";return process.env.NODE_ENV!=="production"&&(B==="dot"||B==="icon")&&!F["aria-label"]&&!V&&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:T,isDisabled:ue||s||void 0,"aria-disabled":a?true:void 0,"aria-describedby":a?E:void 0,onPress:f=>{if(a){b&&window.open(b,"_blank","noopener,noreferrer");return}N?.(f);},className:t(C({fullWidth:j,inVerticalGroup:X}),c),...F,children:f=>jsxRuntime.jsxs("span",{className:t(L({variant:u,visualSize:B,paywall:a,fullWidth:j}),de,v,P,G,S,y),"data-pressed":f.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:h})]}),!s&&V,a&&jsxRuntime.jsx(lucideReact.Zap,{"data-testid":"zap-icon","aria-hidden":"true",className:"ml-1"}),a&&jsxRuntime.jsxs("span",{id:E,className:"sr-only",children:["Premium feature: ",_||"Upgrade required to access this feature"]}),f.isFocusVisible&&g&&jsxRuntime.jsx("kbd",{className:"ml-auto hidden text-xs opacity-60 lg:inline",children:g}),f.isPressed&&jsxRuntime.jsx("span",{className:"absolute inset-0 rounded-[inherit] bg-current opacity-10 motion-safe:animate-in motion-safe:zoom-in-95","aria-hidden":"true"})]})})}));i.displayName="Button";var k=4294967295,O=0,U=-2147483647,Ne=zod.z.object({value:zod.z.number().nullable().optional(),defaultValue:zod.z.number().optional(),minValue:zod.z.number().optional(),maxValue:zod.z.number().optional(),allowNegative:zod.z.boolean().optional(),step:zod.z.number().positive().optional(),formatOptions:zod.z.custom().optional(),isDisabled:zod.z.boolean().optional(),isReadOnly:zod.z.boolean().optional(),isRequired:zod.z.boolean().optional(),isInvalid:zod.z.boolean().optional(),isWheelDisabled:zod.z.boolean().optional(),validate:zod.z.function().optional(),validationBehavior:zod.z.enum(["native","aria"]).default("native"),label:zod.z.string(),description:zod.z.string().optional(),errorMessage:zod.z.union([zod.z.string(),zod.z.function()]).optional(),stepperLayout:zod.z.enum(["sides","stacked","hidden"]).default("sides"),incrementAriaLabel:zod.z.string().optional(),decrementAriaLabel:zod.z.string().optional(),name:zod.z.string().optional(),size:zod.z.enum(["sm","default","lg"]).default("default"),onChange:zod.z.function().optional(),onFocus:zod.z.function().optional(),onBlur:zod.z.function().optional(),onFocusChange:zod.z.function().optional(),className:zod.z.string().optional(),autoFocus:zod.z.boolean().optional()});var H=classVarianceAuthority.cva(["inline-flex items-center rounded-md border","bg-[var(--content-background)] text-[var(--content-foreground)]","transition-colors duration-200","focus-within:ring-2 focus-within:ring-[var(--ring)] focus-within:ring-offset-2","data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50"],{variants:{size:{sm:"h-9 text-sm",default:"h-10 text-base",lg:"h-12 text-lg"},isInvalid:{true:"border-[var(--destructive-background)] focus-within:ring-[var(--destructive-background)]",false:"border-[var(--input-border)] hover:border-[var(--input-border)]/80"}},defaultVariants:{size:"default",isInvalid:false}}),M=classVarianceAuthority.cva(["flex-1 bg-transparent text-center tabular-nums","min-w-0","focus:outline-none","[appearance:textfield]","[&::-webkit-outer-spin-button]:appearance-none","[&::-webkit-inner-spin-button]:appearance-none"],{variants:{size:{sm:"px-2 text-sm",default:"px-3 text-base",lg:"px-4 text-lg"}},defaultVariants:{size:"default"}}),m=classVarianceAuthority.cva(["flex items-center justify-center","transition-colors duration-150","select-none","hover:bg-[var(--accent)] hover:text-[var(--accent-foreground)]","focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]","focus-visible:ring-inset","active:bg-[var(--accent)]/80","disabled:pointer-events-none disabled:opacity-50"],{variants:{size:{sm:"min-w-8 min-h-8 text-sm",default:"min-w-10 min-h-10 text-base",lg:"min-w-12 min-h-12 text-lg"},position:{left:"rounded-l-md border-r border-[var(--input-border)]",right:"rounded-r-md border-l border-[var(--input-border)]",top:"rounded-tr-md border-b border-l border-[var(--input-border)] h-1/2",bottom:"rounded-br-md border-l border-[var(--input-border)] h-1/2"}},defaultVariants:{size:"default"}}),$=classVarianceAuthority.cva(["block font-medium text-[var(--content-foreground)]","mb-1.5",'data-[required]:after:content-["*"] data-[required]:after:ml-0.5',"data-[required]:after:text-[var(--destructive-background)]"],{variants:{size:{sm:"text-sm",default:"text-sm",lg:"text-base"}},defaultVariants:{size:"default"}}),W=classVarianceAuthority.cva(["text-[var(--menu-muted)]","mt-1"],{variants:{size:{sm:"text-xs",default:"text-sm",lg:"text-base"}},defaultVariants:{size:"default"}}),Y=classVarianceAuthority.cva(["flex items-center gap-1","text-[var(--destructive-background)]","mt-1"],{variants:{size:{sm:"text-xs",default:"text-sm",lg:"text-base"}},defaultVariants:{size:"default"}});var se=react.forwardRef(({label:c,description:v,errorMessage:A,size:o="default",stepperLayout:x="sides",allowNegative:I=false,incrementAriaLabel:s,decrementAriaLabel:h,minValue:g,maxValue:V,className:w,isInvalid:a,isRequired:b,isDisabled:_,isReadOnly:N,...F},T)=>{let E=g??(I?U:O),n=V??k,l=x!=="hidden"&&!N,u=x==="stacked";return jsxRuntime.jsxs(reactAriaComponents.NumberField,{ref:T,className:t("group flex flex-col",w),minValue:E,maxValue:n,isInvalid:a,isRequired:b,isDisabled:_,isReadOnly:N,...F,children:[jsxRuntime.jsx(reactAriaComponents.Label,{className:t($({size:o})),"data-required":b||void 0,children:c}),jsxRuntime.jsxs(reactAriaComponents.Group,{className:t(H({size:o,isInvalid:a??false}),u&&"pr-0"),children:[l&&!u&&jsxRuntime.jsx(i,{slot:"decrement",variant:"ghost",className:"!min-h-0 !min-w-0",buttonVisualClassName:t(m({size:o,position:"left"})),"aria-label":h??"Decrease value",children:jsxRuntime.jsx(lucideReact.Minus,{className:"h-4 w-4","aria-hidden":"true"})}),jsxRuntime.jsx(reactAriaComponents.Input,{className:t(M({size:o}))}),l&&u&&jsxRuntime.jsxs("div",{className:"flex flex-col h-full",children:[jsxRuntime.jsx(i,{slot:"increment",variant:"ghost",className:"!min-h-0 !min-w-0",buttonVisualClassName:t(m({size:o,position:"top"})),"aria-label":s??"Increase value",children:jsxRuntime.jsx(lucideReact.Plus,{className:"h-3 w-3","aria-hidden":"true"})}),jsxRuntime.jsx(i,{slot:"decrement",variant:"ghost",className:"!min-h-0 !min-w-0",buttonVisualClassName:t(m({size:o,position:"bottom"})),"aria-label":h??"Decrease value",children:jsxRuntime.jsx(lucideReact.Minus,{className:"h-3 w-3","aria-hidden":"true"})})]}),l&&!u&&jsxRuntime.jsx(i,{slot:"increment",variant:"ghost",className:"!min-h-0 !min-w-0",buttonVisualClassName:t(m({size:o,position:"right"})),"aria-label":s??"Increase value",children:jsxRuntime.jsx(lucideReact.Plus,{className:"h-4 w-4","aria-hidden":"true"})})]}),v&&jsxRuntime.jsx(reactAriaComponents.Text,{slot:"description",className:t(W({size:o})),children:v}),jsxRuntime.jsx(reactAriaComponents.FieldError,{className:t(Y({size:o})),children:A})]})});se.displayName="NumberField";exports.DEFAULT_MAX_VALUE=k;exports.DEFAULT_MIN_VALUE=O;exports.NEGATIVE_MIN_VALUE=U;exports.NumberField=se;exports.NumberFieldPropsSchema=Ne;exports.numberFieldDescriptionVariants=W;exports.numberFieldErrorVariants=Y;exports.numberFieldInputVariants=M;exports.numberFieldLabelVariants=$;exports.numberFieldStepperVariants=m;exports.numberFieldVariants=H;//# sourceMappingURL=index.js.map
|
|
2
|
+
'use strict';var react=require('react'),reactAriaComponents=require('react-aria-components'),lucideReact=require('lucide-react'),clsx=require('clsx'),tailwindMerge=require('tailwind-merge'),classVarianceAuthority=require('class-variance-authority'),jsxRuntime=require('react/jsx-runtime'),zod=require('zod');function n(...r){return tailwindMerge.twMerge(clsx.clsx(r))}var j=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}}),X=classVarianceAuthority.cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors [&_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)] data-[pressed]:bg-[var(--primary-action)]/80",destructive:"bg-[var(--destructive-background)] text-[var(--destructive-foreground)] shadow-md hover:bg-[var(--destructive-background)]/90 data-[pressed]:bg-[var(--destructive-background)]/80",outline:"border border-[var(--input-border)] bg-[var(--page-background)] hover:bg-[var(--input-border)] data-[pressed]:bg-[var(--input-border)]",secondary:"bg-[var(--secondary)] text-[var(--secondary-foreground)] shadow-md hover:bg-[var(--secondary)]/80 data-[pressed]:bg-[var(--secondary)]/70",ghost:"hover:bg-[var(--accent)] hover:text-[var(--accent-foreground)] data-[pressed]:bg-[var(--accent)]",link:"text-[var(--text-link)] underline-offset-4 hover:underline data-[pressed]: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 Q="data-[pressed]:scale-[0.97]";var Z="data-[hovered]:shadow-md";var D="hc:data-[hovered]:outline hc:data-[hovered]:outline-2 hc:data-[hovered]:outline-foreground",M="hc:data-[pressed]:outline hc:data-[pressed]:outline-2 hc:data-[pressed]:outline-offset-1 hc:data-[pressed]:outline-foreground";var ve=react.createContext(null);ve.displayName="ButtonGroupContext";function xe(){return react.useContext(ve)}var he=react.createContext(null);he.displayName="ButtonGroupItemContext";function ge(){return react.useContext(he)}classVarianceAuthority.cva("inline-flex items-center gap-0",{variants:{orientation:{horizontal:"flex-row",vertical:"flex-col w-full"}},defaultVariants:{orientation:"horizontal"}});var Ve=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 d=react.memo(react.forwardRef(({className:r,buttonVisualClassName:a,variant:i,size:t,visualSize:p,fullWidth:O,loading:m=false,loadingText:E="Loading...",shortcut:S,children:h,isDisabled:U,paywall:l=false,paywallRedirect:g,paywallDescription:A,onPress:H,...w},$)=>{let V=react.useId(),u=xe(),I=ge(),C=i??u?.variant??"default",q=t??u?.size,Y=U??u?.isDisabled??false,T=u?.orientation==="vertical",_=O||T,c=I?Ve({orientation:u?.orientation??"horizontal",position:I.position}):"",N=p??q??"default";return process.env.NODE_ENV!=="production"&&(N==="dot"||N==="icon")&&!w["aria-label"]&&!h&&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:$,isDisabled:Y||m||void 0,"aria-disabled":l?true:void 0,"aria-describedby":l?V:void 0,onPress:s=>{if(l){g&&window.open(g,"_blank","noopener,noreferrer");return}H?.(s);},className:n(j({fullWidth:_,inVerticalGroup:T}),r),...w,children:s=>jsxRuntime.jsxs("span",{className:n(X({variant:C,visualSize:N,paywall:l,fullWidth:_}),c,a,Q,Z,D,M),"data-pressed":s.isPressed||void 0,children:[m&&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:E})]}),!m&&h,l&&jsxRuntime.jsx(lucideReact.Zap,{"data-testid":"zap-icon","aria-hidden":"true",className:"ml-1"}),l&&jsxRuntime.jsxs("span",{id:V,className:"sr-only",children:["Premium feature: ",A||"Upgrade required to access this feature"]}),s.isFocusVisible&&S&&jsxRuntime.jsx("kbd",{className:"ml-auto hidden text-xs opacity-60 lg:inline",children:S}),s.isPressed&&jsxRuntime.jsx("span",{className:"absolute inset-0 rounded-[inherit] bg-current opacity-10 motion-safe:animate-in motion-safe:zoom-in-95","aria-hidden":"true"})]})})}));d.displayName="Button";function Ne(r,a){return !(r===false||a)}var ye="(prefers-reduced-motion: reduce)";function Oe(){return typeof window>"u"?false:window.matchMedia(ye).matches}function Fe(){let[r,a]=react.useState(Oe);return react.useEffect(()=>{let i=window.matchMedia(ye);a(i.matches);let t=p=>{a(p.matches);};return i.addEventListener("change",t),()=>i.removeEventListener("change",t)},[]),r}var te=4294967295,re=0,ne=-2147483647,Ue=zod.z.object({value:zod.z.number().nullable().optional(),defaultValue:zod.z.number().optional(),minValue:zod.z.number().optional(),maxValue:zod.z.number().optional(),allowNegative:zod.z.boolean().optional(),step:zod.z.number().positive().optional(),formatOptions:zod.z.custom().optional(),isDisabled:zod.z.boolean().optional(),isReadOnly:zod.z.boolean().optional(),isRequired:zod.z.boolean().optional(),isInvalid:zod.z.boolean().optional(),isWheelDisabled:zod.z.boolean().optional(),validate:zod.z.function().optional(),validationBehavior:zod.z.enum(["native","aria"]).default("native"),label:zod.z.string(),description:zod.z.string().optional(),errorMessage:zod.z.union([zod.z.string(),zod.z.function()]).optional(),stepperLayout:zod.z.enum(["sides","stacked","hidden"]).default("sides"),incrementAriaLabel:zod.z.string().optional(),decrementAriaLabel:zod.z.string().optional(),name:zod.z.string().optional(),size:zod.z.enum(["sm","default","lg"]).default("default"),onChange:zod.z.function().optional(),onFocus:zod.z.function().optional(),onBlur:zod.z.function().optional(),onFocusChange:zod.z.function().optional(),animated:zod.z.boolean().optional().default(true),springConfig:zod.z.object({stiffness:zod.z.number(),damping:zod.z.number()}).optional(),className:zod.z.string().optional(),autoFocus:zod.z.boolean().optional()});var oe=classVarianceAuthority.cva(["inline-flex items-center rounded-md border","bg-[var(--content-background)] text-[var(--content-foreground)]","transition-colors duration-200","focus-within:ring-2 focus-within:ring-[var(--ring)] focus-within:ring-offset-2","data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50"],{variants:{size:{sm:"h-9 text-sm",default:"h-10 text-base",lg:"h-12 text-lg"},isInvalid:{true:"border-[var(--destructive-background)] focus-within:ring-[var(--destructive-background)]",false:"border-[var(--input-border)] hover:border-[var(--input-border)]/80"}},defaultVariants:{size:"default",isInvalid:false}}),ae=classVarianceAuthority.cva(["flex-1 bg-transparent text-center tabular-nums","min-w-0","focus:outline-none","[appearance:textfield]","[&::-webkit-outer-spin-button]:appearance-none","[&::-webkit-inner-spin-button]:appearance-none"],{variants:{size:{sm:"px-2 text-sm",default:"px-3 text-base",lg:"px-4 text-lg"}},defaultVariants:{size:"default"}}),x=classVarianceAuthority.cva(["flex items-center justify-center","transition-colors duration-150","select-none","hover:bg-[var(--accent)] hover:text-[var(--accent-foreground)]","focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]","focus-visible:ring-inset","active:bg-[var(--accent)]/80","disabled:pointer-events-none disabled:opacity-50"],{variants:{size:{sm:"min-w-8 min-h-8 text-sm",default:"min-w-10 min-h-10 text-base",lg:"min-w-12 min-h-12 text-lg"},position:{left:"rounded-l-md border-r border-[var(--input-border)]",right:"rounded-r-md border-l border-[var(--input-border)]",top:"rounded-tr-md border-b border-l border-[var(--input-border)] h-1/2",bottom:"rounded-br-md border-l border-[var(--input-border)] h-1/2"}},defaultVariants:{size:"default"}}),ie=classVarianceAuthority.cva(["block font-medium text-[var(--content-foreground)]","mb-1.5",'data-[required]:after:content-["*"] data-[required]:after:ml-0.5',"data-[required]:after:text-[var(--destructive-background)]"],{variants:{size:{sm:"text-sm",default:"text-sm",lg:"text-base"}},defaultVariants:{size:"default"}}),se=classVarianceAuthority.cva(["text-[var(--menu-muted)]","mt-1"],{variants:{size:{sm:"text-xs",default:"text-sm",lg:"text-base"}},defaultVariants:{size:"default"}}),le=classVarianceAuthority.cva(["flex items-center gap-1","text-[var(--destructive-background)]","mt-1"],{variants:{size:{sm:"text-xs",default:"text-sm",lg:"text-base"}},defaultVariants:{size:"default"}});var F;function Qe(){return F!==void 0?Promise.resolve(F):import('motion').then(r=>(F=r.animate,F)).catch(()=>(F=null,null))}function Ze(r,a){if(a)try{let i={...a};return i.style==="currency"&&!i.currencyDisplay&&(i.currencyDisplay="narrowSymbol"),new Intl.NumberFormat(void 0,i).format(r)}catch{return String(Math.round(r))}return String(Math.round(r))}var we=react.forwardRef(({label:r,description:a,errorMessage:i,size:t="default",stepperLayout:p="sides",allowNegative:O=false,incrementAriaLabel:m,decrementAriaLabel:E,animated:S,springConfig:h,onChange:U,value:l,defaultValue:g,formatOptions:A,minValue:H,maxValue:w,className:$,isInvalid:V,isRequired:u,isDisabled:I,isReadOnly:C,...q},Y)=>{let T=H??(O?ne:re),_=w??te,c=p!=="hidden"&&!C,N=Fe(),y=Ne(S,N)&&c,L=react.useRef(null),s=react.useRef(l??g??0),W=react.useRef(void 0),B=react.useRef(null);react.useEffect(()=>{y&&Qe().then(f=>{W.current=f??null;});},[y]),react.useEffect(()=>()=>{B.current?.stop();},[]);let Ie=f=>{let ue=s.current;if(y&&W.current&&L.current&&ue!==f){B.current?.stop();let de=f,R=L.current,G=R.parentElement?.querySelector("input");R.style.display="",G&&(G.style.color="transparent"),B.current=W.current(ue,de,{type:"spring",stiffness:h?.stiffness??500,damping:h?.damping??30,onUpdate:pe=>{s.current=pe,R.textContent=Ze(pe,A);},onComplete:()=>{R.style.display="none",G&&(G.style.color=""),B.current=null,s.current=de;}});}else s.current=f;U?.(f);},P=p==="stacked";return jsxRuntime.jsxs(reactAriaComponents.NumberField,{ref:Y,className:n("group flex flex-col",$),minValue:T,maxValue:_,isInvalid:V,isRequired:u,isDisabled:I,isReadOnly:C,value:l,defaultValue:g,formatOptions:A,onChange:Ie,...q,children:[jsxRuntime.jsx(reactAriaComponents.Label,{className:n(ie({size:t})),"data-required":u||void 0,children:r}),jsxRuntime.jsxs(reactAriaComponents.Group,{className:n(oe({size:t,isInvalid:V??false}),P&&"pr-0"),children:[c&&!P&&jsxRuntime.jsx(d,{slot:"decrement",variant:"ghost",className:"!min-h-0 !min-w-0",buttonVisualClassName:n(x({size:t,position:"left"})),"aria-label":E??"Decrease value",children:jsxRuntime.jsx(lucideReact.Minus,{className:"h-4 w-4","aria-hidden":"true"})}),jsxRuntime.jsxs("div",{className:"relative flex flex-1",children:[jsxRuntime.jsx(reactAriaComponents.Input,{className:n(ae({size:t}),"w-full")}),y&&jsxRuntime.jsx("span",{ref:L,className:n("absolute inset-0 flex items-center justify-center","pointer-events-none bg-[var(--content-background)] tabular-nums",t==="sm"&&"px-2 text-sm",t==="default"&&"px-3 text-base",t==="lg"&&"px-4 text-lg"),style:{display:"none"},"aria-hidden":"true"})]}),c&&P&&jsxRuntime.jsxs("div",{className:"flex flex-col h-full",children:[jsxRuntime.jsx(d,{slot:"increment",variant:"ghost",className:"!min-h-0 !min-w-0",buttonVisualClassName:n(x({size:t,position:"top"})),"aria-label":m??"Increase value",children:jsxRuntime.jsx(lucideReact.Plus,{className:"h-3 w-3","aria-hidden":"true"})}),jsxRuntime.jsx(d,{slot:"decrement",variant:"ghost",className:"!min-h-0 !min-w-0",buttonVisualClassName:n(x({size:t,position:"bottom"})),"aria-label":E??"Decrease value",children:jsxRuntime.jsx(lucideReact.Minus,{className:"h-3 w-3","aria-hidden":"true"})})]}),c&&!P&&jsxRuntime.jsx(d,{slot:"increment",variant:"ghost",className:"!min-h-0 !min-w-0",buttonVisualClassName:n(x({size:t,position:"right"})),"aria-label":m??"Increase value",children:jsxRuntime.jsx(lucideReact.Plus,{className:"h-4 w-4","aria-hidden":"true"})})]}),a&&jsxRuntime.jsx(reactAriaComponents.Text,{slot:"description",className:n(se({size:t})),children:a}),jsxRuntime.jsx(reactAriaComponents.FieldError,{className:n(le({size:t})),children:i})]})});we.displayName="NumberField";exports.DEFAULT_MAX_VALUE=te;exports.DEFAULT_MIN_VALUE=re;exports.NEGATIVE_MIN_VALUE=ne;exports.NumberField=we;exports.NumberFieldPropsSchema=Ue;exports.numberFieldDescriptionVariants=se;exports.numberFieldErrorVariants=le;exports.numberFieldInputVariants=ae;exports.numberFieldLabelVariants=ie;exports.numberFieldStepperVariants=x;exports.numberFieldVariants=oe;//# sourceMappingURL=index.js.map
|
|
3
3
|
//# sourceMappingURL=index.js.map
|