@maestro-js/components 1.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/add.ts +41 -0
- package/commands/index.ts +7 -0
- package/commands/list.ts +9 -0
- package/dist/components.json +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +48 -0
- package/package.json +49 -0
- package/registry.json +1445 -0
- package/scripts/build.ts +44 -0
- package/src/components/alert-dialog.tsx +150 -0
- package/src/components/autocomplete.tsx +288 -0
- package/src/components/autosuggest.tsx +314 -0
- package/src/components/avatar.tsx +54 -0
- package/src/components/boolean-select.tsx +48 -0
- package/src/components/button-group.tsx +75 -0
- package/src/components/button-link.tsx +71 -0
- package/src/components/button.tsx +132 -0
- package/src/components/checkbox-group.tsx +172 -0
- package/src/components/checkbox.tsx +207 -0
- package/src/components/chip.tsx +158 -0
- package/src/components/container.tsx +82 -0
- package/src/components/currency-field.tsx +183 -0
- package/src/components/date-input.tsx +189 -0
- package/src/components/date-picker.tsx +211 -0
- package/src/components/date-range-picker.tsx +290 -0
- package/src/components/date-time-picker.tsx +196 -0
- package/src/components/dialog.tsx +97 -0
- package/src/components/disclosure.tsx +114 -0
- package/src/components/drawer.tsx +78 -0
- package/src/components/enum-chip.tsx +30 -0
- package/src/components/file-input.tsx +245 -0
- package/src/components/form.tsx +82 -0
- package/src/components/headless-file-input.tsx +362 -0
- package/src/components/helpers/animated-popover.tsx +24 -0
- package/src/components/helpers/button-context.ts +10 -0
- package/src/components/helpers/calendar-month-year-picker.tsx +280 -0
- package/src/components/helpers/form-field.tsx +229 -0
- package/src/components/helpers/get-button-classes.ts +138 -0
- package/src/components/helpers/headless-button.tsx +36 -0
- package/src/components/helpers/pdf-dist.client.ts +6 -0
- package/src/components/icon.tsx +26 -0
- package/src/components/image-input.tsx +265 -0
- package/src/components/img.tsx +46 -0
- package/src/components/inline-alert.tsx +54 -0
- package/src/components/labeled-value.tsx +480 -0
- package/src/components/link-tabs.tsx +118 -0
- package/src/components/menu.tsx +152 -0
- package/src/components/month-day-input.tsx +176 -0
- package/src/components/multi-file-input.tsx +244 -0
- package/src/components/multi-image-input.tsx +389 -0
- package/src/components/multicomplete.tsx +322 -0
- package/src/components/multiselect.tsx +325 -0
- package/src/components/multisuggest.tsx +357 -0
- package/src/components/number-field.tsx +143 -0
- package/src/components/numeric-tag-field.tsx +271 -0
- package/src/components/pdf-input.tsx +249 -0
- package/src/components/pdf.tsx +86 -0
- package/src/components/percentage-field.tsx +187 -0
- package/src/components/phone-number-field.tsx +166 -0
- package/src/components/radio-group.tsx +112 -0
- package/src/components/radio.tsx +91 -0
- package/src/components/select.tsx +215 -0
- package/src/components/spinner.tsx +16 -0
- package/src/components/stepper.tsx +181 -0
- package/src/components/switch.tsx +186 -0
- package/src/components/tabs.tsx +151 -0
- package/src/components/tag-field.tsx +250 -0
- package/src/components/text-area.tsx +148 -0
- package/src/components/text-field.tsx +144 -0
- package/src/components/time-input.tsx +198 -0
- package/src/components/toast.tsx +176 -0
- package/src/components/toggle-button-group.tsx +94 -0
- package/src/components/year-month-input.tsx +187 -0
- package/src/utils/colors.ts +44 -0
- package/src/utils/compose-refs.ts +32 -0
- package/src/utils/enum-colors.ts +1 -0
- package/src/utils/file-input.ts +49 -0
- package/src/utils/icons.d.ts +20 -0
- package/src/utils/tw.ts +13 -0
- package/src/utils/use-element-size.ts +35 -0
- package/src/utils/use-number-input.ts +143 -0
- package/src/utils/use-pagination.ts +38 -0
- package/src/utils/use-prevent-default.ts +27 -0
- package/src/utils/use-render-props.ts +39 -0
- package/src/utils/use-spin-delay.ts +24 -0
- package/src/utils/use-stable-accessor.ts +11 -0
- package/src/utils/use-tab-indicator.ts +106 -0
- package/tests/commands.test.ts +81 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"name":"button","files":[{"name":"components/button.tsx","content":"import * as React from 'react'\nimport { Spinner } from './spinner'\nimport { flushSync } from 'react-dom'\nimport { composeRenderProps } from 'react-aria-components'\nimport {\n getButtonClasses,\n type ButtonColor,\n type ButtonShape,\n type ButtonSize,\n type ButtonVariant\n} from './helpers/get-button-classes'\nimport { HeadlessButton } from './helpers/headless-button'\nimport { useSpinDelay } from '../utils/use-spin-delay'\nimport { usePreventDefault } from '../utils/use-prevent-default'\nimport { composeTailwindRenderProps, tw } from '../utils/tw'\nimport { useStableAccessor } from '../utils/use-stable-accessor'\nimport { mergeProps } from '@react-aria/utils'\nimport { ButtonContext } from './helpers/button-context'\n\n// --- Public types ---\n\nexport type ButtonProps = {\n variant?: ButtonVariant\n color?: ButtonColor\n size?: ButtonSize\n shape?: ButtonShape\n isToggled?: boolean\n fullWidth?: boolean\n onActionSucceeded?: () => void\n onActionCompleted?: () => void\n disabled?: boolean\n onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void\n className?: string\n children?: React.ReactNode\n value?: string | undefined\n isPending?: boolean\n} & Partial<Pick<React.ComponentProps<'button'>, 'type' | 'role' | 'name' | 'form' | 'id' | 'title' | 'aria-label'>>\n\nexport const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ isToggled, onActionSucceeded, onActionCompleted, isPending: isPendingProp, ...propsRaw }, ref) => {\n const {\n variant = 'contained',\n color = 'primary',\n size = 'md',\n shape = 'oval',\n fullWidth,\n ...props\n } = mergeProps(propsRaw, React.useContext(ButtonContext))\n\n const { isClicking, onClick: isClickingHandler } = useIsClicking(props.onClick)\n\n const isPending = isPendingProp || isClicking\n const isPendingSmoothed = useSpinDelay(isPending, { delay: 0, minDuration: 50 })\n const showSpinner = useSpinDelay(isPending, { delay: 500, minDuration: 200 })\n\n const getOnActionCompleted = useStableAccessor(onActionCompleted)\n const getOnActionSucceeded = useStableAccessor(onActionSucceeded)\n const wasPending = React.useRef(false)\n React.useEffect(() => {\n if (isPendingSmoothed !== wasPending.current) {\n wasPending.current = isPendingSmoothed\n if (!isPendingSmoothed) {\n getOnActionCompleted()?.()\n getOnActionSucceeded()?.()\n }\n }\n }, [isPendingSmoothed, getOnActionCompleted, getOnActionSucceeded])\n\n const onClick = usePreventDefault(isClickingHandler, showSpinner || isClicking)\n\n return (\n <HeadlessButton\n {...props}\n ref={ref}\n data-pending={isPending}\n onClick={onClick}\n className={composeTailwindRenderProps(\n props.className,\n tw(getButtonClasses({ variant, color, size, shape, isToggled }), fullWidth && 'w-full')\n )}\n >\n {composeRenderProps(props.children, (children) => (\n <>\n <span className={tw('inline-flex items-center gap-2', showSpinner && 'opacity-0')}>{children}</span>\n <span\n aria-hidden={!showSpinner ? true : undefined}\n className={tw('absolute inset-0 flex items-center justify-center', !showSpinner && 'opacity-0')}\n >\n <Spinner className=\"size-4\" />\n </span>\n </>\n ))}\n </HeadlessButton>\n )\n }\n)\n\nButton.displayName = 'Button'\n\nfunction useIsClicking<C extends ((event: React.MouseEvent<any, MouseEvent>) => unknown) | undefined>(\n onClickRaw: C\n): { onClick: C; isClicking: boolean } {\n type Event = C extends (event: infer E) => unknown ? E : never\n\n const [isClicking, setIsClicking] = React.useState<boolean>(false)\n\n const onClickRawStabilized = useStableAccessor(onClickRaw)\n\n const onClickDebounced = React.useCallback(\n (event: Event) => {\n try {\n const onClickInner = onClickRawStabilized()\n flushSync(() => setIsClicking(true))\n const result = onClickInner ? onClickInner(event) : null\n Promise.resolve(result).finally(() => {\n setIsClicking(false)\n })\n return result\n } catch (e) {\n setIsClicking(false)\n throw e\n }\n },\n [onClickRawStabilized, setIsClicking]\n )\n\n if (!onClickRaw) {\n return { onClick: undefined as C, isClicking }\n } else {\n return { onClick: onClickDebounced as C, isClicking }\n }\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/spinner.tsx","content":"import { ProgressBar } from 'react-aria-components'\nimport { Icon } from './icon'\nimport { tw } from '../utils/tw'\n\ninterface SpinnerProps {\n className?: string\n 'aria-label'?: string\n}\n\nexport function Spinner({ className, 'aria-label': ariaLabel = 'Loading…' }: SpinnerProps) {\n return (\n <ProgressBar isIndeterminate aria-label={ariaLabel}>\n <Icon name=\"arrow-path\" className={tw('animate-spin', className)} />\n </ProgressBar>\n )\n}\n"},{"name":"components/helpers/button-context.ts","content":"import React from 'react'\nimport type { ButtonVariant, ButtonColor, ButtonSize, ButtonShape } from './get-button-classes'\n\nexport const ButtonContext = React.createContext<{\n variant?: ButtonVariant\n color?: ButtonColor\n size?: ButtonSize\n shape?: ButtonShape\n fullWidth?: boolean\n}>({})\n"},{"name":"components/helpers/get-button-classes.ts","content":"import { tw } from '../../utils/tw'\n\nexport type ButtonVariant = 'contained' | 'outlined' | 'soft' | 'plain'\nexport type ButtonColor = 'primary' | 'negative' | 'positive' | 'notice' | 'neutral'\nexport type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\nexport type ButtonShape = 'rectangle' | 'circle' | 'oval'\n\nconst base =\n 'relative inline-flex items-center justify-center gap-2 font-medium whitespace-nowrap transition-all cursor-default select-none [&_svg]:pointer-events-none [&_svg:not([class*=size-])]:size-4 [&_svg]:shrink-0 data-[disabled]:cursor-not-allowed'\n\nconst sizeClasses: Record<ButtonSize, string> = {\n xs: 'h-7 min-w-14 px-2.5 text-xs',\n sm: 'h-8 min-w-16 px-3 text-sm',\n md: 'h-9 min-w-20 px-4 text-sm',\n lg: 'h-10 min-w-24 px-5 text-base',\n xl: 'h-12 min-w-28 px-6 text-base'\n}\n\nconst shapeClasses: Record<ButtonShape, string> = {\n rectangle: 'rounded',\n circle: 'rounded-full aspect-square px-0',\n oval: 'rounded-full'\n}\n\nconst variantColorClasses: Record<ButtonVariant, Record<ButtonColor, string>> = {\n contained: {\n primary:\n 'bg-primary-600 text-white ring-1 ring-inset ring-primary-700 data-[hovered]:bg-primary-700 data-[hovered]:ring-primary-800 data-[pressed]:bg-primary-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',\n negative:\n 'bg-negative-600 text-white ring-1 ring-inset ring-negative-700 data-[hovered]:bg-negative-700 data-[hovered]:ring-negative-800 data-[pressed]:bg-negative-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',\n positive:\n 'bg-positive-600 text-white ring-1 ring-inset ring-positive-700 data-[hovered]:bg-positive-700 data-[hovered]:ring-positive-800 data-[pressed]:bg-positive-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',\n notice:\n 'bg-notice-600 text-white ring-1 ring-inset ring-notice-700 data-[hovered]:bg-notice-700 data-[hovered]:ring-notice-800 data-[pressed]:bg-notice-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',\n neutral:\n 'bg-neutral-800 text-white ring-1 ring-inset ring-neutral-900 data-[hovered]:bg-neutral-900 data-[pressed]:bg-neutral-950 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none'\n },\n outlined: {\n primary:\n 'bg-white ring-1 ring-inset ring-primary-300 text-primary-700 data-[hovered]:bg-primary-50 data-[hovered]:ring-primary-400 data-[pressed]:bg-primary-100 data-[pressed]:ring-primary-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',\n negative:\n 'bg-white ring-1 ring-inset ring-negative-300 text-negative-700 data-[hovered]:bg-negative-50 data-[hovered]:ring-negative-400 data-[pressed]:bg-negative-100 data-[pressed]:ring-negative-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',\n positive:\n 'bg-white ring-1 ring-inset ring-positive-300 text-positive-700 data-[hovered]:bg-positive-50 data-[hovered]:ring-positive-400 data-[pressed]:bg-positive-100 data-[pressed]:ring-positive-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',\n notice:\n 'bg-white ring-1 ring-inset ring-notice-300 text-notice-700 data-[hovered]:bg-notice-50 data-[hovered]:ring-notice-400 data-[pressed]:bg-notice-100 data-[pressed]:ring-notice-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',\n neutral:\n 'bg-white ring-1 ring-inset ring-neutral-300 text-neutral-700 data-[hovered]:bg-neutral-50 data-[hovered]:ring-neutral-400 data-[pressed]:bg-neutral-100 data-[pressed]:ring-neutral-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none'\n },\n soft: {\n primary:\n 'bg-primary-100 text-primary-800 data-[hovered]:bg-primary-200 data-[pressed]:bg-primary-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',\n negative:\n 'bg-negative-100 text-negative-800 data-[hovered]:bg-negative-200 data-[pressed]:bg-negative-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',\n positive:\n 'bg-positive-100 text-positive-800 data-[hovered]:bg-positive-200 data-[pressed]:bg-positive-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',\n notice:\n 'bg-notice-100 text-notice-800 data-[hovered]:bg-notice-200 data-[pressed]:bg-notice-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',\n neutral:\n 'bg-neutral-200 text-neutral-800 data-[hovered]:bg-neutral-300 data-[pressed]:bg-neutral-400 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400'\n },\n plain: {\n primary:\n 'bg-transparent text-primary-700 data-[hovered]:bg-primary-50 data-[pressed]:bg-primary-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',\n negative:\n 'bg-transparent text-negative-700 data-[hovered]:bg-negative-50 data-[pressed]:bg-negative-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',\n positive:\n 'bg-transparent text-positive-700 data-[hovered]:bg-positive-50 data-[pressed]:bg-positive-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',\n notice:\n 'bg-transparent text-notice-700 data-[hovered]:bg-notice-50 data-[pressed]:bg-notice-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',\n neutral:\n 'bg-transparent text-neutral-700 data-[hovered]:bg-neutral-50 data-[pressed]:bg-neutral-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400'\n }\n}\n\nconst toggledVariantColorClasses: Record<ButtonVariant, Record<ButtonColor, string>> = {\n contained: {\n primary: 'bg-primary-800 ring-primary-900',\n negative: 'bg-negative-800 ring-negative-900',\n positive: 'bg-positive-800 ring-positive-900',\n notice: 'bg-notice-800 ring-notice-900',\n neutral: 'bg-neutral-950'\n },\n outlined: {\n primary: 'bg-primary-100 ring-primary-500',\n negative: 'bg-negative-100 ring-negative-500',\n positive: 'bg-positive-100 ring-positive-500',\n notice: 'bg-notice-100 ring-notice-500',\n neutral: 'bg-neutral-100 ring-neutral-500'\n },\n soft: {\n primary: 'bg-primary-200',\n negative: 'bg-negative-200',\n positive: 'bg-positive-200',\n notice: 'bg-notice-200',\n neutral: 'bg-neutral-300'\n },\n plain: {\n primary: 'bg-primary-100',\n negative: 'bg-negative-100',\n positive: 'bg-positive-100',\n notice: 'bg-notice-100',\n neutral: 'bg-neutral-100'\n }\n}\n\nconst focusClasses =\n 'data-[focus-visible]:outline-2 data-[focus-visible]:outline-offset-2 data-[focus-visible]:outline-primary-600'\n\nexport function getButtonClasses({\n variant = 'contained',\n color = 'primary',\n size = 'md',\n shape = 'oval',\n showSpinner,\n isToggled,\n className\n}: {\n variant?: ButtonVariant\n color?: ButtonColor\n size?: ButtonSize\n shape?: ButtonShape\n showSpinner?: boolean\n isToggled?: boolean\n className?: string\n} = {}) {\n return tw(\n base,\n 'data-[pressed]:scale-[.98]',\n showSpinner ? '[&>:not([data-loader])]:opacity-0 !text-transparent' : '',\n sizeClasses[size],\n shapeClasses[shape],\n variantColorClasses[variant][color],\n isToggled && toggledVariantColorClasses[variant][color],\n focusClasses,\n className\n )\n}\n"},{"name":"components/helpers/headless-button.tsx","content":"import { composeRefs } from '../../utils/compose-refs'\nimport { type WithRenderProps, useRenderProps } from '../../utils/use-render-props'\n\nimport React from 'react'\nimport { usePress } from '@react-aria/interactions'\nimport { mergeProps } from '@react-aria/utils'\n\nexport const HeadlessButton = React.forwardRef<\n HTMLButtonElement,\n WithRenderProps<React.ComponentProps<'button'>, { isPressed: boolean }>\n>((rawProps, externalRef) => {\n const myRef = React.useRef<HTMLButtonElement>(null)\n const { isPressed, pressProps } = usePress({ ref: myRef, isDisabled: rawProps.disabled })\n const renderProps = React.useMemo(\n () => ({\n isPressed\n }),\n [isPressed]\n )\n\n const props = useRenderProps(mergeProps(pressProps, rawProps), renderProps, ['children', 'style', 'className'])\n const ref = React.useMemo(() => composeRefs(myRef, externalRef), [myRef, externalRef])\n\n return (\n <button\n type=\"button\"\n {...props}\n {...(props.disabled || props['data-disabled'] ? { ['data-disabled']: true } : {})}\n {...((isPressed && !props.disabled) || props['data-pressed'] ? { ['data-pressed']: true } : {})}\n aria-pressed={isPressed && !props.disabled}\n ref={ref}\n />\n )\n})\n\nHeadlessButton.displayName = 'HeadlessButton'\n"},{"name":"utils/compose-refs.ts","content":"import React from 'react'\n\ntype PossibleRef<T> = React.Ref<T> | undefined\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n if (typeof ref === 'function') {\n ref(value)\n } else if (ref !== null && ref !== undefined) {\n ;(ref as React.MutableRefObject<T>).current = value\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nexport function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n return (node: T) => refs.forEach((ref) => setRef(ref, node))\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nexport function useComposedRefs<T>(...refs: PossibleRef<T>[]) {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return React.useCallback(composeRefs(...refs), refs)\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"},{"name":"utils/use-prevent-default.ts","content":"import React from 'react'\nimport { useStableAccessor } from './use-stable-accessor'\n\nexport function usePreventDefault<E extends React.SyntheticEvent<any>>(\n eventHandler: React.EventHandler<E> | undefined,\n preventDefault: boolean\n): React.EventHandler<E> {\n const getShouldPreventDefault = useStableAccessor(preventDefault)\n const getEventHandler = useStableAccessor(eventHandler)\n\n const myEventHandler = React.useCallback(\n (event: E) => {\n if (getShouldPreventDefault()) {\n event.preventDefault()\n event.stopPropagation()\n return\n } else {\n const eventHandler = getEventHandler()\n if (eventHandler) {\n return eventHandler(event)\n }\n }\n },\n [getEventHandler, getShouldPreventDefault]\n )\n return myEventHandler\n}\n"},{"name":"utils/use-render-props.ts","content":"import React from 'react'\n\ntype Prettify<T> = { [K in keyof T]: T[K] } & {}\n\ntype DefaultRenderPropKeys<Props> = Extract<'children' | 'className' | 'style', keyof Props>\n\ntype MaybeRenderProp<Value, RenderProps> = Value | ((renderProps: RenderProps) => Value)\n\nexport type WithRenderProps<\n Props extends Record<string, any>,\n RenderProps,\n Keys extends keyof Props = DefaultRenderPropKeys<Props>\n> = Prettify<{\n [K in keyof Props]: K extends Keys ? MaybeRenderProp<Props[K], RenderProps> : Props[K]\n}>\n\nexport function useRenderProps<\n Props extends Record<string, any>,\n RenderProps,\n Keys extends keyof Props = DefaultRenderPropKeys<Props>\n>(props: Props, renderProps: RenderProps, keys: Keys[]) {\n const withRenderProps = React.useMemo(\n () =>\n Object.fromEntries(\n keys\n .map((key) => {\n if (key in props && typeof props[key] === 'function') {\n return [key, props[key](renderProps)]\n } else {\n return []\n }\n })\n .filter((k) => k.length)\n ),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [...keys, renderProps, props, ...keys.map((key) => props[key])]\n )\n return { ...props, ...withRenderProps }\n}\n"},{"name":"utils/use-spin-delay.ts","content":"import React from 'react'\n\nexport function useSpinDelay(loading: boolean, { delay = 500, minDuration = 200 } = {}) {\n const [show, setShow] = React.useState(false)\n const delayTimer = React.useRef<ReturnType<typeof setTimeout>>(undefined)\n const minTimer = React.useRef<ReturnType<typeof setTimeout>>(undefined)\n\n React.useEffect(() => {\n if (loading) {\n delayTimer.current = setTimeout(() => setShow(true), delay)\n } else {\n clearTimeout(delayTimer.current)\n if (show) {\n minTimer.current = setTimeout(() => setShow(false), minDuration)\n }\n }\n return () => {\n clearTimeout(delayTimer.current)\n clearTimeout(minTimer.current)\n }\n }, [loading, delay, minDuration, show])\n\n return show\n}\n"},{"name":"utils/use-stable-accessor.ts","content":"import React from 'react'\n\nexport function useStableAccessor<T>(value: T) {\n const initialPersisterKey = React.useRef<{ value: T }>({ value })\n const getValue = React.useCallback(() => {\n return initialPersisterKey.current.value\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [initialPersisterKey.current])\n initialPersisterKey.current.value = value\n return getValue\n}\n"}]},{"name":"icon","files":[{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"}]},{"name":"headless-file-input","files":[{"name":"components/headless-file-input.tsx","content":"import React from 'react'\nimport { useComposedRefs } from '../utils/compose-refs'\n\nexport type HeadlessFileInputProps = {\n children?: React.ReactNode\n} & (\n | {\n multiple: true\n value?: (string | File)[]\n defaultValue?: (string | File)[]\n onChange?(value: (string | File)[]): unknown\n }\n | {\n multiple?: false\n value?: string | File | null\n defaultValue?: string | File | null\n onChange?(value: string | File | null): unknown\n }\n)\n\ntype FileInputContext =\n | { multiple?: false; value: File | string | null; onChange(value: File | string | null): unknown }\n | { multiple: true; value: (File | string)[]; onChange(value: (File | string)[]): unknown }\n\nconst fileInputContext = React.createContext<FileInputContext>({\n multiple: false,\n value: null,\n onChange: () => null\n})\n\nfunction FileInputContainer({ multiple, value, defaultValue, onChange, children }: HeadlessFileInputProps) {\n const controlled = useControlled({ multiple, value, defaultValue, onChange }) as FileInputContext\n return <fileInputContext.Provider value={controlled}>{children}</fileInputContext.Provider>\n}\n\nFileInputContainer.displayName = 'HeadlessFileInput'\n\nconst FileInputDropZone = React.forwardRef<\n HTMLLabelElement,\n {\n name?: string\n children: ((props: { dragOver: boolean }) => React.ReactElement) | React.ReactElement\n inputId?: string\n accept?: string | undefined\n onDragChange?(value: boolean): unknown\n capture?: React.InputHTMLAttributes<'file'>['capture']\n disabled?: React.InputHTMLAttributes<'file'>['disabled']\n processFile?: (file: File) => Promise<File>\n inputRef?: React.Ref<HTMLInputElement>\n } & Omit<React.ComponentProps<'label'>, 'children'>\n>(({ children, inputId, inputRef, onDragChange, name, accept, capture, disabled, processFile, ...props }, ref) => {\n const { onChange, value, multiple } = React.useContext(fileInputContext)\n const [dragOver, setDragOver] = React.useState(false)\n const startDrag = React.useCallback(\n (e: React.DragEvent<HTMLInputElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(true)\n onDragChange && onDragChange(true)\n },\n [setDragOver, onDragChange]\n )\n const stopDrag = React.useCallback(\n (e: React.DragEvent<HTMLInputElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(false)\n onDragChange && onDragChange(false)\n },\n [setDragOver, onDragChange]\n )\n\n const childrenResult = typeof children === 'function' ? children({ dragOver }) : children\n const childProps = childrenResult.props as Record<string, unknown>\n const cloned = React.cloneElement(\n childrenResult,\n {\n ...childProps\n },\n [\n ...React.Children.toArray(childProps.children as React.ReactNode),\n <FileInputInput\n key=\"file-input-hidden\"\n id={inputId}\n name={name}\n accept={accept}\n ref={inputRef}\n style={{ width: '0.1px', height: '0.1px', opacity: 0, overflow: 'hidden', position: 'absolute', zIndex: -10 }}\n capture={capture}\n disabled={disabled}\n processFile={processFile}\n />\n ]\n )\n\n return (\n <label {...props} ref={ref}>\n <div\n onDrag={cancel}\n onDragStart={cancel}\n onDragOver={cancel}\n onDragEnter={startDrag}\n onDragLeave={stopDrag}\n onDrop={(e) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(false)\n onDragChange && onDragChange(false)\n if (e.dataTransfer.files.length) {\n if (multiple) {\n onChange([...value, ...Array.from(e.dataTransfer.files)])\n } else {\n onChange(e.dataTransfer.files.item(0) || null)\n }\n } else {\n const uri = e.dataTransfer.getData('text/uri-list')\n if (uri) {\n fetchFileFromUri(uri).then((file) => {\n if (multiple) {\n onChange([...value, file])\n } else {\n onChange(file)\n }\n })\n }\n }\n }}\n >\n {cloned}\n </div>\n </label>\n )\n})\nFileInputDropZone.displayName = 'FileInputDropZone'\n\nconst FileInputInput = React.forwardRef<\n HTMLInputElement,\n React.ComponentPropsWithoutRef<'input'> & { processFile?: (file: File) => Promise<File> }\n>(({ name, processFile, form, ...props }, refProp) => {\n const { multiple, value, onChange } = React.useContext(fileInputContext)\n const localRef = React.useRef<HTMLInputElement>(null)\n const ref = useComposedRefs(localRef, refProp)\n\n React.useEffect(() => {\n if (localRef.current) {\n const dt = new DataTransfer()\n if (Array.isArray(value)) {\n value.forEach(async (v) => v instanceof File && dt.items.add(v))\n } else if (value && value instanceof File) {\n dt.items.add(value)\n }\n localRef.current.files = dt.files\n }\n }, [value, processFile])\n\n const urls = React.useMemo(() => {\n if (Array.isArray(value)) {\n return value.map((v, i) => (typeof v === 'string' ? { name: `${name}.${i}`, value: v } : null)).filter(Boolean) as {\n name: string\n value: string\n }[]\n } else if (value && typeof value === 'string') {\n return [{ name: name, value: value }]\n } else {\n return []\n }\n }, [value, name])\n\n const hasFiles =\n typeof value !== 'string' &&\n !!value &&\n !(Array.isArray(value) && !value.filter((v) => v && typeof v !== 'string').length)\n const hasUrls = !!urls.length\n\n return (\n <>\n <input\n {...props}\n type=\"file\"\n form={form}\n ref={hasFiles ? ref : undefined}\n name={hasFiles ? name : undefined}\n onChange={async (e) => {\n const newValue = (\n multiple\n ? e.currentTarget.files\n ? [\n ...value,\n ...(await Promise.all(\n Array.from(e.currentTarget.files).map(async (file) => (processFile ? processFile(file) : file))\n ))\n ]\n : []\n : processFile && e.currentTarget.files?.item(0)\n ? await processFile(e.currentTarget.files?.item(0) as File)\n : e.currentTarget.files?.item(0)\n ) as any\n\n onChange(newValue)\n }}\n multiple={multiple}\n />\n {urls.map((url, i) => (\n <input\n type=\"hidden\"\n ref={i === 0 && !hasFiles ? ref : undefined}\n form={form}\n value={url.value}\n name={url.name}\n key={url.name}\n />\n ))}\n {!hasUrls && !hasFiles ? <input type=\"hidden\" ref={ref} form={form} name={name} value=\"\" /> : null}\n </>\n )\n})\nFileInputInput.displayName = 'FileInputInput'\n\nfunction FilePreview({\n children\n}: {\n children(\n props: {\n file: File | null\n fileUrl: string\n remove(): unknown\n replace(newFile: File | string): unknown\n }[]\n ): React.ReactNode\n}) {\n const { value, onChange } = React.useContext(fileInputContext)\n const urlMap = useFileUrls(value)\n const removeSingle = React.useCallback(() => onChange(null as any), [onChange])\n const replaceSingle = React.useCallback((file: File | string) => onChange(file as any), [onChange])\n\n const removeMultiple = React.useCallback(\n (index: number) => onChange((value as File[]).filter((f, i) => i !== index) as any),\n [value, onChange]\n )\n const replaceMultiple = React.useCallback(\n (index: number, file: File | string) =>\n onChange((value as (File | string)[]).map((f, i) => (i === index ? file : f)) as any),\n [value, onChange]\n )\n\n if (!value) {\n return <>{children([])}</>\n } else if (Array.isArray(value)) {\n return (\n <>\n {children(\n value.map((v, i) => ({\n file: typeof v === 'string' ? null : v,\n fileUrl: urlMap.get(v) ?? (typeof v === 'string' ? v : ''),\n remove: () => removeMultiple(i),\n replace: (f) => replaceMultiple(i, f)\n }))\n )}\n </>\n )\n } else {\n return (\n <>\n {children([\n {\n file: typeof value === 'string' ? null : value,\n fileUrl: urlMap.get(value) ?? (typeof value === 'string' ? value : ''),\n remove: removeSingle,\n replace: replaceSingle\n }\n ])}\n </>\n )\n }\n}\n\nexport const HeadlessFileInput = Object.assign(FileInputContainer, {\n Input: FileInputInput,\n Preview: FilePreview,\n DropZone: FileInputDropZone\n})\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction cancel(e: React.SyntheticEvent) {\n e.stopPropagation()\n e.preventDefault()\n}\n\nfunction useControlled<T extends string | File | null | (string | File)[]>({\n defaultValue,\n value,\n onChange: onChangeRaw,\n multiple\n}: {\n value?: T\n defaultValue?: T\n onChange?(value: null | string | File | (string | File)[]): unknown\n multiple?: boolean\n}) {\n const [state, setState] = React.useState(defaultValue)\n\n const onChange = React.useCallback(\n (v: T) => {\n setState(v)\n onChangeRaw && onChangeRaw(v)\n },\n [onChangeRaw, setState]\n )\n\n const proposed = value === undefined ? state : value\n\n return { value: !proposed && multiple ? [] : proposed || null, onChange, multiple: !!multiple }\n}\n\nfunction useFileUrls(value: (File | string)[] | File | string | null): Map<File | string, string> {\n const urlMapRef = React.useRef(new Map<File | string, string>())\n\n const items = value === null ? [] : Array.isArray(value) ? value : [value]\n\n // Create blob URLs for new items only\n for (const item of items) {\n if (!urlMapRef.current.has(item)) {\n urlMapRef.current.set(item, typeof item === 'string' ? item : URL.createObjectURL(item))\n }\n }\n\n // Prune stale entries and revoke their blob URLs\n const currentKeys = new Set(items)\n React.useEffect(() => {\n for (const [key, url] of urlMapRef.current) {\n if (!currentKeys.has(key)) {\n if (typeof key !== 'string') URL.revokeObjectURL(url)\n urlMapRef.current.delete(key)\n }\n }\n })\n\n // Revoke all blob URLs on unmount\n React.useEffect(() => {\n const map = urlMapRef.current\n return () => {\n for (const [key, url] of map) {\n if (typeof key !== 'string') URL.revokeObjectURL(url)\n }\n map.clear()\n }\n }, [])\n\n return urlMapRef.current\n}\n\nasync function fetchFileFromUri(uri: string): Promise<File> {\n const res = await fetch(uri)\n const blob = await res.blob()\n const contentType = res.headers.get('content-type') || blob.type || ''\n const contentDisposition = res.headers.get('content-disposition')\n let filename = new URL(uri).pathname.split('/').pop() || 'file'\n const match = contentDisposition?.match(/filename\\*?=(?:UTF-8'')?[\"']?([^\"';\\n]+)/)\n if (match) filename = decodeURIComponent(match[1]!)\n return new File([blob], filename, { type: contentType })\n}\n"},{"name":"utils/compose-refs.ts","content":"import React from 'react'\n\ntype PossibleRef<T> = React.Ref<T> | undefined\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n if (typeof ref === 'function') {\n ref(value)\n } else if (ref !== null && ref !== undefined) {\n ;(ref as React.MutableRefObject<T>).current = value\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nexport function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n return (node: T) => refs.forEach((ref) => setRef(ref, node))\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nexport function useComposedRefs<T>(...refs: PossibleRef<T>[]) {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return React.useCallback(composeRefs(...refs), refs)\n}\n"}]},{"name":"button-link","files":[{"name":"components/button-link.tsx","content":"import * as React from 'react'\nimport { Link } from 'react-router'\nimport { usePress } from '@react-aria/interactions'\nimport { mergeProps } from '@react-aria/utils'\nimport { Spinner } from './spinner'\nimport {\n getButtonClasses,\n type ButtonColor,\n type ButtonShape,\n type ButtonSize,\n type ButtonVariant\n} from './helpers/get-button-classes'\nimport { useSpinDelay } from '../utils/use-spin-delay'\nimport { tw } from '../utils/tw'\nimport { ButtonContext } from './helpers/button-context'\n\nexport type ButtonLinkProps = React.ComponentPropsWithoutRef<typeof Link> & {\n size?: ButtonSize\n variant?: ButtonVariant\n color?: ButtonColor\n shape?: ButtonShape\n disabled?: boolean\n fullWidth?: boolean\n isPending?: boolean\n}\n\nexport const ButtonLink = React.forwardRef<HTMLAnchorElement, ButtonLinkProps>(\n ({ disabled, fullWidth, isPending: isPendingProp, children, onClick, className, ...propsRaw }, ref) => {\n const {\n size = 'md',\n variant = 'contained',\n color = 'primary',\n shape = 'oval',\n ...props\n } = mergeProps(propsRaw, React.useContext(ButtonContext))\n const { isPressed, pressProps } = usePress({ isDisabled: disabled })\n\n const isPending = isPendingProp ?? false\n const showSpinner = useSpinDelay(isPending, { delay: 500, minDuration: 200 })\n\n const handleClick = disabled || isPending ? (e: React.MouseEvent<HTMLAnchorElement>) => e.preventDefault() : onClick\n\n return (\n <Link\n {...mergeProps(props, pressProps, { onClick: handleClick })}\n ref={ref}\n data-pending={isPending || undefined}\n data-disabled={disabled || undefined}\n data-pressed={(isPressed && !disabled) || undefined}\n aria-disabled={disabled || undefined}\n className={getButtonClasses({\n size,\n variant,\n color,\n shape,\n className: tw(fullWidth && 'w-full', className)\n })}\n >\n <span className={tw('inline-flex items-center gap-2', showSpinner && 'opacity-0')}>{children}</span>\n <span\n aria-hidden={!showSpinner ? true : undefined}\n className={tw('absolute inset-0 flex items-center justify-center', !showSpinner && 'opacity-0')}\n >\n <Spinner className=\"size-4\" />\n </span>\n </Link>\n )\n }\n)\n\nButtonLink.displayName = 'ButtonLink'\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/spinner.tsx","content":"import { ProgressBar } from 'react-aria-components'\nimport { Icon } from './icon'\nimport { tw } from '../utils/tw'\n\ninterface SpinnerProps {\n className?: string\n 'aria-label'?: string\n}\n\nexport function Spinner({ className, 'aria-label': ariaLabel = 'Loading…' }: SpinnerProps) {\n return (\n <ProgressBar isIndeterminate aria-label={ariaLabel}>\n <Icon name=\"arrow-path\" className={tw('animate-spin', className)} />\n </ProgressBar>\n )\n}\n"},{"name":"components/helpers/button-context.ts","content":"import React from 'react'\nimport type { ButtonVariant, ButtonColor, ButtonSize, ButtonShape } from './get-button-classes'\n\nexport const ButtonContext = React.createContext<{\n variant?: ButtonVariant\n color?: ButtonColor\n size?: ButtonSize\n shape?: ButtonShape\n fullWidth?: boolean\n}>({})\n"},{"name":"components/helpers/get-button-classes.ts","content":"import { tw } from '../../utils/tw'\n\nexport type ButtonVariant = 'contained' | 'outlined' | 'soft' | 'plain'\nexport type ButtonColor = 'primary' | 'negative' | 'positive' | 'notice' | 'neutral'\nexport type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\nexport type ButtonShape = 'rectangle' | 'circle' | 'oval'\n\nconst base =\n 'relative inline-flex items-center justify-center gap-2 font-medium whitespace-nowrap transition-all cursor-default select-none [&_svg]:pointer-events-none [&_svg:not([class*=size-])]:size-4 [&_svg]:shrink-0 data-[disabled]:cursor-not-allowed'\n\nconst sizeClasses: Record<ButtonSize, string> = {\n xs: 'h-7 min-w-14 px-2.5 text-xs',\n sm: 'h-8 min-w-16 px-3 text-sm',\n md: 'h-9 min-w-20 px-4 text-sm',\n lg: 'h-10 min-w-24 px-5 text-base',\n xl: 'h-12 min-w-28 px-6 text-base'\n}\n\nconst shapeClasses: Record<ButtonShape, string> = {\n rectangle: 'rounded',\n circle: 'rounded-full aspect-square px-0',\n oval: 'rounded-full'\n}\n\nconst variantColorClasses: Record<ButtonVariant, Record<ButtonColor, string>> = {\n contained: {\n primary:\n 'bg-primary-600 text-white ring-1 ring-inset ring-primary-700 data-[hovered]:bg-primary-700 data-[hovered]:ring-primary-800 data-[pressed]:bg-primary-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',\n negative:\n 'bg-negative-600 text-white ring-1 ring-inset ring-negative-700 data-[hovered]:bg-negative-700 data-[hovered]:ring-negative-800 data-[pressed]:bg-negative-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',\n positive:\n 'bg-positive-600 text-white ring-1 ring-inset ring-positive-700 data-[hovered]:bg-positive-700 data-[hovered]:ring-positive-800 data-[pressed]:bg-positive-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',\n notice:\n 'bg-notice-600 text-white ring-1 ring-inset ring-notice-700 data-[hovered]:bg-notice-700 data-[hovered]:ring-notice-800 data-[pressed]:bg-notice-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',\n neutral:\n 'bg-neutral-800 text-white ring-1 ring-inset ring-neutral-900 data-[hovered]:bg-neutral-900 data-[pressed]:bg-neutral-950 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none'\n },\n outlined: {\n primary:\n 'bg-white ring-1 ring-inset ring-primary-300 text-primary-700 data-[hovered]:bg-primary-50 data-[hovered]:ring-primary-400 data-[pressed]:bg-primary-100 data-[pressed]:ring-primary-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',\n negative:\n 'bg-white ring-1 ring-inset ring-negative-300 text-negative-700 data-[hovered]:bg-negative-50 data-[hovered]:ring-negative-400 data-[pressed]:bg-negative-100 data-[pressed]:ring-negative-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',\n positive:\n 'bg-white ring-1 ring-inset ring-positive-300 text-positive-700 data-[hovered]:bg-positive-50 data-[hovered]:ring-positive-400 data-[pressed]:bg-positive-100 data-[pressed]:ring-positive-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',\n notice:\n 'bg-white ring-1 ring-inset ring-notice-300 text-notice-700 data-[hovered]:bg-notice-50 data-[hovered]:ring-notice-400 data-[pressed]:bg-notice-100 data-[pressed]:ring-notice-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',\n neutral:\n 'bg-white ring-1 ring-inset ring-neutral-300 text-neutral-700 data-[hovered]:bg-neutral-50 data-[hovered]:ring-neutral-400 data-[pressed]:bg-neutral-100 data-[pressed]:ring-neutral-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none'\n },\n soft: {\n primary:\n 'bg-primary-100 text-primary-800 data-[hovered]:bg-primary-200 data-[pressed]:bg-primary-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',\n negative:\n 'bg-negative-100 text-negative-800 data-[hovered]:bg-negative-200 data-[pressed]:bg-negative-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',\n positive:\n 'bg-positive-100 text-positive-800 data-[hovered]:bg-positive-200 data-[pressed]:bg-positive-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',\n notice:\n 'bg-notice-100 text-notice-800 data-[hovered]:bg-notice-200 data-[pressed]:bg-notice-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',\n neutral:\n 'bg-neutral-200 text-neutral-800 data-[hovered]:bg-neutral-300 data-[pressed]:bg-neutral-400 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400'\n },\n plain: {\n primary:\n 'bg-transparent text-primary-700 data-[hovered]:bg-primary-50 data-[pressed]:bg-primary-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',\n negative:\n 'bg-transparent text-negative-700 data-[hovered]:bg-negative-50 data-[pressed]:bg-negative-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',\n positive:\n 'bg-transparent text-positive-700 data-[hovered]:bg-positive-50 data-[pressed]:bg-positive-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',\n notice:\n 'bg-transparent text-notice-700 data-[hovered]:bg-notice-50 data-[pressed]:bg-notice-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',\n neutral:\n 'bg-transparent text-neutral-700 data-[hovered]:bg-neutral-50 data-[pressed]:bg-neutral-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400'\n }\n}\n\nconst toggledVariantColorClasses: Record<ButtonVariant, Record<ButtonColor, string>> = {\n contained: {\n primary: 'bg-primary-800 ring-primary-900',\n negative: 'bg-negative-800 ring-negative-900',\n positive: 'bg-positive-800 ring-positive-900',\n notice: 'bg-notice-800 ring-notice-900',\n neutral: 'bg-neutral-950'\n },\n outlined: {\n primary: 'bg-primary-100 ring-primary-500',\n negative: 'bg-negative-100 ring-negative-500',\n positive: 'bg-positive-100 ring-positive-500',\n notice: 'bg-notice-100 ring-notice-500',\n neutral: 'bg-neutral-100 ring-neutral-500'\n },\n soft: {\n primary: 'bg-primary-200',\n negative: 'bg-negative-200',\n positive: 'bg-positive-200',\n notice: 'bg-notice-200',\n neutral: 'bg-neutral-300'\n },\n plain: {\n primary: 'bg-primary-100',\n negative: 'bg-negative-100',\n positive: 'bg-positive-100',\n notice: 'bg-notice-100',\n neutral: 'bg-neutral-100'\n }\n}\n\nconst focusClasses =\n 'data-[focus-visible]:outline-2 data-[focus-visible]:outline-offset-2 data-[focus-visible]:outline-primary-600'\n\nexport function getButtonClasses({\n variant = 'contained',\n color = 'primary',\n size = 'md',\n shape = 'oval',\n showSpinner,\n isToggled,\n className\n}: {\n variant?: ButtonVariant\n color?: ButtonColor\n size?: ButtonSize\n shape?: ButtonShape\n showSpinner?: boolean\n isToggled?: boolean\n className?: string\n} = {}) {\n return tw(\n base,\n 'data-[pressed]:scale-[.98]',\n showSpinner ? '[&>:not([data-loader])]:opacity-0 !text-transparent' : '',\n sizeClasses[size],\n shapeClasses[shape],\n variantColorClasses[variant][color],\n isToggled && toggledVariantColorClasses[variant][color],\n focusClasses,\n className\n )\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"},{"name":"utils/use-spin-delay.ts","content":"import React from 'react'\n\nexport function useSpinDelay(loading: boolean, { delay = 500, minDuration = 200 } = {}) {\n const [show, setShow] = React.useState(false)\n const delayTimer = React.useRef<ReturnType<typeof setTimeout>>(undefined)\n const minTimer = React.useRef<ReturnType<typeof setTimeout>>(undefined)\n\n React.useEffect(() => {\n if (loading) {\n delayTimer.current = setTimeout(() => setShow(true), delay)\n } else {\n clearTimeout(delayTimer.current)\n if (show) {\n minTimer.current = setTimeout(() => setShow(false), minDuration)\n }\n }\n return () => {\n clearTimeout(delayTimer.current)\n clearTimeout(minTimer.current)\n }\n }, [loading, delay, minDuration, show])\n\n return show\n}\n"}]},{"name":"button-group","files":[{"name":"components/button-group.tsx","content":"import * as React from 'react'\nimport { mergeProps } from '@react-aria/utils'\nimport { tw } from '../utils/tw'\n\nexport type ButtonGroupOrientation = 'horizontal' | 'vertical'\nexport type ButtonGroupAlignment = 'start' | 'center' | 'end'\nexport type ButtonGroupSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\nexport type ButtonGroupProps = {\n orientation?: ButtonGroupOrientation\n alignment?: ButtonGroupAlignment\n size?: ButtonGroupSize\n className?: string\n children?: React.ReactNode\n 'aria-label'?: string\n 'aria-labelledby'?: string\n 'aria-describedby'?: string\n fullWidth?: boolean\n}\n\nexport const ButtonGroupContext = React.createContext<{\n orientation?: ButtonGroupOrientation\n alignment?: ButtonGroupAlignment\n size?: ButtonGroupSize\n fullWidth?: boolean\n className?: string\n}>({})\n\nconst sizeGapClasses: Record<ButtonGroupSize, string> = {\n xl: 'gap-4',\n lg: 'gap-3',\n md: 'gap-2',\n sm: 'gap-1',\n xs: 'gap-0.5'\n}\n\nconst alignmentClasses: Record<ButtonGroupAlignment, string> = {\n start: 'justify-start',\n center: 'justify-center',\n end: 'justify-end'\n}\n\nexport function ButtonGroup(inputProps: ButtonGroupProps) {\n const context = React.useContext(ButtonGroupContext)\n const {\n orientation = 'horizontal',\n alignment = 'end',\n size = 'md',\n className,\n children,\n fullWidth,\n ...ariaProps\n } = mergeProps(React.useContext(ButtonGroupContext), inputProps)\n\n return (\n <div\n role=\"group\"\n aria-label={ariaProps['aria-label']}\n aria-labelledby={ariaProps['aria-labelledby']}\n aria-describedby={ariaProps['aria-describedby']}\n className={tw(\n 'inline-flex w-fit',\n sizeGapClasses[size],\n orientation === 'horizontal' ? 'flex-row items-center flex-wrap' : 'flex-col',\n alignmentClasses[alignment],\n fullWidth && 'w-full',\n className\n )}\n >\n {children}\n </div>\n )\n}\n\nButtonGroup.displayName = 'ButtonGroup'\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"toggle-button-group","files":[{"name":"components/toggle-button-group.tsx","content":"import * as React from 'react'\nimport { RadioGroup, Radio } from 'react-aria-components'\nimport { tw } from '../utils/tw'\nimport type { ButtonSize } from './helpers/get-button-classes'\n\n// --- Types ---\n\nexport type ToggleButtonGroupSize = ButtonSize\n\nexport type ToggleButtonGroupProps = {\n value?: string | null\n defaultValue?: string | null\n onChange?: (value: string) => void\n orientation?: 'horizontal' | 'vertical'\n className?: string\n isDisabled?: boolean\n name?: string\n children: React.ReactNode\n 'aria-label'?: string\n 'aria-labelledby'?: string\n}\n\n// --- ToggleButtonGroup ---\n\nexport function ToggleButtonGroup({ orientation = 'horizontal', className, children, ...props }: ToggleButtonGroupProps) {\n return (\n <RadioGroup\n {...props}\n orientation={orientation}\n className={tw(\n 'inline-flex',\n orientation === 'horizontal'\n ? ['flex-row', '[&>*:first-child]:rounded-l [&>*:last-child]:rounded-r', '[&>*:not(:first-child)]:-ml-px']\n : ['flex-col', '[&>*:first-child]:rounded-t [&>*:last-child]:rounded-b', '[&>*:not(:first-child)]:-mt-px'],\n className\n )}\n >\n {children}\n </RadioGroup>\n )\n}\n\nToggleButtonGroup.displayName = 'ToggleButtonGroup'\n\n// --- ToggleButton ---\n\nexport type ToggleButtonProps = {\n value: string\n children: React.ReactNode\n isDisabled?: boolean\n className?: string\n 'aria-label'?: string\n size?: ToggleButtonGroupSize\n}\n\nexport function ToggleButton({ children, className, size = 'md', ...props }: ToggleButtonProps) {\n return (\n <Radio {...props} className={({ isSelected }) => tw(getToggleButtonClasses({ size, isSelected }), className)}>\n {children}\n </Radio>\n )\n}\n\nToggleButton.displayName = 'ToggleButton'\n\n// --- Styling ---\n\nfunction getToggleButtonClasses({ size = 'md', isSelected }: { size?: ButtonSize; isSelected?: boolean }) {\n return tw(\n 'relative rounded-none cursor-default select-none',\n 'font-medium transition-all',\n 'data-[focus-visible]:outline data-[focus-visible]:outline-2 data-[focus-visible]:outline-offset-2',\n 'data-[disabled]:bg-neutral-50 data-[disabled]:ring-neutral-50 data-[disabled]:cursor-not-allowed data-[disabled]:text-neutral-400',\n\n // Size\n size === 'xs' && 'px-3.5 py-1.5 text-xs gap-x-1 min-w-6',\n size === 'sm' && 'px-4 py-1.5 text-sm gap-x-1.5 min-w-7',\n size === 'md' && 'px-5 py-2 text-sm gap-x-2 min-w-8',\n size === 'lg' && 'px-6 py-2.5 text-base gap-x-2 min-w-9',\n size === 'xl' && 'px-8 py-3 text-base gap-x-2.5 min-w-10',\n\n // Unselected\n !isSelected && 'bg-white ring-1 ring-inset ring-neutral-300',\n !isSelected && 'data-[hovered]:bg-neutral-50 data-[hovered]:ring-neutral-400',\n !isSelected && 'text-neutral-700 data-[focus-visible]:outline-neutral-400',\n\n // Selected\n isSelected && 'bg-primary-500 text-white ring-1 ring-inset ring-primary-700',\n isSelected && 'data-[hovered]:bg-primary-600 data-[hovered]:ring-primary-700',\n isSelected && 'data-[focus-visible]:outline-primary-500',\n\n 'inline-flex justify-center items-center gap-x-2'\n )\n}\n"},{"name":"components/helpers/get-button-classes.ts","content":"import { tw } from '../../utils/tw'\n\nexport type ButtonVariant = 'contained' | 'outlined' | 'soft' | 'plain'\nexport type ButtonColor = 'primary' | 'negative' | 'positive' | 'notice' | 'neutral'\nexport type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\nexport type ButtonShape = 'rectangle' | 'circle' | 'oval'\n\nconst base =\n 'relative inline-flex items-center justify-center gap-2 font-medium whitespace-nowrap transition-all cursor-default select-none [&_svg]:pointer-events-none [&_svg:not([class*=size-])]:size-4 [&_svg]:shrink-0 data-[disabled]:cursor-not-allowed'\n\nconst sizeClasses: Record<ButtonSize, string> = {\n xs: 'h-7 min-w-14 px-2.5 text-xs',\n sm: 'h-8 min-w-16 px-3 text-sm',\n md: 'h-9 min-w-20 px-4 text-sm',\n lg: 'h-10 min-w-24 px-5 text-base',\n xl: 'h-12 min-w-28 px-6 text-base'\n}\n\nconst shapeClasses: Record<ButtonShape, string> = {\n rectangle: 'rounded',\n circle: 'rounded-full aspect-square px-0',\n oval: 'rounded-full'\n}\n\nconst variantColorClasses: Record<ButtonVariant, Record<ButtonColor, string>> = {\n contained: {\n primary:\n 'bg-primary-600 text-white ring-1 ring-inset ring-primary-700 data-[hovered]:bg-primary-700 data-[hovered]:ring-primary-800 data-[pressed]:bg-primary-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',\n negative:\n 'bg-negative-600 text-white ring-1 ring-inset ring-negative-700 data-[hovered]:bg-negative-700 data-[hovered]:ring-negative-800 data-[pressed]:bg-negative-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',\n positive:\n 'bg-positive-600 text-white ring-1 ring-inset ring-positive-700 data-[hovered]:bg-positive-700 data-[hovered]:ring-positive-800 data-[pressed]:bg-positive-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',\n notice:\n 'bg-notice-600 text-white ring-1 ring-inset ring-notice-700 data-[hovered]:bg-notice-700 data-[hovered]:ring-notice-800 data-[pressed]:bg-notice-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',\n neutral:\n 'bg-neutral-800 text-white ring-1 ring-inset ring-neutral-900 data-[hovered]:bg-neutral-900 data-[pressed]:bg-neutral-950 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none'\n },\n outlined: {\n primary:\n 'bg-white ring-1 ring-inset ring-primary-300 text-primary-700 data-[hovered]:bg-primary-50 data-[hovered]:ring-primary-400 data-[pressed]:bg-primary-100 data-[pressed]:ring-primary-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',\n negative:\n 'bg-white ring-1 ring-inset ring-negative-300 text-negative-700 data-[hovered]:bg-negative-50 data-[hovered]:ring-negative-400 data-[pressed]:bg-negative-100 data-[pressed]:ring-negative-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',\n positive:\n 'bg-white ring-1 ring-inset ring-positive-300 text-positive-700 data-[hovered]:bg-positive-50 data-[hovered]:ring-positive-400 data-[pressed]:bg-positive-100 data-[pressed]:ring-positive-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',\n notice:\n 'bg-white ring-1 ring-inset ring-notice-300 text-notice-700 data-[hovered]:bg-notice-50 data-[hovered]:ring-notice-400 data-[pressed]:bg-notice-100 data-[pressed]:ring-notice-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',\n neutral:\n 'bg-white ring-1 ring-inset ring-neutral-300 text-neutral-700 data-[hovered]:bg-neutral-50 data-[hovered]:ring-neutral-400 data-[pressed]:bg-neutral-100 data-[pressed]:ring-neutral-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none'\n },\n soft: {\n primary:\n 'bg-primary-100 text-primary-800 data-[hovered]:bg-primary-200 data-[pressed]:bg-primary-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',\n negative:\n 'bg-negative-100 text-negative-800 data-[hovered]:bg-negative-200 data-[pressed]:bg-negative-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',\n positive:\n 'bg-positive-100 text-positive-800 data-[hovered]:bg-positive-200 data-[pressed]:bg-positive-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',\n notice:\n 'bg-notice-100 text-notice-800 data-[hovered]:bg-notice-200 data-[pressed]:bg-notice-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',\n neutral:\n 'bg-neutral-200 text-neutral-800 data-[hovered]:bg-neutral-300 data-[pressed]:bg-neutral-400 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400'\n },\n plain: {\n primary:\n 'bg-transparent text-primary-700 data-[hovered]:bg-primary-50 data-[pressed]:bg-primary-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',\n negative:\n 'bg-transparent text-negative-700 data-[hovered]:bg-negative-50 data-[pressed]:bg-negative-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',\n positive:\n 'bg-transparent text-positive-700 data-[hovered]:bg-positive-50 data-[pressed]:bg-positive-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',\n notice:\n 'bg-transparent text-notice-700 data-[hovered]:bg-notice-50 data-[pressed]:bg-notice-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',\n neutral:\n 'bg-transparent text-neutral-700 data-[hovered]:bg-neutral-50 data-[pressed]:bg-neutral-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400'\n }\n}\n\nconst toggledVariantColorClasses: Record<ButtonVariant, Record<ButtonColor, string>> = {\n contained: {\n primary: 'bg-primary-800 ring-primary-900',\n negative: 'bg-negative-800 ring-negative-900',\n positive: 'bg-positive-800 ring-positive-900',\n notice: 'bg-notice-800 ring-notice-900',\n neutral: 'bg-neutral-950'\n },\n outlined: {\n primary: 'bg-primary-100 ring-primary-500',\n negative: 'bg-negative-100 ring-negative-500',\n positive: 'bg-positive-100 ring-positive-500',\n notice: 'bg-notice-100 ring-notice-500',\n neutral: 'bg-neutral-100 ring-neutral-500'\n },\n soft: {\n primary: 'bg-primary-200',\n negative: 'bg-negative-200',\n positive: 'bg-positive-200',\n notice: 'bg-notice-200',\n neutral: 'bg-neutral-300'\n },\n plain: {\n primary: 'bg-primary-100',\n negative: 'bg-negative-100',\n positive: 'bg-positive-100',\n notice: 'bg-notice-100',\n neutral: 'bg-neutral-100'\n }\n}\n\nconst focusClasses =\n 'data-[focus-visible]:outline-2 data-[focus-visible]:outline-offset-2 data-[focus-visible]:outline-primary-600'\n\nexport function getButtonClasses({\n variant = 'contained',\n color = 'primary',\n size = 'md',\n shape = 'oval',\n showSpinner,\n isToggled,\n className\n}: {\n variant?: ButtonVariant\n color?: ButtonColor\n size?: ButtonSize\n shape?: ButtonShape\n showSpinner?: boolean\n isToggled?: boolean\n className?: string\n} = {}) {\n return tw(\n base,\n 'data-[pressed]:scale-[.98]',\n showSpinner ? '[&>:not([data-loader])]:opacity-0 !text-transparent' : '',\n sizeClasses[size],\n shapeClasses[shape],\n variantColorClasses[variant][color],\n isToggled && toggledVariantColorClasses[variant][color],\n focusClasses,\n className\n )\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"date-picker","files":[{"name":"components/date-picker.tsx","content":"import * as React from 'react'\nimport { DatePicker as AriaDatePicker, DateInput, DateSegment, Button, Dialog, I18nProvider } from 'react-aria-components'\nimport { AnimatedPopover } from './helpers/animated-popover'\nimport { CalendarDate, parseDate } from '@internationalized/date'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { Icon } from './icon'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { CustomCalendar } from './helpers/calendar-month-year-picker'\nimport { dateFns, type Iso } from 'iso-fns'\n\nexport type DatePickerProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<Iso.Date | null>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management (ISO date string YYYY-MM-DD)\n value?: Iso.Date | null\n defaultValue?: Iso.Date | null\n onChange?: (value: Iso.Date | null) => void\n\n // Date specific (ISO date strings)\n minValue?: Iso.Date\n maxValue?: Iso.Date\n isDateUnavailable?: (date: Iso.Date) => boolean\n\n // Calendar display\n locale?: string\n\n // Input shape\n shape?: 'rectangle' | 'oval'\n}\n\nfunction DatePicker_(props: DatePickerProps) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const labelId = React.useId()\n\n // Internal state for CalendarDate\n const [value, _setValue] = React.useState(() => {\n try {\n const startingDate = props.value === undefined ? (props.defaultValue ?? null) : props.value\n return startingDate ? parseDate(startingDate) : null\n } catch {\n return null\n }\n })\n\n function setValue(d: CalendarDate | null) {\n _setValue(d)\n if (!d || dateFns.isValid(d.toString())) {\n props.onChange && props.onChange(d ? (d.toString() as Iso.Date) : null)\n }\n commitValidation()\n }\n\n // Sync controlled value\n if (props.value !== undefined) {\n const currentValueAsIso = value && dateFns.isValid(value.toString()) ? value.toString() : null\n const propValue = props.value && dateFns.isValid(props.value) ? props.value : null\n if ((!value || dateFns.isValid(value.toString())) && propValue !== currentValueAsIso) {\n try {\n _setValue(propValue ? parseDate(propValue) : null)\n } catch {\n _setValue(null)\n }\n }\n }\n\n // Convert min/max dates\n const minDate = React.useMemo(() => {\n if (!props.minValue) return undefined\n try {\n return parseDate(props.minValue)\n } catch {\n return undefined\n }\n }, [props.minValue])\n\n const maxDate = React.useMemo(() => {\n if (!props.maxValue) return undefined\n try {\n return parseDate(props.maxValue)\n } catch {\n return undefined\n }\n }, [props.maxValue])\n\n // Form validation\n const {\n validationMessage: validationMessageRaw,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: value && dateFns.isValid(value.toString()) ? (value.toString() as Iso.Date) : null,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n // Focus handled by AriaDatePicker\n }\n },\n hiddenInputRef as React.RefObject<HTMLInputElement>\n )\n\n const lessThanMin =\n props.minValue && value && dateFns.isValid(value.toString()) ? value.toString() < props.minValue : false\n const greaterThanMax =\n props.maxValue && value && dateFns.isValid(value.toString()) ? value.toString() > props.maxValue : false\n\n const validationMessage =\n validationMessageRaw ||\n (lessThanMin ? `Cannot be less than ${dateFns.format(props.minValue!, 'MM/dd/yyyy')}` : '') ||\n (greaterThanMax ? `Cannot be greater than ${dateFns.format(props.maxValue!, 'MM/dd/yyyy')}` : '')\n\n const isInvalid = props.isInvalid || validationInvalid || lessThanMin || greaterThanMax\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n return (\n <I18nProvider locale={props.locale ?? 'en'}>\n <div className=\"relative\">\n {/* Hidden input for form submission */}\n <HeadlessForm.HiddenInput ref={hiddenInputRef} name={props.name} value={value?.toString() ?? ''} form={props.form} />\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} labelId={labelId} />\n\n <AriaDatePicker\n value={value}\n aria-labelledby={labelId}\n onChange={setValue}\n minValue={minDate}\n maxValue={maxDate}\n isDateUnavailable={\n props.isDateUnavailable ? (date) => props.isDateUnavailable!(date.toString() as Iso.Date) : undefined\n }\n isDisabled={props.isDisabled}\n isReadOnly={props.isReadonly}\n isInvalid={isInvalid}\n isRequired={props.isRequired}\n >\n <FormFieldComponents.FieldGroup\n isDisabled={props.isDisabled}\n isReadonly={props.isReadonly}\n isInvalid={isInvalid}\n hasWarning={hasWarning}\n shape={props.shape}\n className=\"flex\"\n >\n <DateInput className=\"flex-1 grow flex px-3 py-2 text-sm\">\n {(segment) => (\n <DateSegment\n segment={segment}\n className={tw('px-0.5 tabular-nums outline-none rounded-sm', 'focus:bg-primary-500 focus:text-white')}\n />\n )}\n </DateInput>\n <Button className=\"flex items-center px-2 py-2\">\n <Icon name=\"calendar\" className=\"h-4 w-4 text-neutral-400\" />\n </Button>\n </FormFieldComponents.FieldGroup>\n\n <AnimatedPopover>\n <Dialog>\n <CustomCalendar\n value={value}\n onChange={setValue}\n minValue={minDate}\n maxValue={maxDate}\n isDateUnavailable={\n props.isDateUnavailable ? (date) => props.isDateUnavailable!(date.toString() as Iso.Date) : undefined\n }\n />\n </Dialog>\n </AnimatedPopover>\n </AriaDatePicker>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n </I18nProvider>\n )\n}\n\nconst today = dateFns.now()\nexport const DatePicker = Object.assign(DatePicker_, {\n isDateInPast: (date: Iso.Date) => dateFns.isBefore(date, today)\n})\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/animated-popover.tsx","content":"import { Popover, type PopoverProps } from 'react-aria-components'\nimport { tw } from '../../utils/tw'\n\nconst animationClasses = tw(\n 'transition-all duration-150 ease-out',\n // resting state: keep transform properties defined so transitions have endpoints\n 'translate-x-0 translate-y-0',\n // entering: fade + slide from trigger direction\n 'data-[entering]:opacity-0',\n 'data-[entering]:data-[placement=bottom]:-translate-y-1',\n 'data-[entering]:data-[placement=top]:translate-y-1',\n 'data-[entering]:data-[placement=left]:translate-x-1',\n 'data-[entering]:data-[placement=right]:-translate-x-1',\n // exiting: fade + slide toward trigger direction\n 'data-[exiting]:opacity-0',\n 'data-[exiting]:data-[placement=bottom]:-translate-y-1',\n 'data-[exiting]:data-[placement=top]:translate-y-1',\n 'data-[exiting]:data-[placement=left]:translate-x-1',\n 'data-[exiting]:data-[placement=right]:-translate-x-1'\n)\n\nexport function AnimatedPopover({ className, ...props }: PopoverProps) {\n return <Popover {...props} className={tw(animationClasses, className)} />\n}\n"},{"name":"components/helpers/calendar-month-year-picker.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Calendar,\n CalendarGrid,\n CalendarCell,\n Heading,\n CalendarGridHeader,\n CalendarHeaderCell,\n CalendarGridBody\n} from 'react-aria-components'\nimport { CalendarDate, CalendarDateTime } from '@internationalized/date'\nimport { tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\ntype MonthYearPickerProps = {\n currentDate: CalendarDate | CalendarDateTime\n onSelect: (year: number, month: number) => void\n onCancel: () => void\n}\n\nexport function MonthYearPicker({ currentDate, onSelect, onCancel }: MonthYearPickerProps) {\n const [selectedYear, setSelectedYear] = React.useState(currentDate.year)\n const [viewingYear, setViewingYear] = React.useState(currentDate.year)\n\n const months = [\n 'January',\n 'February',\n 'March',\n 'April',\n 'May',\n 'June',\n 'July',\n 'August',\n 'September',\n 'October',\n 'November',\n 'December'\n ]\n\n // Generate year range (current year ± 50 years)\n const startYear = currentDate.year - 50\n const endYear = currentDate.year + 50\n const years = Array.from({ length: endYear - startYear + 1 }, (_, i) => startYear + i)\n\n // Find years to display (show 12 years at a time, 3x4 grid)\n const yearIndex = years.indexOf(viewingYear)\n const displayYears = years.slice(Math.max(0, yearIndex - 5), Math.min(years.length, yearIndex + 7))\n\n const handlePreviousYears = () => {\n setViewingYear((prev) => prev - 12)\n }\n\n const handleNextYears = () => {\n setViewingYear((prev) => prev + 12)\n }\n\n return (\n <div className=\"bg-white border border-neutral-200 rounded-md shadow-lg p-4 w-80\">\n {/* Year selection header */}\n <div className=\"mb-4\">\n <header className=\"flex items-center justify-between mb-3\">\n <Button className=\"p-1 hover:bg-neutral-100 rounded\" onPress={handlePreviousYears}>\n <Icon name=\"chevron-left\" className=\"h-4 w-4\" />\n </Button>\n <div className=\"text-sm font-semibold text-neutral-900\">\n {displayYears[0]} - {displayYears[displayYears.length - 1]}\n </div>\n <Button className=\"p-1 hover:bg-neutral-100 rounded\" onPress={handleNextYears}>\n <Icon name=\"chevron-right\" className=\"h-4 w-4\" />\n </Button>\n </header>\n\n {/* Year grid */}\n <div className=\"grid grid-cols-3 gap-2\">\n {displayYears.map((year) => (\n <button\n key={year}\n type=\"button\"\n onClick={() => setSelectedYear(year)}\n className={tw(\n 'py-2 px-3 text-sm rounded cursor-pointer transition-colors',\n 'hover:bg-neutral-100',\n selectedYear === year && 'bg-primary-500 text-white hover:bg-primary-500',\n year === currentDate.year && selectedYear !== year && 'font-semibold'\n )}\n >\n {year}\n </button>\n ))}\n </div>\n </div>\n\n {/* Month selection */}\n <div>\n <div className=\"text-sm font-semibold text-neutral-900 mb-2\">{selectedYear}</div>\n <div className=\"grid grid-cols-3 gap-2\">\n {months.map((month, index) => {\n const monthNum = index + 1\n const isCurrentMonth = selectedYear === currentDate.year && monthNum === currentDate.month\n\n return (\n <button\n key={month}\n type=\"button\"\n onClick={() => onSelect(selectedYear, monthNum)}\n className={tw(\n 'py-2 px-2 text-sm rounded cursor-pointer transition-colors',\n 'hover:bg-neutral-100',\n isCurrentMonth && 'font-semibold border border-primary-500'\n )}\n >\n {month.slice(0, 3)}\n </button>\n )\n })}\n </div>\n </div>\n\n {/* Cancel button */}\n <div className=\"mt-4 flex justify-end\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"py-1 px-3 text-sm text-neutral-600 hover:text-neutral-900 hover:bg-neutral-100 rounded transition-colors\"\n >\n Cancel\n </button>\n </div>\n </div>\n )\n}\n\ntype CustomCalendarPropsBase = {\n isDateUnavailable?: (date: CalendarDate | CalendarDateTime) => boolean\n}\n\ntype CustomCalendarPropsDate = CustomCalendarPropsBase & {\n value: CalendarDate | null\n onChange: (date: CalendarDate | null) => void\n minValue?: CalendarDate\n maxValue?: CalendarDate\n}\n\ntype CustomCalendarPropsDateTime = CustomCalendarPropsBase & {\n value: CalendarDateTime | null\n onChange: (date: CalendarDateTime | null) => void\n minValue?: CalendarDateTime\n maxValue?: CalendarDateTime\n}\n\ntype CustomCalendarProps = CustomCalendarPropsDate | CustomCalendarPropsDateTime\n\nexport function CustomCalendar(props: CustomCalendarProps) {\n const { value, onChange, minValue, maxValue, isDateUnavailable } = props\n const [showMonthYearPicker, setShowMonthYearPicker] = React.useState(false)\n\n // Create a default focused date\n const createDefaultDate = () => {\n const now = new Date()\n const year = now.getFullYear()\n const month = now.getMonth() + 1\n const day = now.getDate()\n\n // Check if we're working with CalendarDateTime\n if (value && 'hour' in value) {\n return new CalendarDateTime(year, month, day, 0, 0, 0)\n }\n return new CalendarDate(year, month, day)\n }\n\n const [focusedDate, setFocusedDate] = React.useState(value || createDefaultDate())\n\n const handleMonthYearSelect = (year: number, month: number) => {\n if ('hour' in focusedDate) {\n // CalendarDateTime\n const newDate = focusedDate.set({ year, month })\n setFocusedDate(newDate)\n } else {\n // CalendarDate - use safe day value\n const newDate = new CalendarDate(year, month, Math.min(focusedDate.day, 28))\n setFocusedDate(newDate)\n }\n setShowMonthYearPicker(false)\n }\n\n const handleFocusChange = (date: CalendarDate) => {\n if ('hour' in focusedDate) {\n // Convert CalendarDate to CalendarDateTime by preserving time from focusedDate\n const newDateTime = focusedDate.set({\n year: date.year,\n month: date.month,\n day: date.day\n })\n setFocusedDate(newDateTime)\n } else {\n setFocusedDate(date)\n }\n }\n\n if (showMonthYearPicker) {\n return (\n <MonthYearPicker\n currentDate={focusedDate}\n onSelect={handleMonthYearSelect}\n onCancel={() => setShowMonthYearPicker(false)}\n />\n )\n }\n\n const handleCalendarChange = (date: CalendarDate | CalendarDateTime | null) => {\n if (typeof onChange === 'function') {\n // Type assertion needed due to union type constraints\n ;(onChange as (date: CalendarDate | CalendarDateTime | null) => void)(date)\n }\n }\n\n return (\n <Calendar\n value={value ?? undefined}\n onChange={handleCalendarChange}\n focusedValue={focusedDate}\n onFocusChange={handleFocusChange}\n minValue={minValue !== undefined && minValue !== null ? minValue : undefined}\n maxValue={maxValue !== undefined && maxValue !== null ? maxValue : undefined}\n isDateUnavailable={\n isDateUnavailable\n ? (date) => {\n // Convert DateValue to CalendarDate for the callback\n if ('hour' in date) {\n // It's a CalendarDateTime, convert to CalendarDate\n const calendarDate = new CalendarDate(date.year, date.month, date.day)\n return isDateUnavailable(calendarDate)\n }\n return isDateUnavailable(date as CalendarDate)\n }\n : undefined\n }\n className=\"bg-white border border-neutral-200 rounded-md shadow-lg p-3\"\n >\n <header className=\"flex items-center justify-between mb-2\">\n <Button slot=\"previous\" className=\"p-1 hover:bg-neutral-100 rounded\">\n <Icon name=\"chevron-left\" className=\"h-4 w-4\" />\n </Button>\n <button\n type=\"button\"\n onClick={() => setShowMonthYearPicker(true)}\n className=\"text-sm font-semibold text-neutral-900 hover:bg-neutral-100 px-2 py-1 rounded transition-colors\"\n >\n <Heading />\n </button>\n <Button slot=\"next\" className=\"p-1 hover:bg-neutral-100 rounded\">\n <Icon name=\"chevron-right\" className=\"h-4 w-4\" />\n </Button>\n </header>\n <CalendarGrid>\n <CalendarGridHeader>\n {(day) => <CalendarHeaderCell className=\"text-xs text-neutral-500 font-medium w-8 h-8\">{day}</CalendarHeaderCell>}\n </CalendarGridHeader>\n <CalendarGridBody>\n {(date) => (\n <CalendarCell\n date={date}\n className={({ isSelected, isOutsideMonth, isDisabled, isUnavailable, isFocusVisible }) =>\n tw(\n 'w-8 h-8 text-sm rounded cursor-pointer flex items-center justify-center',\n 'hover:bg-neutral-100',\n isOutsideMonth && 'text-neutral-300',\n (isDisabled || isUnavailable) && 'text-neutral-300 cursor-not-allowed',\n isSelected && 'bg-primary-500 text-white hover:bg-primary-500',\n isFocusVisible && 'ring-2 ring-primary-500 ring-offset-1'\n )\n }\n />\n )}\n </CalendarGridBody>\n </CalendarGrid>\n </Calendar>\n )\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"text-field","files":[{"name":"components/text-field.tsx","content":"import * as React from 'react'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { HeadlessButton } from './helpers/headless-button'\n\nexport type TextFieldProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<string>\n\n // Value management\n value?: string\n defaultValue?: string\n onChange?: (value: string) => void\n onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>\n\n // Input type\n type?: 'text' | 'email' | 'password' | 'search' | 'tel' | 'url'\n shape?: 'rectangle' | 'oval'\n\n // Additional input props\n autoComplete?: string\n autoFocus?: boolean\n maxLength?: number\n minLength?: number\n pattern?: string\n inputMode?: React.HTMLAttributes<HTMLInputElement>['inputMode']\n\n trailingInlineAddon?: React.ReactNode\n}\n\nexport function TextField(props: TextFieldProps) {\n const inputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const inputId = React.useId()\n\n // Controlled state management\n const [value, setValue] = HeadlessForm.useControlledState(props.value, props.defaultValue ?? '', props.onChange)\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n inputRef.current?.focus()\n }\n },\n inputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setValue(e.target.value)\n }\n\n return (\n <div className=\"relative\">\n {/* Hidden input for form submission */}\n <HeadlessForm.HiddenInput ref={inputRef} name={props.name} value={value} form={props.form} />\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} htmlFor={inputId} />\n\n {/* Input wrapper with state-based styling */}\n <FormFieldComponents.FieldGroup\n isDisabled={props.isDisabled}\n isReadonly={props.isReadonly}\n isInvalid={isInvalid}\n hasWarning={hasWarning}\n shape={props.shape}\n className=\"flex\"\n >\n <FormFieldComponents.FieldInput\n id={inputId}\n type={props.type ?? 'text'}\n value={value}\n onChange={handleChange}\n onKeyDown={props.onKeyDown}\n onBlur={commitValidation}\n placeholder={props.placeholder}\n disabled={props.isDisabled}\n readOnly={props.isReadonly}\n autoComplete={props.autoComplete ?? 'off'}\n autoFocus={props.autoFocus}\n maxLength={props.maxLength}\n minLength={props.minLength}\n pattern={props.pattern}\n inputMode={props.inputMode}\n aria-invalid={isInvalid || undefined}\n aria-describedby={helpTextId}\n />\n {props.trailingInlineAddon}\n </FormFieldComponents.FieldGroup>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n\nexport function TrailingInlineAddonButton({ className, ...props }: React.ComponentProps<typeof HeadlessButton>) {\n return (\n <HeadlessButton\n {...props}\n className={(a) =>\n tw(\n 'inline-flex items-center inset-y-0 right-0 px-3 text-neutral-500 hover:bg-neutral-50 rounded-l sm:text-sm',\n typeof className === 'function' ? className(a) : className\n )\n }\n />\n )\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"components/helpers/headless-button.tsx","content":"import { composeRefs } from '../../utils/compose-refs'\nimport { type WithRenderProps, useRenderProps } from '../../utils/use-render-props'\n\nimport React from 'react'\nimport { usePress } from '@react-aria/interactions'\nimport { mergeProps } from '@react-aria/utils'\n\nexport const HeadlessButton = React.forwardRef<\n HTMLButtonElement,\n WithRenderProps<React.ComponentProps<'button'>, { isPressed: boolean }>\n>((rawProps, externalRef) => {\n const myRef = React.useRef<HTMLButtonElement>(null)\n const { isPressed, pressProps } = usePress({ ref: myRef, isDisabled: rawProps.disabled })\n const renderProps = React.useMemo(\n () => ({\n isPressed\n }),\n [isPressed]\n )\n\n const props = useRenderProps(mergeProps(pressProps, rawProps), renderProps, ['children', 'style', 'className'])\n const ref = React.useMemo(() => composeRefs(myRef, externalRef), [myRef, externalRef])\n\n return (\n <button\n type=\"button\"\n {...props}\n {...(props.disabled || props['data-disabled'] ? { ['data-disabled']: true } : {})}\n {...((isPressed && !props.disabled) || props['data-pressed'] ? { ['data-pressed']: true } : {})}\n aria-pressed={isPressed && !props.disabled}\n ref={ref}\n />\n )\n})\n\nHeadlessButton.displayName = 'HeadlessButton'\n"},{"name":"utils/compose-refs.ts","content":"import React from 'react'\n\ntype PossibleRef<T> = React.Ref<T> | undefined\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n if (typeof ref === 'function') {\n ref(value)\n } else if (ref !== null && ref !== undefined) {\n ;(ref as React.MutableRefObject<T>).current = value\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nexport function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n return (node: T) => refs.forEach((ref) => setRef(ref, node))\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nexport function useComposedRefs<T>(...refs: PossibleRef<T>[]) {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return React.useCallback(composeRefs(...refs), refs)\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"},{"name":"utils/use-render-props.ts","content":"import React from 'react'\n\ntype Prettify<T> = { [K in keyof T]: T[K] } & {}\n\ntype DefaultRenderPropKeys<Props> = Extract<'children' | 'className' | 'style', keyof Props>\n\ntype MaybeRenderProp<Value, RenderProps> = Value | ((renderProps: RenderProps) => Value)\n\nexport type WithRenderProps<\n Props extends Record<string, any>,\n RenderProps,\n Keys extends keyof Props = DefaultRenderPropKeys<Props>\n> = Prettify<{\n [K in keyof Props]: K extends Keys ? MaybeRenderProp<Props[K], RenderProps> : Props[K]\n}>\n\nexport function useRenderProps<\n Props extends Record<string, any>,\n RenderProps,\n Keys extends keyof Props = DefaultRenderPropKeys<Props>\n>(props: Props, renderProps: RenderProps, keys: Keys[]) {\n const withRenderProps = React.useMemo(\n () =>\n Object.fromEntries(\n keys\n .map((key) => {\n if (key in props && typeof props[key] === 'function') {\n return [key, props[key](renderProps)]\n } else {\n return []\n }\n })\n .filter((k) => k.length)\n ),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [...keys, renderProps, props, ...keys.map((key) => props[key])]\n )\n return { ...props, ...withRenderProps }\n}\n"}]},{"name":"text-area","files":[{"name":"components/text-area.tsx","content":"import * as React from 'react'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\n\nexport type TextAreaProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<string>\n\n // Value management\n value?: string\n defaultValue?: string\n onChange?: (value: string) => void\n\n // TextArea specific\n rows?: number\n minRows?: number\n maxRows?: number\n autoResize?: boolean\n maxLength?: number\n resize?: 'none' | 'both' | 'horizontal' | 'vertical'\n shape?: 'rectangle' | 'oval'\n}\n\nexport function TextArea(props: TextAreaProps) {\n const autoResize = props.autoResize ?? true\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const textAreaRef = React.useRef<HTMLTextAreaElement>(null)\n const helpTextId = React.useId()\n const textAreaId = React.useId()\n\n // Controlled state management\n const [value, setValue] = HeadlessForm.useControlledState(props.value, props.defaultValue ?? '', props.onChange)\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n textAreaRef.current?.focus()\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setValue(e.target.value)\n }\n\n return (\n <div className=\"relative\">\n {/* Hidden input for form submission */}\n <HeadlessForm.HiddenInput ref={hiddenInputRef} name={props.name} value={value} form={props.form} />\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} htmlFor={textAreaId} />\n\n {/* TextArea wrapper with state-based styling */}\n <FormFieldComponents.FieldGroup\n isDisabled={props.isDisabled}\n isReadonly={props.isReadonly}\n isInvalid={isInvalid}\n hasWarning={hasWarning}\n shape={props.shape}\n className={autoResize ? 'grid' : undefined}\n >\n {/* Auto-expand helper element */}\n {autoResize && (\n <p\n className={tw(\n 'whitespace-pre-wrap overflow-hidden col-span-full row-span-full px-3 py-2 text-sm text-transparent pointer-events-none',\n 'border-0 m-0'\n )}\n style={{\n WebkitLineClamp: props.maxRows || undefined,\n WebkitBoxOrient: props.maxRows ? 'vertical' : undefined,\n display: props.maxRows ? '-webkit-box' : undefined,\n minHeight: props.minRows ? `${props.minRows * 1.5}rem` : undefined\n }}\n aria-hidden=\"true\"\n >\n {value ? value + ' ' : ' '}\n </p>\n )}\n\n <textarea\n ref={textAreaRef}\n id={textAreaId}\n className={tw(\n 'w-full bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n autoResize && 'resize-none overflow-hidden col-span-full row-span-full',\n (props.isDisabled || props.isReadonly) && 'cursor-not-allowed',\n !autoResize && props.resize === 'none' && 'resize-none',\n !autoResize && props.resize === 'both' && 'resize',\n !autoResize && props.resize === 'horizontal' && 'resize-x',\n !autoResize && props.resize === 'vertical' && 'resize-y',\n !autoResize && !props.resize && 'resize-y'\n )}\n value={value}\n onChange={handleChange}\n onBlur={commitValidation}\n placeholder={props.placeholder}\n disabled={props.isDisabled}\n readOnly={props.isReadonly}\n maxLength={props.maxLength}\n rows={!autoResize ? props.rows || props.minRows || 3 : props.minRows || 3}\n aria-invalid={isInvalid || undefined}\n aria-describedby={helpTextId}\n />\n </FormFieldComponents.FieldGroup>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"number-field","files":[{"name":"components/number-field.tsx","content":"import * as React from 'react'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction } from '@maestro-js/form'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { useNumberInput } from '../utils/use-number-input'\n\nexport type NumberFieldProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<number>\n errorMessage?: React.ReactNode | ((v: import('@maestro-js/form').ValidationResult) => React.ReactNode)\n\n // Value management\n value?: number | null\n defaultValue?: number | null\n onChange?: (value: number | null) => void\n\n // Number specific\n min?: number | null\n max?: number | null\n step?: number | null\n formatOptions?: Intl.NumberFormatOptions\n autoComplete?: string | null\n autoFocus?: boolean\n shape?: 'rectangle' | 'oval'\n}\n\nexport function NumberField(props: NumberFieldProps) {\n const inputRef = React.useRef<HTMLInputElement>(null)\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n\n const helpTextId = React.useId()\n const inputId = React.useId()\n\n const numberInput = useNumberInput({\n value: props.value ?? undefined,\n defaultValue: props.defaultValue ?? undefined,\n onChange: props.onChange,\n minValue: props.min ?? undefined,\n maxValue: props.max ?? undefined,\n step: props.step ?? undefined,\n formatOptions: props.formatOptions,\n locale: 'en-US',\n isDisabled: props.isDisabled,\n isReadOnly: props.isReadonly ?? false\n })\n\n HeadlessForm.useReset(inputRef, numberInput.numberValue, numberInput.setNumberValue)\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n value: numberInput.numberValue as number,\n validate: props.validate,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n inputRef.current?.focus()\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n const handleBlur = () => {\n numberInput.commit(inputRef.current?.value)\n commitValidation()\n }\n\n return (\n <div className=\"relative\">\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} htmlFor={inputId} />\n\n {/* Hidden input for form submission */}\n {props.isDisabled ? null : (\n <HeadlessForm.HiddenInput\n ref={hiddenInputRef}\n form={props.form}\n name={props.name}\n value={numberInput.numberValue ?? ''}\n type=\"number\"\n min={numberInput.minValue}\n max={numberInput.maxValue}\n />\n )}\n\n <FormFieldComponents.FieldGroup\n isDisabled={props.isDisabled}\n isReadonly={props.isReadonly}\n isInvalid={isInvalid}\n hasWarning={hasWarning}\n shape={props.shape}\n className=\"flex\"\n >\n <FormFieldComponents.FieldInput\n ref={inputRef}\n id={inputId}\n type=\"text\"\n value={numberInput.inputValue}\n onChange={(e) => numberInput.setInputValue(e.currentTarget.value)}\n onBlur={handleBlur}\n placeholder={props.placeholder}\n disabled={props.isDisabled}\n readOnly={props.isReadonly}\n autoComplete={props.autoComplete ?? undefined}\n autoFocus={props.autoFocus}\n aria-invalid={isInvalid || undefined}\n aria-describedby={helpTextId}\n />\n </FormFieldComponents.FieldGroup>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"},{"name":"utils/use-number-input.ts","content":"import * as React from 'react'\nimport { HeadlessForm } from '@maestro-js/form'\n\nexport type UseNumberInputOptions = {\n value?: number | null\n defaultValue?: number | null\n onChange?: ((value: number | null) => void) | null\n minValue?: number\n maxValue?: number\n step?: number\n formatOptions?: Intl.NumberFormatOptions\n locale?: string\n isDisabled?: boolean\n isReadOnly?: boolean\n}\n\nexport type UseNumberInputResult = {\n inputValue: string\n setInputValue: (value: string) => void\n numberValue: number | null\n setNumberValue: (value: number | null) => void\n minValue?: number\n maxValue?: number\n commit: (rawValue?: string) => void\n}\n\nfunction formatNumber(value: number | null | undefined, locale: string, formatOptions?: Intl.NumberFormatOptions): string {\n if (value == null) return ''\n try {\n return new Intl.NumberFormat(locale, formatOptions).format(value)\n } catch {\n return String(value)\n }\n}\n\nfunction parseNumber(text: string, locale: string, formatOptions?: Intl.NumberFormatOptions): number | null {\n if (text === '' || text == null) return null\n\n // Get the decimal and group separators for the locale\n const parts = new Intl.NumberFormat(locale, formatOptions).formatToParts(1234.5)\n const groupSeparator = parts.find((p) => p.type === 'group')?.value ?? ','\n const decimalSeparator = parts.find((p) => p.type === 'decimal')?.value ?? '.'\n\n // Strip everything except digits, decimal separator, and minus sign\n let cleaned = text\n // Remove currency symbols, percent signs, and other literals\n for (const part of parts) {\n if (part.type === 'currency' || part.type === 'percentSign' || part.type === 'literal') {\n cleaned = cleaned.replaceAll(part.value, '')\n }\n }\n // Remove group separators\n cleaned = cleaned.replaceAll(groupSeparator, '')\n // Normalize decimal separator to '.'\n if (decimalSeparator !== '.') {\n cleaned = cleaned.replaceAll(decimalSeparator, '.')\n }\n\n cleaned = cleaned.trim()\n if (cleaned === '' || cleaned === '-') return null\n\n const num = Number(cleaned)\n\n // For percent format, Intl.NumberFormat displays 0.5 as \"50%\",\n // so we need to divide by 100 when parsing\n if (formatOptions?.style === 'percent') {\n return isNaN(num) ? null : num / 100\n }\n\n return isNaN(num) ? null : num\n}\n\nfunction clamp(value: number, min?: number, max?: number): number {\n if (min != null && value < min) return min\n if (max != null && value > max) return max\n return value\n}\n\nexport function useNumberInput(options: UseNumberInputOptions): UseNumberInputResult {\n const { minValue, maxValue, step, formatOptions, locale = 'en-US', isDisabled = false, isReadOnly = false } = options\n\n const [numberValue, setNumberValue] = HeadlessForm.useControlledState<number | null>(\n options.value !== undefined ? (options.value ?? null) : undefined!,\n options.defaultValue !== undefined ? (options.defaultValue ?? null) : null,\n options.onChange ?? undefined\n )\n\n const [inputValue, setInputValueRaw] = React.useState(() => formatNumber(numberValue, locale, formatOptions))\n\n // Keep inputValue in sync when numberValue changes externally (controlled mode)\n const prevNumberValueRef = React.useRef(numberValue)\n React.useEffect(() => {\n if (prevNumberValueRef.current !== numberValue) {\n prevNumberValueRef.current = numberValue\n setInputValueRaw(formatNumber(numberValue, locale, formatOptions))\n }\n }, [numberValue, locale, formatOptions])\n\n const setInputValue = React.useCallback(\n (text: string) => {\n if (isDisabled || isReadOnly) return\n setInputValueRaw(text)\n },\n [isDisabled, isReadOnly]\n )\n\n const commit = React.useCallback(\n (rawValue?: string) => {\n if (isDisabled || isReadOnly) return\n\n const parsed = parseNumber(rawValue ?? inputValue, locale, formatOptions)\n if (parsed == null) {\n setNumberValue(null)\n setInputValueRaw('')\n return\n }\n\n let clamped = clamp(parsed, minValue, maxValue)\n\n // Snap to step if provided\n if (step != null && minValue != null) {\n const remainder = (clamped - minValue) % step\n if (remainder !== 0) {\n clamped = clamped - remainder\n }\n }\n\n setNumberValue(clamped)\n setInputValueRaw(formatNumber(clamped, locale, formatOptions))\n },\n [inputValue, locale, formatOptions, minValue, maxValue, step, isDisabled, isReadOnly, setNumberValue]\n )\n\n return {\n inputValue,\n setInputValue,\n numberValue,\n setNumberValue,\n minValue,\n maxValue,\n commit\n }\n}\n"}]},{"name":"currency-field","files":[{"name":"components/currency-field.tsx","content":"import * as React from 'react'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction } from '@maestro-js/form'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { useNumberInput } from '../utils/use-number-input'\n\nexport type CurrencyFieldProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<number>\n errorMessage?: React.ReactNode | ((v: import('@maestro-js/form').ValidationResult) => React.ReactNode)\n\n // Value management (in the unit specified by `units`)\n value?: number | null\n defaultValue?: number | null\n onChange?: (value: number | null) => void\n\n // Number specific\n min?: number | null\n max?: number | null\n autoComplete?: string | null\n autoFocus?: boolean\n\n // Currency specific\n units: 'dollars' | 'cents'\n shape?: 'rectangle' | 'oval'\n}\n\nconst currencyFormatOptions: Intl.NumberFormatOptions = {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: 2,\n maximumFractionDigits: 2\n}\n\nexport function CurrencyField(props: CurrencyFieldProps) {\n const inputRef = React.useRef<HTMLInputElement>(null)\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n\n const helpTextId = React.useId()\n const inputId = React.useId()\n\n const isCents = props.units === 'cents'\n\n // Stable ref for onChange to avoid unnecessary re-renders in useNumberInput\n const onChangeRef = React.useRef(props.onChange)\n onChangeRef.current = props.onChange\n\n const handleChange = React.useCallback(\n (newValue: number | null) => {\n onChangeRef.current?.(isCents && newValue !== null ? dollarsToCents(newValue) : newValue)\n },\n [isCents]\n )\n\n const displayValue = isCents && props.value != null ? centsToDollars(props.value) : props.value\n const displayDefaultValue = isCents && props.defaultValue != null ? centsToDollars(props.defaultValue) : props.defaultValue\n const displayMin = isCents && props.min != null ? centsToDollars(props.min) : props.min\n const displayMax = isCents && props.max != null ? centsToDollars(props.max) : props.max\n\n const numberInput = useNumberInput({\n value: displayValue ?? undefined,\n defaultValue: displayDefaultValue ?? undefined,\n onChange: handleChange,\n minValue: displayMin ?? undefined,\n maxValue: displayMax ?? undefined,\n step: 0.01,\n formatOptions: currencyFormatOptions,\n locale: 'en-US',\n isDisabled: props.isDisabled,\n isReadOnly: props.isReadonly ?? false\n })\n\n HeadlessForm.useReset(inputRef, numberInput.numberValue, numberInput.setNumberValue)\n\n // Validation value should be in the original unit\n const validationValue = isCents ? dollarsToCents(numberInput.numberValue) : numberInput.numberValue\n\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: validationValue!,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n inputRef.current?.focus()\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n // Hidden input value in the original unit\n const hiddenValue = isCents ? dollarsToCents(numberInput.numberValue) : numberInput.numberValue\n\n const handleBlur = () => {\n numberInput.commit(inputRef.current?.value)\n commitValidation()\n }\n\n return (\n <div className=\"relative\">\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} htmlFor={inputId} />\n\n {/* Hidden input for form submission */}\n {props.isDisabled ? null : (\n <HeadlessForm.HiddenInput\n ref={hiddenInputRef}\n form={props.form}\n name={props.name}\n value={hiddenValue ?? ''}\n type=\"number\"\n min={numberInput.minValue}\n max={numberInput.maxValue}\n />\n )}\n\n <FormFieldComponents.FieldGroup\n isDisabled={props.isDisabled}\n isReadonly={props.isReadonly}\n isInvalid={isInvalid}\n hasWarning={hasWarning}\n shape={props.shape}\n className=\"flex\"\n >\n <FormFieldComponents.FieldInput\n ref={inputRef}\n id={inputId}\n type=\"text\"\n value={numberInput.inputValue}\n onChange={(e) => numberInput.setInputValue(e.currentTarget.value)}\n onBlur={handleBlur}\n placeholder={props.placeholder}\n disabled={props.isDisabled}\n readOnly={props.isReadonly}\n autoComplete={props.autoComplete ?? undefined}\n autoFocus={props.autoFocus}\n aria-invalid={isInvalid || undefined}\n aria-describedby={helpTextId}\n />\n </FormFieldComponents.FieldGroup>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n\nfunction centsToDollars(cents: number): number {\n return cents / 100\n}\n\nfunction dollarsToCents(dollars: number | null): number | null {\n if (dollars == null) return null\n return Math.round(dollars * 100)\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"},{"name":"utils/use-number-input.ts","content":"import * as React from 'react'\nimport { HeadlessForm } from '@maestro-js/form'\n\nexport type UseNumberInputOptions = {\n value?: number | null\n defaultValue?: number | null\n onChange?: ((value: number | null) => void) | null\n minValue?: number\n maxValue?: number\n step?: number\n formatOptions?: Intl.NumberFormatOptions\n locale?: string\n isDisabled?: boolean\n isReadOnly?: boolean\n}\n\nexport type UseNumberInputResult = {\n inputValue: string\n setInputValue: (value: string) => void\n numberValue: number | null\n setNumberValue: (value: number | null) => void\n minValue?: number\n maxValue?: number\n commit: (rawValue?: string) => void\n}\n\nfunction formatNumber(value: number | null | undefined, locale: string, formatOptions?: Intl.NumberFormatOptions): string {\n if (value == null) return ''\n try {\n return new Intl.NumberFormat(locale, formatOptions).format(value)\n } catch {\n return String(value)\n }\n}\n\nfunction parseNumber(text: string, locale: string, formatOptions?: Intl.NumberFormatOptions): number | null {\n if (text === '' || text == null) return null\n\n // Get the decimal and group separators for the locale\n const parts = new Intl.NumberFormat(locale, formatOptions).formatToParts(1234.5)\n const groupSeparator = parts.find((p) => p.type === 'group')?.value ?? ','\n const decimalSeparator = parts.find((p) => p.type === 'decimal')?.value ?? '.'\n\n // Strip everything except digits, decimal separator, and minus sign\n let cleaned = text\n // Remove currency symbols, percent signs, and other literals\n for (const part of parts) {\n if (part.type === 'currency' || part.type === 'percentSign' || part.type === 'literal') {\n cleaned = cleaned.replaceAll(part.value, '')\n }\n }\n // Remove group separators\n cleaned = cleaned.replaceAll(groupSeparator, '')\n // Normalize decimal separator to '.'\n if (decimalSeparator !== '.') {\n cleaned = cleaned.replaceAll(decimalSeparator, '.')\n }\n\n cleaned = cleaned.trim()\n if (cleaned === '' || cleaned === '-') return null\n\n const num = Number(cleaned)\n\n // For percent format, Intl.NumberFormat displays 0.5 as \"50%\",\n // so we need to divide by 100 when parsing\n if (formatOptions?.style === 'percent') {\n return isNaN(num) ? null : num / 100\n }\n\n return isNaN(num) ? null : num\n}\n\nfunction clamp(value: number, min?: number, max?: number): number {\n if (min != null && value < min) return min\n if (max != null && value > max) return max\n return value\n}\n\nexport function useNumberInput(options: UseNumberInputOptions): UseNumberInputResult {\n const { minValue, maxValue, step, formatOptions, locale = 'en-US', isDisabled = false, isReadOnly = false } = options\n\n const [numberValue, setNumberValue] = HeadlessForm.useControlledState<number | null>(\n options.value !== undefined ? (options.value ?? null) : undefined!,\n options.defaultValue !== undefined ? (options.defaultValue ?? null) : null,\n options.onChange ?? undefined\n )\n\n const [inputValue, setInputValueRaw] = React.useState(() => formatNumber(numberValue, locale, formatOptions))\n\n // Keep inputValue in sync when numberValue changes externally (controlled mode)\n const prevNumberValueRef = React.useRef(numberValue)\n React.useEffect(() => {\n if (prevNumberValueRef.current !== numberValue) {\n prevNumberValueRef.current = numberValue\n setInputValueRaw(formatNumber(numberValue, locale, formatOptions))\n }\n }, [numberValue, locale, formatOptions])\n\n const setInputValue = React.useCallback(\n (text: string) => {\n if (isDisabled || isReadOnly) return\n setInputValueRaw(text)\n },\n [isDisabled, isReadOnly]\n )\n\n const commit = React.useCallback(\n (rawValue?: string) => {\n if (isDisabled || isReadOnly) return\n\n const parsed = parseNumber(rawValue ?? inputValue, locale, formatOptions)\n if (parsed == null) {\n setNumberValue(null)\n setInputValueRaw('')\n return\n }\n\n let clamped = clamp(parsed, minValue, maxValue)\n\n // Snap to step if provided\n if (step != null && minValue != null) {\n const remainder = (clamped - minValue) % step\n if (remainder !== 0) {\n clamped = clamped - remainder\n }\n }\n\n setNumberValue(clamped)\n setInputValueRaw(formatNumber(clamped, locale, formatOptions))\n },\n [inputValue, locale, formatOptions, minValue, maxValue, step, isDisabled, isReadOnly, setNumberValue]\n )\n\n return {\n inputValue,\n setInputValue,\n numberValue,\n setNumberValue,\n minValue,\n maxValue,\n commit\n }\n}\n"}]},{"name":"percentage-field","files":[{"name":"components/percentage-field.tsx","content":"import * as React from 'react'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction } from '@maestro-js/form'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { useNumberInput } from '../utils/use-number-input'\n\nexport type PercentageFieldProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<number>\n errorMessage?: React.ReactNode | ((v: import('@maestro-js/form').ValidationResult) => React.ReactNode)\n\n // Value management (in the unit specified by `units`)\n value?: number | null\n defaultValue?: number | null\n onChange?: (value: number | null) => void\n\n // Number specific\n min?: number | null\n max?: number | null\n autoComplete?: string | null\n autoFocus?: boolean\n\n // Percentage specific\n units: 'integer' | 'decimal'\n minimumFractionDigits?: number\n maximumFractionDigits?: number\n shape?: 'rectangle' | 'oval'\n}\n\nexport function PercentageField(props: PercentageFieldProps) {\n const inputRef = React.useRef<HTMLInputElement>(null)\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n\n const helpTextId = React.useId()\n const inputId = React.useId()\n\n const isInteger = props.units === 'integer'\n\n // Stable ref for onChange to avoid unnecessary re-renders in useNumberInput\n const onChangeRef = React.useRef(props.onChange)\n onChangeRef.current = props.onChange\n\n const handleChange = React.useCallback(\n (newValue: number | null) => {\n onChangeRef.current?.(isInteger && newValue !== null ? decimalToInteger(newValue) : newValue)\n },\n [isInteger]\n )\n\n const formatOptions = React.useMemo<Intl.NumberFormatOptions>(\n () => ({\n style: 'percent',\n minimumFractionDigits: props.minimumFractionDigits,\n maximumFractionDigits: props.maximumFractionDigits\n }),\n [props.minimumFractionDigits, props.maximumFractionDigits]\n )\n\n const displayValue = isInteger && props.value != null ? integerToDecimal(props.value) : props.value\n const displayDefaultValue =\n isInteger && props.defaultValue != null ? integerToDecimal(props.defaultValue) : props.defaultValue\n const displayMin = isInteger && props.min != null ? integerToDecimal(props.min) : props.min\n const displayMax = isInteger && props.max != null ? integerToDecimal(props.max) : props.max\n\n const numberInput = useNumberInput({\n value: displayValue ?? undefined,\n defaultValue: displayDefaultValue ?? undefined,\n onChange: handleChange,\n minValue: displayMin ?? undefined,\n maxValue: displayMax ?? undefined,\n formatOptions,\n locale: 'en-US',\n isDisabled: props.isDisabled,\n isReadOnly: props.isReadonly ?? false\n })\n\n HeadlessForm.useReset(inputRef, numberInput.numberValue, numberInput.setNumberValue)\n\n // Validation value should be in the original unit\n const validationValue = isInteger ? decimalToInteger(numberInput.numberValue) : numberInput.numberValue\n\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: validationValue as number,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n inputRef.current?.focus()\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n // Hidden input value in the original unit\n const hiddenValue = isInteger ? decimalToInteger(numberInput.numberValue) : numberInput.numberValue\n\n const handleBlur = () => {\n numberInput.commit(inputRef.current?.value)\n commitValidation()\n }\n\n return (\n <div className=\"relative\">\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} htmlFor={inputId} />\n\n {/* Hidden input for form submission */}\n {props.isDisabled ? null : (\n <HeadlessForm.HiddenInput\n ref={hiddenInputRef}\n form={props.form}\n name={props.name}\n value={hiddenValue ?? ''}\n type=\"number\"\n min={numberInput.minValue}\n max={numberInput.maxValue}\n />\n )}\n\n <FormFieldComponents.FieldGroup\n isDisabled={props.isDisabled}\n isReadonly={props.isReadonly}\n isInvalid={isInvalid}\n hasWarning={hasWarning}\n shape={props.shape}\n className=\"flex\"\n >\n <FormFieldComponents.FieldInput\n ref={inputRef}\n id={inputId}\n type=\"text\"\n value={numberInput.inputValue}\n onChange={(e) => numberInput.setInputValue(e.currentTarget.value)}\n onBlur={handleBlur}\n placeholder={props.placeholder}\n disabled={props.isDisabled}\n readOnly={props.isReadonly}\n autoComplete={props.autoComplete ?? undefined}\n autoFocus={props.autoFocus}\n aria-invalid={isInvalid || undefined}\n aria-describedby={helpTextId}\n />\n </FormFieldComponents.FieldGroup>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n\nfunction integerToDecimal(value: number): number {\n return value / 100\n}\n\nfunction decimalToInteger(value: number | null): number | null {\n if (value == null) return null\n return Math.round(value * 100)\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"},{"name":"utils/use-number-input.ts","content":"import * as React from 'react'\nimport { HeadlessForm } from '@maestro-js/form'\n\nexport type UseNumberInputOptions = {\n value?: number | null\n defaultValue?: number | null\n onChange?: ((value: number | null) => void) | null\n minValue?: number\n maxValue?: number\n step?: number\n formatOptions?: Intl.NumberFormatOptions\n locale?: string\n isDisabled?: boolean\n isReadOnly?: boolean\n}\n\nexport type UseNumberInputResult = {\n inputValue: string\n setInputValue: (value: string) => void\n numberValue: number | null\n setNumberValue: (value: number | null) => void\n minValue?: number\n maxValue?: number\n commit: (rawValue?: string) => void\n}\n\nfunction formatNumber(value: number | null | undefined, locale: string, formatOptions?: Intl.NumberFormatOptions): string {\n if (value == null) return ''\n try {\n return new Intl.NumberFormat(locale, formatOptions).format(value)\n } catch {\n return String(value)\n }\n}\n\nfunction parseNumber(text: string, locale: string, formatOptions?: Intl.NumberFormatOptions): number | null {\n if (text === '' || text == null) return null\n\n // Get the decimal and group separators for the locale\n const parts = new Intl.NumberFormat(locale, formatOptions).formatToParts(1234.5)\n const groupSeparator = parts.find((p) => p.type === 'group')?.value ?? ','\n const decimalSeparator = parts.find((p) => p.type === 'decimal')?.value ?? '.'\n\n // Strip everything except digits, decimal separator, and minus sign\n let cleaned = text\n // Remove currency symbols, percent signs, and other literals\n for (const part of parts) {\n if (part.type === 'currency' || part.type === 'percentSign' || part.type === 'literal') {\n cleaned = cleaned.replaceAll(part.value, '')\n }\n }\n // Remove group separators\n cleaned = cleaned.replaceAll(groupSeparator, '')\n // Normalize decimal separator to '.'\n if (decimalSeparator !== '.') {\n cleaned = cleaned.replaceAll(decimalSeparator, '.')\n }\n\n cleaned = cleaned.trim()\n if (cleaned === '' || cleaned === '-') return null\n\n const num = Number(cleaned)\n\n // For percent format, Intl.NumberFormat displays 0.5 as \"50%\",\n // so we need to divide by 100 when parsing\n if (formatOptions?.style === 'percent') {\n return isNaN(num) ? null : num / 100\n }\n\n return isNaN(num) ? null : num\n}\n\nfunction clamp(value: number, min?: number, max?: number): number {\n if (min != null && value < min) return min\n if (max != null && value > max) return max\n return value\n}\n\nexport function useNumberInput(options: UseNumberInputOptions): UseNumberInputResult {\n const { minValue, maxValue, step, formatOptions, locale = 'en-US', isDisabled = false, isReadOnly = false } = options\n\n const [numberValue, setNumberValue] = HeadlessForm.useControlledState<number | null>(\n options.value !== undefined ? (options.value ?? null) : undefined!,\n options.defaultValue !== undefined ? (options.defaultValue ?? null) : null,\n options.onChange ?? undefined\n )\n\n const [inputValue, setInputValueRaw] = React.useState(() => formatNumber(numberValue, locale, formatOptions))\n\n // Keep inputValue in sync when numberValue changes externally (controlled mode)\n const prevNumberValueRef = React.useRef(numberValue)\n React.useEffect(() => {\n if (prevNumberValueRef.current !== numberValue) {\n prevNumberValueRef.current = numberValue\n setInputValueRaw(formatNumber(numberValue, locale, formatOptions))\n }\n }, [numberValue, locale, formatOptions])\n\n const setInputValue = React.useCallback(\n (text: string) => {\n if (isDisabled || isReadOnly) return\n setInputValueRaw(text)\n },\n [isDisabled, isReadOnly]\n )\n\n const commit = React.useCallback(\n (rawValue?: string) => {\n if (isDisabled || isReadOnly) return\n\n const parsed = parseNumber(rawValue ?? inputValue, locale, formatOptions)\n if (parsed == null) {\n setNumberValue(null)\n setInputValueRaw('')\n return\n }\n\n let clamped = clamp(parsed, minValue, maxValue)\n\n // Snap to step if provided\n if (step != null && minValue != null) {\n const remainder = (clamped - minValue) % step\n if (remainder !== 0) {\n clamped = clamped - remainder\n }\n }\n\n setNumberValue(clamped)\n setInputValueRaw(formatNumber(clamped, locale, formatOptions))\n },\n [inputValue, locale, formatOptions, minValue, maxValue, step, isDisabled, isReadOnly, setNumberValue]\n )\n\n return {\n inputValue,\n setInputValue,\n numberValue,\n setNumberValue,\n minValue,\n maxValue,\n commit\n }\n}\n"}]},{"name":"phone-number-field","files":[{"name":"components/phone-number-field.tsx","content":"import * as React from 'react'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction } from '@maestro-js/form'\nimport { FormFieldComponents } from './helpers/form-field'\n\nexport type PhoneNumberFieldProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<string>\n\n // Value management\n value?: string\n defaultValue?: string\n onChange?: (value: string) => void\n\n // Phone specific\n country?: 'US' | 'CA' | 'international'\n autoFormat?: boolean\n autoComplete?: string\n autoFocus?: boolean\n shape?: 'rectangle' | 'oval'\n}\n\nexport function PhoneNumberField(props: PhoneNumberFieldProps) {\n const inputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const inputId = React.useId()\n\n // Controlled state management\n const [value, setValue] = HeadlessForm.useControlledState(props.value, props.defaultValue ?? '', props.onChange)\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n inputRef.current?.focus()\n }\n },\n inputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n // Track whether field is focused for formatting toggle\n const [isFocused, setIsFocused] = React.useState(false)\n\n // Display value - formatted when not focused, raw when focused\n const displayValue = React.useMemo(() => {\n if (props.autoFormat === false) return value\n if (isFocused) return value\n return formatPhoneNumber(value, props.country)\n }, [value, props.autoFormat, props.country, isFocused])\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (props.autoFormat !== false) {\n setValue(getDigits(e.target.value))\n } else {\n setValue(e.target.value)\n }\n }\n\n const handleFocus = () => {\n setIsFocused(true)\n }\n\n const handleBlur = () => {\n setIsFocused(false)\n commitValidation()\n }\n\n return (\n <div className=\"relative\">\n {/* Hidden input for form submission */}\n <HeadlessForm.HiddenInput ref={inputRef} name={props.name} value={value} form={props.form} />\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} htmlFor={inputId} />\n\n <FormFieldComponents.FieldGroup\n isDisabled={props.isDisabled}\n isReadonly={props.isReadonly}\n isInvalid={isInvalid}\n hasWarning={hasWarning}\n shape={props.shape}\n className=\"flex\"\n >\n <FormFieldComponents.FieldInput\n id={inputId}\n type=\"tel\"\n value={displayValue}\n onChange={handleChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n placeholder={props.placeholder ?? (props.country === 'international' ? '+1234567890' : '(555) 555-5555')}\n disabled={props.isDisabled}\n readOnly={props.isReadonly}\n autoComplete={props.autoComplete ?? 'tel'}\n autoFocus={props.autoFocus}\n inputMode=\"tel\"\n aria-invalid={isInvalid || undefined}\n aria-describedby={helpTextId}\n />\n </FormFieldComponents.FieldGroup>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n\n// Format phone number for display\nfunction formatPhoneNumber(value: string, country: 'US' | 'CA' | 'international' = 'US'): string {\n const digits = value.replace(/\\D/g, '')\n\n if (country === 'US' || country === 'CA') {\n if (digits.length === 0) return ''\n if (digits.length <= 3) return `(${digits}`\n if (digits.length <= 6) return `(${digits.slice(0, 3)}) ${digits.slice(3)}`\n if (digits.length <= 10) return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`\n // Handle country code\n if (digits.length === 11 && digits[0] === '1') {\n return `+1 (${digits.slice(1, 4)}) ${digits.slice(4, 7)}-${digits.slice(7)}`\n }\n return value\n }\n\n // International - just add + prefix for readability\n if (digits.length > 0 && !value.startsWith('+')) {\n return '+' + digits\n }\n return value\n}\n\n// Get raw digits from formatted phone\nfunction getDigits(value: string): string {\n return value.replace(/\\D/g, '')\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"tag-field","files":[{"name":"components/tag-field.tsx","content":"import * as React from 'react'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { Icon } from './icon'\n\nexport type TagFieldProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<string[]>\n\n // Value management\n value?: string[]\n defaultValue?: string[]\n onChange?: (value: string[]) => void\n\n // Tag specific\n maxTags?: number\n hideTagCount?: boolean\n allowDuplicates?: boolean\n separator?: string | RegExp | (string | RegExp)[]\n validateTag?: (tag: string) => boolean\n transformTag?: (tag: string) => string\n\n // Custom rendering\n renderTag?: (tag: string, index: number, onRemove: () => void, isDisabled?: boolean) => React.ReactNode\n shape?: 'rectangle' | 'oval'\n}\n\nexport function TagField(props: TagFieldProps) {\n const inputRef = React.useRef<HTMLInputElement>(null)\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const inputId = React.useId()\n const [query, setQuery] = React.useState('')\n\n // Controlled state management\n const [tags, setTags] = HeadlessForm.useControlledState(props.value, props.defaultValue ?? [], props.onChange)\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: tags,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n inputRef.current?.focus()\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n const separators = React.useMemo(() => {\n const sep = props.separator ?? [',', '\\n']\n return Array.isArray(sep) ? sep : [sep]\n }, [props.separator])\n\n const addTag = React.useCallback(\n (tag: string) => {\n const trimmed = tag.trim()\n if (!trimmed) return\n\n const final = props.transformTag ? props.transformTag(trimmed) : trimmed\n\n if (props.validateTag && !props.validateTag(final)) return\n if (!props.allowDuplicates && tags.includes(final)) return\n if (props.maxTags !== undefined && tags.length >= props.maxTags) return\n\n setTags([...tags, final])\n setQuery('')\n commitValidation()\n },\n [tags, setTags, commitValidation, props.transformTag, props.validateTag, props.allowDuplicates, props.maxTags]\n )\n\n const removeTag = React.useCallback(\n (index: number) => {\n setTags(tags.filter((_, i) => i !== index))\n commitValidation()\n },\n [tags, setTags, commitValidation]\n )\n\n const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const value = e.target.value\n\n for (const sep of separators) {\n const matches = typeof sep === 'string' ? value.includes(sep) : sep.test(value)\n if (matches) {\n const parts = typeof sep === 'string' ? value.split(sep) : value.split(sep)\n for (const part of parts) {\n if (part.trim()) addTag(part)\n }\n setQuery('')\n return\n }\n }\n\n setQuery(value)\n }\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.preventDefault()\n if (query) addTag(query)\n } else if (e.key === 'Backspace' && !query && tags.length > 0) {\n removeTag(tags.length - 1)\n }\n }\n\n const atMax = props.maxTags !== undefined && tags.length >= props.maxTags\n const isDisabled = props.isDisabled || atMax\n\n return (\n <div className=\"relative\">\n {/* Hidden inputs for form submission */}\n {tags.map((tag, i) => (\n <HeadlessForm.HiddenInput\n key={i}\n ref={i === 0 ? hiddenInputRef : undefined}\n name={props.name}\n value={tag}\n form={props.form}\n />\n ))}\n {tags.length === 0 && <HeadlessForm.HiddenInput ref={hiddenInputRef} name={props.name} value=\"\" form={props.form} />}\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} htmlFor={inputId} />\n\n <FormFieldComponents.FieldGroup isDisabled={props.isDisabled} isInvalid={isInvalid} hasWarning={hasWarning} shape={props.shape} onClick={() => inputRef.current?.focus()}>\n <div className=\"flex flex-wrap items-center gap-1.5 px-3 py-1.5 min-h-[38px] w-full\">\n {/* Display existing tags */}\n {tags.map((tag, index) =>\n props.renderTag ? (\n <React.Fragment key={index}>\n {props.renderTag(tag, index, () => removeTag(index), props.isDisabled)}\n </React.Fragment>\n ) : (\n <Tag\n key={index}\n onRemove={!props.isDisabled ? () => removeTag(index) : undefined}\n isDisabled={props.isDisabled}\n >\n {tag}\n </Tag>\n )\n )}\n\n {/* Text input for adding tags */}\n <input\n id={inputId}\n ref={inputRef}\n className={tw(\n 'flex-1 min-w-[120px] bg-transparent p-0 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n props.isDisabled && 'cursor-not-allowed'\n )}\n value={query}\n onChange={handleInputChange}\n onKeyDown={handleKeyDown}\n onBlur={() => {\n if (query) addTag(query)\n commitValidation()\n }}\n placeholder={\n tags.length === 0 ? (props.placeholder ?? 'Add tags...') : atMax ? `Maximum ${props.maxTags} tags` : ''\n }\n disabled={isDisabled}\n aria-invalid={isInvalid || undefined}\n aria-describedby={helpTextId}\n autoComplete=\"off\"\n />\n </div>\n </FormFieldComponents.FieldGroup>\n\n {/* Tag count */}\n {!props.hideTagCount && props.maxTags !== undefined && (\n <div className=\"mt-1 text-xs text-neutral-500\">\n {tags.length} / {props.maxTags} tags\n </div>\n )}\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n\nfunction Tag({\n children,\n onRemove,\n isDisabled\n}: {\n children: React.ReactNode\n onRemove?: () => void\n isDisabled?: boolean\n}) {\n return (\n <span\n className={tw(\n 'inline-flex items-center rounded-full px-2 py-0.5 text-xs font-normal bg-primary-500/10 text-primary-500',\n isDisabled && 'opacity-50'\n )}\n >\n <span className=\"truncate\">{children}</span>\n {onRemove && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n onRemove()\n }}\n className=\"inline-flex items-center justify-center h-4 w-4 -m-0.5 p-0.5 rounded-full translate-x-1 hover:bg-black/10\"\n aria-label=\"Remove\"\n tabIndex={-1}\n >\n <Icon name=\"x-mark\" className=\"h-2 w-2\" aria-hidden=\"true\" />\n </button>\n )}\n </span>\n )\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"numeric-tag-field","files":[{"name":"components/numeric-tag-field.tsx","content":"import * as React from 'react'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { Icon } from './icon'\n\nexport type NumericTagFieldProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<number[]>\n\n // Value management\n value?: number[]\n defaultValue?: number[]\n onChange?: (value: number[]) => void\n\n // Tag specific\n maxTags?: number\n hideTagCount?: boolean\n allowDuplicates?: boolean\n separator?: string | RegExp | (string | RegExp)[]\n validateTag?: (tag: number) => boolean\n transformTag?: (tag: number) => number\n min?: number\n max?: number\n shape?: 'rectangle' | 'oval'\n}\n\nexport function NumericTagField(props: NumericTagFieldProps) {\n const inputRef = React.useRef<HTMLInputElement>(null)\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const inputId = React.useId()\n const [query, setQuery] = React.useState('')\n\n // Controlled state management\n const [tags, setTags] = HeadlessForm.useControlledState(props.value, props.defaultValue ?? [], props.onChange)\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: tags,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n inputRef.current?.focus()\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n const separators = React.useMemo(() => {\n const sep = props.separator ?? [',', '\\n']\n return Array.isArray(sep) ? sep : [sep]\n }, [props.separator])\n\n const addTag = React.useCallback(\n (raw: string) => {\n const trimmed = raw.trim()\n if (!trimmed || Number.isNaN(Number(trimmed))) return\n\n let num = Number(trimmed)\n\n // Transform tag if needed\n if (props.transformTag) num = props.transformTag(num)\n\n // Check min/max bounds\n if (props.min !== undefined && num < props.min) return\n if (props.max !== undefined && num > props.max) return\n\n // Validate tag\n if (props.validateTag && !props.validateTag(num)) return\n\n // Check for duplicates\n if (!props.allowDuplicates && tags.includes(num)) return\n\n // Check max tags\n if (props.maxTags !== undefined && tags.length >= props.maxTags) return\n\n setTags([...tags, num])\n setQuery('')\n commitValidation()\n },\n [\n tags,\n setTags,\n commitValidation,\n props.transformTag,\n props.validateTag,\n props.allowDuplicates,\n props.maxTags,\n props.min,\n props.max\n ]\n )\n\n const removeTag = React.useCallback(\n (index: number) => {\n setTags(tags.filter((_, i) => i !== index))\n commitValidation()\n },\n [tags, setTags, commitValidation]\n )\n\n const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const value = e.target.value\n\n for (const sep of separators) {\n const matches = typeof sep === 'string' ? value.includes(sep) : sep.test(value)\n if (matches) {\n const parts = typeof sep === 'string' ? value.split(sep) : value.split(sep)\n for (const part of parts) {\n if (part.trim()) addTag(part)\n }\n setQuery('')\n return\n }\n }\n\n setQuery(value)\n }\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.preventDefault()\n if (query) addTag(query)\n } else if (e.key === 'Backspace' && !query && tags.length > 0) {\n removeTag(tags.length - 1)\n }\n }\n\n const atMax = props.maxTags !== undefined && tags.length >= props.maxTags\n const isDisabled = props.isDisabled || atMax\n\n const defaultPlaceholder =\n props.min !== undefined && props.max !== undefined\n ? `Add numbers (${props.min}–${props.max})...`\n : props.min !== undefined\n ? `Add numbers (min: ${props.min})...`\n : props.max !== undefined\n ? `Add numbers (max: ${props.max})...`\n : 'Add numbers...'\n\n return (\n <div className=\"relative\">\n {/* Hidden inputs for form submission */}\n {tags.map((tag, i) => (\n <HeadlessForm.HiddenInput\n key={i}\n ref={i === 0 ? hiddenInputRef : undefined}\n name={props.name}\n value={tag}\n form={props.form}\n />\n ))}\n {tags.length === 0 && <HeadlessForm.HiddenInput ref={hiddenInputRef} name={props.name} value=\"\" form={props.form} />}\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} htmlFor={inputId} />\n\n <FormFieldComponents.FieldGroup isDisabled={props.isDisabled} isInvalid={isInvalid} hasWarning={hasWarning} shape={props.shape} onClick={() => inputRef.current?.focus()}>\n <div className=\"flex flex-wrap items-center gap-1.5 px-3 py-1.5 min-h-[38px] w-full\">\n {/* Display existing tags */}\n {tags.map((tag, index) => (\n <Tag key={index} onRemove={!props.isDisabled ? () => removeTag(index) : undefined} isDisabled={props.isDisabled}>\n {tag}\n </Tag>\n ))}\n\n {/* Text input for adding tags */}\n <input\n id={inputId}\n ref={inputRef}\n className={tw(\n 'flex-1 min-w-[120px] bg-transparent p-0 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n props.isDisabled && 'cursor-not-allowed'\n )}\n value={query}\n onChange={handleInputChange}\n onKeyDown={handleKeyDown}\n onBlur={() => {\n if (query) addTag(query)\n commitValidation()\n }}\n placeholder={\n tags.length === 0 ? (props.placeholder ?? defaultPlaceholder) : atMax ? `Maximum ${props.maxTags} tags` : ''\n }\n disabled={isDisabled}\n aria-invalid={isInvalid || undefined}\n aria-describedby={helpTextId}\n autoComplete=\"off\"\n inputMode=\"numeric\"\n />\n </div>\n </FormFieldComponents.FieldGroup>\n\n {/* Tag count */}\n {!props.hideTagCount && props.maxTags !== undefined && (\n <div className=\"mt-1 text-xs text-neutral-500\">\n {tags.length} / {props.maxTags} tags\n </div>\n )}\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n\nfunction Tag({\n children,\n onRemove,\n isDisabled\n}: {\n children: React.ReactNode\n onRemove?: () => void\n isDisabled?: boolean\n}) {\n return (\n <span\n className={tw(\n 'inline-flex items-center rounded-full px-2 py-0.5 text-xs font-normal bg-primary-500/10 text-primary-500',\n isDisabled && 'opacity-50'\n )}\n >\n <span className=\"truncate\">{children}</span>\n {onRemove && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n onRemove()\n }}\n className=\"inline-flex items-center justify-center h-4 w-4 -m-0.5 p-0.5 rounded-full translate-x-1 hover:bg-black/10\"\n aria-label=\"Remove\"\n tabIndex={-1}\n >\n <Icon name=\"x-mark\" className=\"h-2 w-2\" aria-hidden=\"true\" />\n </button>\n )}\n </span>\n )\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"date-input","files":[{"name":"components/date-input.tsx","content":"import * as React from 'react'\nimport { DateField, DateInput as AriaDateInput, DateSegment, I18nProvider } from 'react-aria-components'\nimport { CalendarDate, parseDate } from '@internationalized/date'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { dateFns, type Iso } from 'iso-fns'\n\nexport type DateInputProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<Iso.Date | null>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management (ISO date string YYYY-MM-DD)\n value?: Iso.Date | null\n defaultValue?: Iso.Date | null\n onChange?: (value: Iso.Date | null) => void\n\n // Date specific (ISO date strings)\n minValue?: Iso.Date\n maxValue?: Iso.Date\n isDateUnavailable?: (date: Iso.Date) => boolean\n\n // Locale\n locale?: string\n shape?: 'rectangle' | 'oval'\n}\n\nexport function DateInput(props: DateInputProps) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const labelId = React.useId()\n\n // Internal state for CalendarDate\n const [value, _setValue] = React.useState(() => {\n try {\n const startingDate = props.value === undefined ? (props.defaultValue ?? null) : props.value\n return startingDate ? parseDate(startingDate) : null\n } catch {\n return null\n }\n })\n\n function setValue(d: CalendarDate | null) {\n _setValue(d)\n if (!d || dateFns.isValid(d.toString())) {\n props.onChange && props.onChange(d ? (d.toString() as Iso.Date) : null)\n }\n commitValidation()\n }\n\n // Sync controlled value\n if (props.value !== undefined) {\n const currentValueAsIso = value && dateFns.isValid(value.toString()) ? value.toString() : null\n const propValue = props.value && dateFns.isValid(props.value) ? props.value : null\n if ((!value || dateFns.isValid(value.toString())) && propValue !== currentValueAsIso) {\n try {\n _setValue(propValue ? parseDate(propValue) : null)\n } catch {\n _setValue(null)\n }\n }\n }\n\n // Convert min/max dates\n const minDate = React.useMemo(() => {\n if (!props.minValue) return undefined\n try {\n return parseDate(props.minValue)\n } catch {\n return undefined\n }\n }, [props.minValue])\n\n const maxDate = React.useMemo(() => {\n if (!props.maxValue) return undefined\n try {\n return parseDate(props.maxValue)\n } catch {\n return undefined\n }\n }, [props.maxValue])\n\n // Form validation\n const {\n validationMessage: validationMessageRaw,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: value && dateFns.isValid(value.toString()) ? (value.toString() as Iso.Date) : null,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n // Focus handled by DateField\n }\n },\n hiddenInputRef as React.RefObject<HTMLInputElement>\n )\n\n const lessThanMin =\n props.minValue && value && dateFns.isValid(value.toString()) ? value.toString() < props.minValue : false\n const greaterThanMax =\n props.maxValue && value && dateFns.isValid(value.toString()) ? value.toString() > props.maxValue : false\n\n const validationMessage =\n validationMessageRaw ||\n (lessThanMin ? `Cannot be less than ${dateFns.format(props.minValue!, 'MM/dd/yyyy')}` : '') ||\n (greaterThanMax ? `Cannot be greater than ${dateFns.format(props.maxValue!, 'MM/dd/yyyy')}` : '')\n\n const isInvalid = props.isInvalid || validationInvalid || lessThanMin || greaterThanMax\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n return (\n <I18nProvider locale={props.locale ?? 'en'}>\n <div className=\"relative\">\n {/* Hidden input for form submission */}\n <HeadlessForm.HiddenInput ref={hiddenInputRef} name={props.name} value={value?.toString() ?? ''} form={props.form} />\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} labelId={labelId} />\n\n {/* Date input */}\n <DateField\n aria-labelledby={labelId}\n aria-describedby={helpTextId}\n value={value}\n onChange={setValue}\n minValue={minDate}\n maxValue={maxDate}\n isDisabled={props.isDisabled}\n isReadOnly={props.isReadonly}\n isInvalid={isInvalid}\n isRequired={props.isRequired}\n isDateUnavailable={\n props.isDateUnavailable ? (date) => props.isDateUnavailable!(date.toString() as Iso.Date) : undefined\n }\n >\n <FormFieldComponents.FieldGroup\n isDisabled={props.isDisabled}\n isReadonly={props.isReadonly}\n isInvalid={isInvalid}\n hasWarning={hasWarning}\n shape={props.shape}\n >\n <AriaDateInput className=\"flex px-3 py-2 text-sm\">\n {(segment) => (\n <DateSegment\n segment={segment}\n className={tw(\n 'px-0.5 tabular-nums outline-none rounded-sm',\n 'focus:bg-primary-500 focus:text-white',\n segment.isPlaceholder && 'text-neutral-400'\n )}\n />\n )}\n </AriaDateInput>\n </FormFieldComponents.FieldGroup>\n </DateField>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n </I18nProvider>\n )\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"date-range-picker","files":[{"name":"components/date-range-picker.tsx","content":"import * as React from 'react'\nimport {\n DateRangePicker as AriaDateRangePicker,\n DateInput,\n DateSegment,\n Button,\n Dialog,\n RangeCalendar,\n CalendarGrid,\n CalendarCell,\n Heading,\n CalendarGridHeader,\n CalendarHeaderCell,\n CalendarGridBody,\n I18nProvider\n} from 'react-aria-components'\nimport { AnimatedPopover } from './helpers/animated-popover'\nimport { CalendarDate, parseDate } from '@internationalized/date'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { Icon } from './icon'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\n\nexport type DateRange = {\n start: string | null\n end: string | null\n}\n\nexport type DateRangePickerProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<DateRange>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management\n value?: DateRange\n defaultValue?: DateRange\n onChange?: (value: DateRange) => void\n\n // Date specific (ISO date strings)\n minValue?: string\n maxValue?: string\n presetRanges?: Array<{\n label: string\n start: string\n end: string\n }>\n\n // Calendar display\n locale?: string\n shape?: 'rectangle' | 'oval'\n}\n\nexport function DateRangePicker(props: DateRangePickerProps) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const labelId = React.useId()\n\n // Controlled state management\n const [value, setValue] = HeadlessForm.useControlledState<DateRange>(\n props.value,\n props.defaultValue ?? { start: null, end: null },\n props.onChange\n )\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n // Focus handled by AriaDateRangePicker\n }\n },\n hiddenInputRef as React.RefObject<HTMLInputElement>\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n // Convert ISO strings to CalendarDate for aria components\n const ariaValue = React.useMemo(() => {\n if (!value.start || !value.end) return null\n try {\n return {\n start: parseDate(value.start),\n end: parseDate(value.end)\n }\n } catch {\n return null\n }\n }, [value])\n\n const minDate = React.useMemo(() => {\n if (!props.minValue) return undefined\n try {\n return parseDate(props.minValue)\n } catch {\n return undefined\n }\n }, [props.minValue])\n\n const maxDate = React.useMemo(() => {\n if (!props.maxValue) return undefined\n try {\n return parseDate(props.maxValue)\n } catch {\n return undefined\n }\n }, [props.maxValue])\n\n const handleRangeChange = (range: { start: CalendarDate; end: CalendarDate } | null) => {\n if (!range) {\n setValue({ start: null, end: null })\n } else {\n setValue({\n start: range.start.toString(),\n end: range.end.toString()\n })\n }\n commitValidation()\n }\n\n const handlePresetClick = (preset: { start: string; end: string }) => {\n setValue({ start: preset.start, end: preset.end })\n commitValidation()\n }\n\n return (\n <I18nProvider locale={props.locale ?? 'en'}>\n <div className=\"relative\">\n {/* Hidden inputs for form submission */}\n <HeadlessForm.HiddenInput\n ref={hiddenInputRef}\n name={props.name ? `${props.name}.start` : undefined}\n value={value.start || ''}\n form={props.form}\n />\n <HeadlessForm.HiddenInput\n name={props.name ? `${props.name}.end` : undefined}\n value={value.end || ''}\n form={props.form}\n />\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} labelId={labelId} />\n\n {/* Preset ranges */}\n {props.presetRanges && props.presetRanges.length > 0 && (\n <div className=\"flex flex-wrap gap-2 mb-2\">\n {props.presetRanges.map((preset, i) => (\n <button\n key={i}\n type=\"button\"\n onClick={() => handlePresetClick(preset)}\n disabled={props.isDisabled}\n className=\"px-2 py-1 text-xs rounded border border-neutral-200 hover:bg-neutral-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {preset.label}\n </button>\n ))}\n </div>\n )}\n\n {/* Date range picker */}\n <AriaDateRangePicker\n aria-labelledby={labelId}\n value={ariaValue}\n onChange={handleRangeChange}\n minValue={minDate}\n maxValue={maxDate}\n isDisabled={props.isDisabled}\n isReadOnly={props.isReadonly}\n isInvalid={isInvalid}\n isRequired={props.isRequired}\n >\n <FormFieldComponents.FieldGroup\n isDisabled={props.isDisabled}\n isReadonly={props.isReadonly}\n isInvalid={isInvalid}\n hasWarning={hasWarning}\n shape={props.shape}\n className=\"flex\"\n >\n <div className=\"flex items-center justify-start flex-1 px-3 py-2 text-sm\">\n <DateInput slot=\"start\" className=\"flex\">\n {(segment) => (\n <DateSegment\n segment={segment}\n className={tw('px-0.5 tabular-nums outline-none rounded-sm', 'focus:bg-primary-500 focus:text-white')}\n />\n )}\n </DateInput>\n <span className=\"mx-2 text-neutral-400\">–</span>\n <DateInput slot=\"end\" className=\"flex\">\n {(segment) => (\n <DateSegment\n segment={segment}\n className={tw('px-0.5 tabular-nums outline-none rounded-sm', 'focus:bg-primary-500 focus:text-white')}\n />\n )}\n </DateInput>\n </div>\n <Button className=\"flex items-center px-2 py-2\">\n <Icon name=\"calendar\" className=\"h-4 w-4 text-neutral-400\" />\n </Button>\n </FormFieldComponents.FieldGroup>\n\n <AnimatedPopover>\n <Dialog>\n <RangeCalendar className=\"bg-white border border-neutral-200 rounded-md shadow-lg p-3\">\n <header className=\"flex items-center justify-between mb-2\">\n <Button slot=\"previous\" className=\"p-1 hover:bg-neutral-100 rounded\">\n <Icon name=\"chevron-left\" className=\"h-4 w-4\" />\n </Button>\n <Heading className=\"text-sm font-semibold text-neutral-900 flex-1 text-center\" />\n <Button slot=\"next\" className=\"p-1 hover:bg-neutral-100 rounded\">\n <Icon name=\"chevron-right\" className=\"h-4 w-4\" />\n </Button>\n </header>\n <CalendarGrid>\n <CalendarGridHeader>\n {(day) => (\n <CalendarHeaderCell className=\"text-xs text-neutral-500 font-medium w-8 h-8\">{day}</CalendarHeaderCell>\n )}\n </CalendarGridHeader>\n <CalendarGridBody>\n {(date) => (\n <CalendarCell\n date={date}\n className={({\n isSelected,\n isSelectionStart,\n isSelectionEnd,\n isOutsideMonth,\n isDisabled,\n isUnavailable,\n isFocusVisible\n }) =>\n tw(\n 'w-8 h-8 text-sm rounded cursor-pointer flex items-center justify-center',\n 'hover:bg-neutral-100',\n isOutsideMonth && 'text-neutral-300',\n (isDisabled || isUnavailable) && 'text-neutral-300 cursor-not-allowed',\n isSelected && !isSelectionStart && !isSelectionEnd && 'bg-primary-500/20',\n (isSelectionStart || isSelectionEnd) && 'bg-primary-500 text-white hover:bg-primary-500',\n isFocusVisible && 'ring-2 ring-primary-500 ring-offset-1'\n )\n }\n />\n )}\n </CalendarGridBody>\n </CalendarGrid>\n </RangeCalendar>\n </Dialog>\n </AnimatedPopover>\n </AriaDateRangePicker>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n </I18nProvider>\n )\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/animated-popover.tsx","content":"import { Popover, type PopoverProps } from 'react-aria-components'\nimport { tw } from '../../utils/tw'\n\nconst animationClasses = tw(\n 'transition-all duration-150 ease-out',\n // resting state: keep transform properties defined so transitions have endpoints\n 'translate-x-0 translate-y-0',\n // entering: fade + slide from trigger direction\n 'data-[entering]:opacity-0',\n 'data-[entering]:data-[placement=bottom]:-translate-y-1',\n 'data-[entering]:data-[placement=top]:translate-y-1',\n 'data-[entering]:data-[placement=left]:translate-x-1',\n 'data-[entering]:data-[placement=right]:-translate-x-1',\n // exiting: fade + slide toward trigger direction\n 'data-[exiting]:opacity-0',\n 'data-[exiting]:data-[placement=bottom]:-translate-y-1',\n 'data-[exiting]:data-[placement=top]:translate-y-1',\n 'data-[exiting]:data-[placement=left]:translate-x-1',\n 'data-[exiting]:data-[placement=right]:-translate-x-1'\n)\n\nexport function AnimatedPopover({ className, ...props }: PopoverProps) {\n return <Popover {...props} className={tw(animationClasses, className)} />\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"date-time-picker","files":[{"name":"components/date-time-picker.tsx","content":"import * as React from 'react'\nimport { DatePicker as AriaDatePicker, DateInput, DateSegment, Button, Dialog, I18nProvider } from 'react-aria-components'\nimport { AnimatedPopover } from './helpers/animated-popover'\nimport { CalendarDateTime, parseDateTime } from '@internationalized/date'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { Icon } from './icon'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { CustomCalendar } from './helpers/calendar-month-year-picker'\nimport { dateFns, dateTimeFns, type Iso } from 'iso-fns'\n\nexport type DateTimePickerProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<Iso.DateTime | null>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management (ISO datetime string e.g. 2024-01-15T09:30:00)\n value?: Iso.DateTime | null\n defaultValue?: Iso.DateTime | null\n onChange?: (value: Iso.DateTime | null) => void\n\n // Date/Time specific\n minValue?: Iso.DateTime\n maxValue?: Iso.DateTime\n use24Hour?: boolean\n minuteStep?: number\n locale?: string\n shape?: 'rectangle' | 'oval'\n}\n\nexport function DateTimePicker(props: DateTimePickerProps) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const labelId = React.useId()\n\n // Internal state for CalendarDateTime\n const [value, _setValue] = React.useState(() => {\n try {\n const startingValue = props.value === undefined ? (props.defaultValue ?? null) : props.value\n return startingValue ? parseDateTime(startingValue) : null\n } catch {\n return null\n }\n })\n\n function setValue(dt: CalendarDateTime | null) {\n _setValue(dt)\n if (!dt || dateTimeFns.isValid(dt.toString())) {\n props.onChange && props.onChange(dt ? (dt.toString() as Iso.DateTime) : null)\n }\n commitValidation()\n }\n\n // Sync controlled value\n if (props.value !== undefined) {\n const currentValueAsIso = value && dateTimeFns.isValid(value.toString()) ? value.toString() : null\n const propValue = props.value && dateTimeFns.isValid(props.value) ? props.value : null\n if ((!value || dateTimeFns.isValid(value.toString())) && propValue !== currentValueAsIso) {\n try {\n _setValue(propValue ? parseDateTime(propValue) : null)\n } catch {\n _setValue(null)\n }\n }\n }\n\n // Convert min/max values\n const minDateTime = React.useMemo(() => {\n if (!props.minValue) return undefined\n try {\n return parseDateTime(props.minValue)\n } catch {\n return undefined\n }\n }, [props.minValue])\n\n const maxDateTime = React.useMemo(() => {\n if (!props.maxValue) return undefined\n try {\n return parseDateTime(props.maxValue)\n } catch {\n return undefined\n }\n }, [props.maxValue])\n\n // Form validation\n const {\n validationMessage: validationMessageRaw,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: value && dateTimeFns.isValid(value.toString()) ? (value.toString() as Iso.DateTime) : null,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n // Focus handled by AriaDatePicker\n }\n },\n hiddenInputRef as React.RefObject<HTMLInputElement>\n )\n\n const lessThanMin =\n props.minValue && value && dateTimeFns.isValid(value.toString()) ? value.toString() < props.minValue : false\n const greaterThanMax =\n props.maxValue && value && dateTimeFns.isValid(value.toString()) ? value.toString() > props.maxValue : false\n\n const validationMessage =\n validationMessageRaw ||\n (lessThanMin ? `Cannot be before ${dateTimeFns.format(props.minValue!, 'MM/dd/yyyy h:mm a')}` : '') ||\n (greaterThanMax ? `Cannot be after ${dateTimeFns.format(props.maxValue!, 'MM/dd/yyyy h:mm a')}` : '')\n\n const isInvalid = props.isInvalid || validationInvalid || lessThanMin || greaterThanMax\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n return (\n <I18nProvider locale={props.locale ?? 'en'}>\n <div className=\"relative\">\n {/* Hidden input for form submission */}\n <HeadlessForm.HiddenInput ref={hiddenInputRef} name={props.name} value={value?.toString() ?? ''} form={props.form} />\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} labelId={labelId} />\n\n <AriaDatePicker\n value={value}\n aria-labelledby={labelId}\n aria-describedby={helpTextId}\n onChange={setValue}\n minValue={minDateTime}\n maxValue={maxDateTime}\n isDisabled={props.isDisabled}\n isReadOnly={props.isReadonly}\n isInvalid={isInvalid}\n isRequired={props.isRequired}\n granularity=\"minute\"\n hideTimeZone\n hourCycle={props.use24Hour ? 24 : 12}\n >\n <FormFieldComponents.FieldGroup\n isDisabled={props.isDisabled}\n isReadonly={props.isReadonly}\n isInvalid={isInvalid}\n hasWarning={hasWarning}\n shape={props.shape}\n className=\"flex\"\n >\n <DateInput className=\"flex-1 grow flex px-3 py-2 text-sm\">\n {(segment) => (\n <DateSegment\n segment={segment}\n className={tw('px-0.5 tabular-nums outline-none rounded-sm', 'focus:bg-primary-500 focus:text-white')}\n />\n )}\n </DateInput>\n <Button className=\"flex items-center px-2 py-2\">\n <Icon name=\"calendar\" className=\"h-4 w-4 text-neutral-400\" />\n </Button>\n </FormFieldComponents.FieldGroup>\n\n <AnimatedPopover>\n <Dialog>\n <CustomCalendar value={value} onChange={setValue} minValue={minDateTime} maxValue={maxDateTime} />\n </Dialog>\n </AnimatedPopover>\n </AriaDatePicker>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n </I18nProvider>\n )\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/animated-popover.tsx","content":"import { Popover, type PopoverProps } from 'react-aria-components'\nimport { tw } from '../../utils/tw'\n\nconst animationClasses = tw(\n 'transition-all duration-150 ease-out',\n // resting state: keep transform properties defined so transitions have endpoints\n 'translate-x-0 translate-y-0',\n // entering: fade + slide from trigger direction\n 'data-[entering]:opacity-0',\n 'data-[entering]:data-[placement=bottom]:-translate-y-1',\n 'data-[entering]:data-[placement=top]:translate-y-1',\n 'data-[entering]:data-[placement=left]:translate-x-1',\n 'data-[entering]:data-[placement=right]:-translate-x-1',\n // exiting: fade + slide toward trigger direction\n 'data-[exiting]:opacity-0',\n 'data-[exiting]:data-[placement=bottom]:-translate-y-1',\n 'data-[exiting]:data-[placement=top]:translate-y-1',\n 'data-[exiting]:data-[placement=left]:translate-x-1',\n 'data-[exiting]:data-[placement=right]:-translate-x-1'\n)\n\nexport function AnimatedPopover({ className, ...props }: PopoverProps) {\n return <Popover {...props} className={tw(animationClasses, className)} />\n}\n"},{"name":"components/helpers/calendar-month-year-picker.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Calendar,\n CalendarGrid,\n CalendarCell,\n Heading,\n CalendarGridHeader,\n CalendarHeaderCell,\n CalendarGridBody\n} from 'react-aria-components'\nimport { CalendarDate, CalendarDateTime } from '@internationalized/date'\nimport { tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\ntype MonthYearPickerProps = {\n currentDate: CalendarDate | CalendarDateTime\n onSelect: (year: number, month: number) => void\n onCancel: () => void\n}\n\nexport function MonthYearPicker({ currentDate, onSelect, onCancel }: MonthYearPickerProps) {\n const [selectedYear, setSelectedYear] = React.useState(currentDate.year)\n const [viewingYear, setViewingYear] = React.useState(currentDate.year)\n\n const months = [\n 'January',\n 'February',\n 'March',\n 'April',\n 'May',\n 'June',\n 'July',\n 'August',\n 'September',\n 'October',\n 'November',\n 'December'\n ]\n\n // Generate year range (current year ± 50 years)\n const startYear = currentDate.year - 50\n const endYear = currentDate.year + 50\n const years = Array.from({ length: endYear - startYear + 1 }, (_, i) => startYear + i)\n\n // Find years to display (show 12 years at a time, 3x4 grid)\n const yearIndex = years.indexOf(viewingYear)\n const displayYears = years.slice(Math.max(0, yearIndex - 5), Math.min(years.length, yearIndex + 7))\n\n const handlePreviousYears = () => {\n setViewingYear((prev) => prev - 12)\n }\n\n const handleNextYears = () => {\n setViewingYear((prev) => prev + 12)\n }\n\n return (\n <div className=\"bg-white border border-neutral-200 rounded-md shadow-lg p-4 w-80\">\n {/* Year selection header */}\n <div className=\"mb-4\">\n <header className=\"flex items-center justify-between mb-3\">\n <Button className=\"p-1 hover:bg-neutral-100 rounded\" onPress={handlePreviousYears}>\n <Icon name=\"chevron-left\" className=\"h-4 w-4\" />\n </Button>\n <div className=\"text-sm font-semibold text-neutral-900\">\n {displayYears[0]} - {displayYears[displayYears.length - 1]}\n </div>\n <Button className=\"p-1 hover:bg-neutral-100 rounded\" onPress={handleNextYears}>\n <Icon name=\"chevron-right\" className=\"h-4 w-4\" />\n </Button>\n </header>\n\n {/* Year grid */}\n <div className=\"grid grid-cols-3 gap-2\">\n {displayYears.map((year) => (\n <button\n key={year}\n type=\"button\"\n onClick={() => setSelectedYear(year)}\n className={tw(\n 'py-2 px-3 text-sm rounded cursor-pointer transition-colors',\n 'hover:bg-neutral-100',\n selectedYear === year && 'bg-primary-500 text-white hover:bg-primary-500',\n year === currentDate.year && selectedYear !== year && 'font-semibold'\n )}\n >\n {year}\n </button>\n ))}\n </div>\n </div>\n\n {/* Month selection */}\n <div>\n <div className=\"text-sm font-semibold text-neutral-900 mb-2\">{selectedYear}</div>\n <div className=\"grid grid-cols-3 gap-2\">\n {months.map((month, index) => {\n const monthNum = index + 1\n const isCurrentMonth = selectedYear === currentDate.year && monthNum === currentDate.month\n\n return (\n <button\n key={month}\n type=\"button\"\n onClick={() => onSelect(selectedYear, monthNum)}\n className={tw(\n 'py-2 px-2 text-sm rounded cursor-pointer transition-colors',\n 'hover:bg-neutral-100',\n isCurrentMonth && 'font-semibold border border-primary-500'\n )}\n >\n {month.slice(0, 3)}\n </button>\n )\n })}\n </div>\n </div>\n\n {/* Cancel button */}\n <div className=\"mt-4 flex justify-end\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"py-1 px-3 text-sm text-neutral-600 hover:text-neutral-900 hover:bg-neutral-100 rounded transition-colors\"\n >\n Cancel\n </button>\n </div>\n </div>\n )\n}\n\ntype CustomCalendarPropsBase = {\n isDateUnavailable?: (date: CalendarDate | CalendarDateTime) => boolean\n}\n\ntype CustomCalendarPropsDate = CustomCalendarPropsBase & {\n value: CalendarDate | null\n onChange: (date: CalendarDate | null) => void\n minValue?: CalendarDate\n maxValue?: CalendarDate\n}\n\ntype CustomCalendarPropsDateTime = CustomCalendarPropsBase & {\n value: CalendarDateTime | null\n onChange: (date: CalendarDateTime | null) => void\n minValue?: CalendarDateTime\n maxValue?: CalendarDateTime\n}\n\ntype CustomCalendarProps = CustomCalendarPropsDate | CustomCalendarPropsDateTime\n\nexport function CustomCalendar(props: CustomCalendarProps) {\n const { value, onChange, minValue, maxValue, isDateUnavailable } = props\n const [showMonthYearPicker, setShowMonthYearPicker] = React.useState(false)\n\n // Create a default focused date\n const createDefaultDate = () => {\n const now = new Date()\n const year = now.getFullYear()\n const month = now.getMonth() + 1\n const day = now.getDate()\n\n // Check if we're working with CalendarDateTime\n if (value && 'hour' in value) {\n return new CalendarDateTime(year, month, day, 0, 0, 0)\n }\n return new CalendarDate(year, month, day)\n }\n\n const [focusedDate, setFocusedDate] = React.useState(value || createDefaultDate())\n\n const handleMonthYearSelect = (year: number, month: number) => {\n if ('hour' in focusedDate) {\n // CalendarDateTime\n const newDate = focusedDate.set({ year, month })\n setFocusedDate(newDate)\n } else {\n // CalendarDate - use safe day value\n const newDate = new CalendarDate(year, month, Math.min(focusedDate.day, 28))\n setFocusedDate(newDate)\n }\n setShowMonthYearPicker(false)\n }\n\n const handleFocusChange = (date: CalendarDate) => {\n if ('hour' in focusedDate) {\n // Convert CalendarDate to CalendarDateTime by preserving time from focusedDate\n const newDateTime = focusedDate.set({\n year: date.year,\n month: date.month,\n day: date.day\n })\n setFocusedDate(newDateTime)\n } else {\n setFocusedDate(date)\n }\n }\n\n if (showMonthYearPicker) {\n return (\n <MonthYearPicker\n currentDate={focusedDate}\n onSelect={handleMonthYearSelect}\n onCancel={() => setShowMonthYearPicker(false)}\n />\n )\n }\n\n const handleCalendarChange = (date: CalendarDate | CalendarDateTime | null) => {\n if (typeof onChange === 'function') {\n // Type assertion needed due to union type constraints\n ;(onChange as (date: CalendarDate | CalendarDateTime | null) => void)(date)\n }\n }\n\n return (\n <Calendar\n value={value ?? undefined}\n onChange={handleCalendarChange}\n focusedValue={focusedDate}\n onFocusChange={handleFocusChange}\n minValue={minValue !== undefined && minValue !== null ? minValue : undefined}\n maxValue={maxValue !== undefined && maxValue !== null ? maxValue : undefined}\n isDateUnavailable={\n isDateUnavailable\n ? (date) => {\n // Convert DateValue to CalendarDate for the callback\n if ('hour' in date) {\n // It's a CalendarDateTime, convert to CalendarDate\n const calendarDate = new CalendarDate(date.year, date.month, date.day)\n return isDateUnavailable(calendarDate)\n }\n return isDateUnavailable(date as CalendarDate)\n }\n : undefined\n }\n className=\"bg-white border border-neutral-200 rounded-md shadow-lg p-3\"\n >\n <header className=\"flex items-center justify-between mb-2\">\n <Button slot=\"previous\" className=\"p-1 hover:bg-neutral-100 rounded\">\n <Icon name=\"chevron-left\" className=\"h-4 w-4\" />\n </Button>\n <button\n type=\"button\"\n onClick={() => setShowMonthYearPicker(true)}\n className=\"text-sm font-semibold text-neutral-900 hover:bg-neutral-100 px-2 py-1 rounded transition-colors\"\n >\n <Heading />\n </button>\n <Button slot=\"next\" className=\"p-1 hover:bg-neutral-100 rounded\">\n <Icon name=\"chevron-right\" className=\"h-4 w-4\" />\n </Button>\n </header>\n <CalendarGrid>\n <CalendarGridHeader>\n {(day) => <CalendarHeaderCell className=\"text-xs text-neutral-500 font-medium w-8 h-8\">{day}</CalendarHeaderCell>}\n </CalendarGridHeader>\n <CalendarGridBody>\n {(date) => (\n <CalendarCell\n date={date}\n className={({ isSelected, isOutsideMonth, isDisabled, isUnavailable, isFocusVisible }) =>\n tw(\n 'w-8 h-8 text-sm rounded cursor-pointer flex items-center justify-center',\n 'hover:bg-neutral-100',\n isOutsideMonth && 'text-neutral-300',\n (isDisabled || isUnavailable) && 'text-neutral-300 cursor-not-allowed',\n isSelected && 'bg-primary-500 text-white hover:bg-primary-500',\n isFocusVisible && 'ring-2 ring-primary-500 ring-offset-1'\n )\n }\n />\n )}\n </CalendarGridBody>\n </CalendarGrid>\n </Calendar>\n )\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"time-input","files":[{"name":"components/time-input.tsx","content":"import * as React from 'react'\nimport { TimeField, DateInput, DateSegment, I18nProvider } from 'react-aria-components'\nimport { Time, parseTime } from '@internationalized/date'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { timeFns, type Iso } from 'iso-fns'\n\nexport type TimeInputProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<Iso.Time | null>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management (IsoTimeString format HH:mm:ss)\n value?: Iso.Time | null\n defaultValue?: Iso.Time | null\n onChange?: (value: Iso.Time | null) => void\n\n // Time specific\n use24Hour?: boolean\n minuteStep?: number\n minTime?: Iso.Time\n maxTime?: Iso.Time\n\n // Locale\n locale?: string\n shape?: 'rectangle' | 'oval'\n}\n\nexport function TimeInput(props: TimeInputProps) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const labelId = React.useId()\n\n // Internal state for Time object\n const [value, _setValue] = React.useState<Time | null>(() => {\n try {\n const startingTime = props.value === undefined ? (props.defaultValue ?? null) : props.value\n return startingTime ? parseTime(startingTime) : null\n } catch {\n return null\n }\n })\n\n function setValue(t: Time | null) {\n _setValue(t)\n const isoTime = t ? (t.toString() as Iso.Time) : null\n props.onChange?.(isoTime)\n commitValidation()\n }\n\n // Sync controlled value\n if (props.value !== undefined) {\n const currentValueAsIso = value && isValidTimeString(value.toString()) ? value.toString() : null\n const propValue = props.value && isValidTimeString(props.value) ? props.value : null\n if ((!value || isValidTimeString(value.toString())) && propValue !== currentValueAsIso) {\n try {\n _setValue(propValue ? parseTime(propValue) : null)\n } catch {\n _setValue(null)\n }\n }\n }\n\n // Convert min/max times\n const minTime = React.useMemo(() => {\n if (!props.minTime) return undefined\n try {\n return parseTime(props.minTime)\n } catch {\n return undefined\n }\n }, [props.minTime])\n\n const maxTime = React.useMemo(() => {\n if (!props.maxTime) return undefined\n try {\n return parseTime(props.maxTime)\n } catch {\n return undefined\n }\n }, [props.maxTime])\n\n // Form validation\n const {\n validationMessage: validationMessageRaw,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: value && isValidTimeString(value.toString()) ? (value.toString() as Iso.Time) : null,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n // Focus handled by TimeField\n }\n },\n hiddenInputRef as React.RefObject<HTMLInputElement>\n )\n\n const lessThanMin =\n props.minTime && value && isValidTimeString(value.toString()) ? value.toString() < props.minTime : false\n const greaterThanMax =\n props.maxTime && value && isValidTimeString(value.toString()) ? value.toString() > props.maxTime : false\n\n const validationMessage =\n validationMessageRaw ||\n (lessThanMin ? `Cannot be before ${formatTimeString(props.minTime!, 'h:mm a')}` : '') ||\n (greaterThanMax ? `Cannot be after ${formatTimeString(props.maxTime!, 'h:mm a')}` : '')\n\n const isInvalid = props.isInvalid || validationInvalid || lessThanMin || greaterThanMax\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n return (\n <I18nProvider locale={props.locale ?? 'en'}>\n <div className=\"relative\">\n {/* Hidden input for form submission */}\n <HeadlessForm.HiddenInput ref={hiddenInputRef} name={props.name} value={value?.toString() ?? ''} form={props.form} />\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} labelId={labelId} />\n\n {/* Time field */}\n <TimeField\n aria-labelledby={labelId}\n aria-describedby={helpTextId}\n value={value}\n onChange={setValue}\n isRequired={props.isRequired}\n isDisabled={props.isDisabled}\n isReadOnly={props.isReadonly}\n isInvalid={isInvalid}\n minValue={minTime}\n maxValue={maxTime}\n hourCycle={props.use24Hour ? 24 : 12}\n granularity=\"minute\"\n >\n <FormFieldComponents.FieldGroup\n isDisabled={props.isDisabled}\n isReadonly={props.isReadonly}\n isInvalid={isInvalid}\n hasWarning={hasWarning}\n shape={props.shape}\n >\n <DateInput className=\"flex w-full bg-transparent px-3 py-2 text-sm\">\n {(segment) => (\n <DateSegment\n segment={segment}\n className={tw(\n 'px-0.5 tabular-nums outline-none rounded-sm',\n 'focus:bg-primary-500 focus:text-white',\n segment.isPlaceholder && 'text-neutral-400'\n )}\n />\n )}\n </DateInput>\n </FormFieldComponents.FieldGroup>\n </TimeField>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n </I18nProvider>\n )\n}\n\n/** Check if a string is a valid HH:mm:ss time string */\nfunction isValidTimeString(timeStr: string): boolean {\n return timeFns.isValid(timeStr)\n}\n\n/** Format an HH:mm:ss time string using a date-fns format pattern */\nfunction formatTimeString(timeStr: Iso.Time, fmt: string): string {\n return timeFns.format(timeStr, fmt)\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"month-day-input","files":[{"name":"components/month-day-input.tsx","content":"import * as React from 'react'\nimport { DateField, DateInput as AriaDateInput, DateSegment, I18nProvider } from 'react-aria-components'\nimport { CalendarDate } from '@internationalized/date'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { monthDayFns, type Iso } from 'iso-fns'\n\nexport type MonthDayInputProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<Iso.MonthDay | null>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management (ISO month-day string --MM-DD)\n value?: Iso.MonthDay | null\n defaultValue?: Iso.MonthDay | null\n onChange?: (value: Iso.MonthDay | null) => void\n\n // Locale\n locale?: string\n shape?: 'rectangle' | 'oval'\n}\n\nexport function MonthDayInput(props: MonthDayInputProps) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const labelId = React.useId()\n\n // Internal state for CalendarDate (using placeholder year 1972)\n const [value, _setValue] = React.useState(() => {\n try {\n const startingDate = props.value === undefined ? (props.defaultValue ?? null) : props.value\n return startingDate ? parseMonthDay(startingDate) : null\n } catch {\n return null\n }\n })\n\n function setValue(d: CalendarDate | null) {\n _setValue(d)\n const monthDay = formatMonthDay(d)\n props.onChange?.(monthDay)\n commitValidation()\n }\n\n // Sync controlled value\n if (props.value !== undefined) {\n const currentMonthDay = formatMonthDay(value)\n if (props.value !== currentMonthDay) {\n try {\n _setValue(props.value ? parseMonthDay(props.value) : null)\n } catch {\n _setValue(null)\n }\n }\n }\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: formatMonthDay(value),\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n // Focus handled by DateField\n }\n },\n hiddenInputRef as React.RefObject<HTMLInputElement>\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n return (\n <I18nProvider locale={props.locale ?? 'en'}>\n <div className=\"relative\">\n {/* Hidden input for form submission */}\n <HeadlessForm.HiddenInput\n ref={hiddenInputRef}\n name={props.name}\n value={formatMonthDay(value) ?? ''}\n form={props.form}\n />\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} labelId={labelId} />\n\n {/* Month/Day input using DateField */}\n <DateField\n aria-labelledby={labelId}\n aria-describedby={helpTextId}\n value={value}\n onChange={setValue}\n placeholderValue={new CalendarDate(1972, 1, 1)}\n isDisabled={props.isDisabled}\n isReadOnly={props.isReadonly}\n isInvalid={isInvalid}\n isRequired={props.isRequired}\n granularity=\"day\"\n hideTimeZone\n >\n <FormFieldComponents.FieldGroup\n isDisabled={props.isDisabled}\n isReadonly={props.isReadonly}\n isInvalid={isInvalid}\n hasWarning={hasWarning}\n shape={props.shape}\n >\n <AriaDateInput className=\"flex px-3 py-2 text-sm [&>[data-literal]:not(:has(~[data-literal]))]:hidden\">\n {(segment) => {\n const isHidden = segment.type === 'year'\n\n return (\n <DateSegment\n segment={segment}\n data-literal={segment.type === 'literal' ? true : undefined}\n className={tw(\n 'px-0.5 tabular-nums outline-none rounded-sm',\n 'focus:bg-primary-500 focus:text-white',\n segment.isPlaceholder && 'text-neutral-400',\n 'last-of-type:hidden',\n isHidden && 'hidden'\n )}\n />\n )\n }}\n </AriaDateInput>\n </FormFieldComponents.FieldGroup>\n </DateField>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n </I18nProvider>\n )\n}\n\nfunction parseMonthDay(monthDay: Iso.MonthDay): CalendarDate | null {\n if (!monthDayFns.isValid(monthDay)) return null\n // Use a leap-year placeholder so Feb 29 is valid\n return new CalendarDate(1972, monthDayFns.getMonth(monthDay), monthDayFns.getDay(monthDay))\n}\n\nfunction formatMonthDay(date: CalendarDate | null): Iso.MonthDay | null {\n if (!date) return null\n const month = date.month.toString().padStart(2, '0')\n const day = date.day.toString().padStart(2, '0')\n return `--${month}-${day}` as Iso.MonthDay\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"year-month-input","files":[{"name":"components/year-month-input.tsx","content":"import * as React from 'react'\nimport { DateField, DateInput as AriaDateInput, DateSegment, I18nProvider } from 'react-aria-components'\nimport { CalendarDate } from '@internationalized/date'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { yearMonthFns } from 'iso-fns'\n\n/** ISO year-month string in YYYY-MM format */\nexport type YearMonth = string\n\nexport type YearMonthInputProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<YearMonth | null>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management (ISO year-month string YYYY-MM)\n value?: YearMonth | null\n defaultValue?: YearMonth | null\n onChange?: (value: YearMonth | null) => void\n\n // Locale\n locale?: string\n shape?: 'rectangle' | 'oval'\n}\n\nexport function YearMonthInput(props: YearMonthInputProps) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const labelId = React.useId()\n\n // Internal state for CalendarDate\n const [value, _setValue] = React.useState(() => {\n try {\n const startingDate = props.value === undefined ? (props.defaultValue ?? null) : props.value\n return startingDate ? parseYearMonth(startingDate) : null\n } catch {\n return null\n }\n })\n\n function setValue(d: CalendarDate | null) {\n _setValue(d)\n const yearMonth = formatYearMonth(d)\n props.onChange?.(yearMonth)\n commitValidation()\n }\n\n // Sync controlled value\n if (props.value !== undefined) {\n const currentYearMonth = formatYearMonth(value)\n if (props.value !== currentYearMonth) {\n try {\n _setValue(props.value ? parseYearMonth(props.value) : null)\n } catch {\n _setValue(null)\n }\n }\n }\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: formatYearMonth(value),\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n // Focus handled by DateField\n }\n },\n hiddenInputRef as React.RefObject<HTMLInputElement>\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n return (\n <I18nProvider locale={props.locale ?? 'en'}>\n <div className=\"relative\">\n {/* Hidden input for form submission */}\n <HeadlessForm.HiddenInput\n ref={hiddenInputRef}\n name={props.name}\n value={formatYearMonth(value) ?? ''}\n form={props.form}\n />\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} labelId={labelId} />\n\n {/* Year/Month input using DateField */}\n <DateField\n aria-labelledby={labelId}\n aria-describedby={helpTextId}\n value={value}\n onChange={setValue}\n isDisabled={props.isDisabled}\n isReadOnly={props.isReadonly}\n isInvalid={isInvalid}\n isRequired={props.isRequired}\n granularity=\"day\"\n hideTimeZone\n >\n <FormFieldComponents.FieldGroup\n isDisabled={props.isDisabled}\n isReadonly={props.isReadonly}\n isInvalid={isInvalid}\n hasWarning={hasWarning}\n shape={props.shape}\n >\n <AriaDateInput className=\"flex px-3 py-2 text-sm [&>[data-literal]:not(:has(~[data-literal]))]:hidden\">\n {(segment) => {\n // Hide day segment\n const isHidden = segment.type === 'day'\n\n return (\n <DateSegment\n segment={segment}\n data-literal={segment.type === 'literal' ? true : undefined}\n className={tw(\n 'px-0.5 tabular-nums outline-none rounded-sm',\n 'focus:bg-primary-500 focus:text-white',\n segment.isPlaceholder && 'text-neutral-400',\n isHidden && 'hidden'\n )}\n />\n )\n }}\n </AriaDateInput>\n </FormFieldComponents.FieldGroup>\n </DateField>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n </I18nProvider>\n )\n}\n\nfunction parseYearMonth(yearMonth: YearMonth): CalendarDate | null {\n const match = yearMonth.match(/^(\\d{4})-(\\d{2})$/)\n if (!match || match.length < 3) return null\n const year = parseInt(match[1]!, 10)\n const month = parseInt(match[2]!, 10)\n // Use day 1 as placeholder for the CalendarDate\n return new CalendarDate(year, month, 1)\n}\n\nfunction formatYearMonth(date: CalendarDate | null): YearMonth | null {\n if (!date) return null\n const year = date.year.toString().padStart(4, '0')\n const month = date.month.toString().padStart(2, '0')\n const yearMonth = `${year}-${month}`\n return isValidYearMonth(yearMonth) ? yearMonth : null\n}\n\n/** Check if a string is a valid YYYY-MM year-month string */\nfunction isValidYearMonth(yearMonth: string): boolean {\n return yearMonthFns.isValid(yearMonth)\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"select","files":[{"name":"components/select.tsx","content":"import * as React from 'react'\nimport { Select as AriaSelect, Button, ListBoxItem } from 'react-aria-components'\nimport { AnimatedPopover } from './helpers/animated-popover'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { Icon } from './icon'\nimport { getColor, stringToNumber } from '../utils/colors'\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport type SelectOption = {\n value: string | number | null\n label: string\n secondary?: string\n isDisabled?: boolean\n}\n\nexport type SelectProps<T extends SelectOption = SelectOption> = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<T['value'] | null>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management\n value?: T['value'] | null\n defaultValue?: T['value'] | null\n onChange?: (value: T['value'] | null) => void\n\n // Options\n options: T[]\n renderOption?: (props: { option: T; colored: boolean; index: number }) => React.ReactNode\n\n colored?: boolean\n shape?: 'rectangle' | 'oval'\n}\n\n// ── Component ──────────────────────────────────────────────────────────────────\n\nexport function Select<T extends SelectOption = SelectOption>(props: SelectProps<T>) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const buttonRef = React.useRef<HTMLButtonElement>(null)\n const helpTextId = React.useId()\n const labelId = React.useId()\n\n // Controlled state management\n const [value, setValue] = HeadlessForm.useControlledState(props.value, props.defaultValue ?? null, props.onChange)\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n buttonRef.current?.focus()\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n // Find the selected option for display\n const selectedOption = React.useMemo(() => {\n return props.options.find((option) => option.value === value) ?? null\n }, [props.options, value])\n\n const selectedKey = value != null ? toKey(value) : null\n\n return (\n <div className=\"relative\">\n {/* Hidden input for form submission */}\n <HeadlessForm.HiddenInput ref={hiddenInputRef} name={props.name} value={value ?? ''} form={props.form} />\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} labelId={labelId} />\n\n <AriaSelect\n aria-labelledby={props.label ? labelId : undefined}\n aria-label={!props.label ? 'Select' : undefined}\n selectedKey={selectedKey}\n onSelectionChange={(key) => {\n const newValue = fromKey(key, props.options)\n setValue(newValue)\n commitValidation()\n }}\n isDisabled={props.isDisabled || props.isReadonly}\n isInvalid={isInvalid}\n isRequired={props.isRequired}\n >\n {/* Select button */}\n <Button\n ref={buttonRef}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n props.shape === 'oval' ? 'rounded-full' : 'rounded',\n 'focus:outline-none focus:ring-1',\n (props.isDisabled || props.isReadonly) && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n isInvalid && 'border-negative-200 bg-negative-50 focus:border-negative-500 focus:ring-negative-500/20',\n hasWarning && 'border-notice-200 bg-notice-50 focus:border-notice-500 focus:ring-notice-500/20',\n !isInvalid &&\n !hasWarning &&\n !props.isDisabled &&\n !props.isReadonly &&\n 'border-neutral-200 hover:border-neutral-300 focus:border-primary-500 focus:ring-primary-500/20',\n 'w-full cursor-default py-2 pl-3 pr-10 text-left placeholder:text-neutral-400'\n )}\n aria-invalid={isInvalid || undefined}\n aria-describedby={helpTextId}\n >\n {props.colored && selectedOption ? (\n <div\n className=\"absolute left-0 top-0 w-[3px] h-full\"\n style={{ backgroundColor: getColor(stringToNumber(String(selectedOption.value))) }}\n />\n ) : null}\n {selectedOption ? (\n <span className=\"block truncate text-sm min-h-5\">{selectedOption.label}</span>\n ) : (\n <span className=\"block truncate text-sm min-h-5 text-neutral-300 italic\">\n {props.placeholder ?? 'Select an option'}\n </span>\n )}\n <span className=\"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2\">\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400\" />\n </span>\n </Button>\n\n {/* Options dropdown */}\n <AnimatedPopover offset={4} className=\"w-(--trigger-width)\">\n <FormFieldComponents.DropdownListBox\n items={props.options.map((option, index) => ({ ...option, index, id: toKey(option.value) }))}\n renderEmptyState={() => <div className=\"px-3 py-2 text-neutral-500 text-sm\">No options available</div>}\n >\n {(item) => {\n const option = item as T & { index: number; id: string }\n if (props.renderOption) {\n return (\n <ListBoxItem\n id={option.id}\n textValue={option.label}\n isDisabled={option.isDisabled}\n className=\"outline-none\"\n >\n {props.renderOption({ option, colored: props.colored ?? false, index: option.index })}\n </ListBoxItem>\n )\n }\n\n const color = props.colored ? getColor(stringToNumber(String(option.value))) : null\n return (\n <FormFieldComponents.DropdownItem\n id={option.id}\n textValue={option.label}\n label={option.label}\n secondary={option.secondary}\n isDisabled={option.isDisabled}\n color={color}\n />\n )\n }}\n </FormFieldComponents.DropdownListBox>\n </AnimatedPopover>\n </AriaSelect>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────────\n\nconst NULL_KEY = '__select_null__'\n\nfunction toKey(value: string | number | null): string {\n if (value === null) return NULL_KEY\n return String(value)\n}\n\nfunction fromKey<T extends SelectOption>(key: React.Key | null, options: T[]): T['value'] | null {\n if (key === null || key === undefined) return null\n const keyStr = String(key)\n if (keyStr === NULL_KEY) return null\n const option = options.find((o) => toKey(o.value) === keyStr)\n return option?.value ?? null\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/animated-popover.tsx","content":"import { Popover, type PopoverProps } from 'react-aria-components'\nimport { tw } from '../../utils/tw'\n\nconst animationClasses = tw(\n 'transition-all duration-150 ease-out',\n // resting state: keep transform properties defined so transitions have endpoints\n 'translate-x-0 translate-y-0',\n // entering: fade + slide from trigger direction\n 'data-[entering]:opacity-0',\n 'data-[entering]:data-[placement=bottom]:-translate-y-1',\n 'data-[entering]:data-[placement=top]:translate-y-1',\n 'data-[entering]:data-[placement=left]:translate-x-1',\n 'data-[entering]:data-[placement=right]:-translate-x-1',\n // exiting: fade + slide toward trigger direction\n 'data-[exiting]:opacity-0',\n 'data-[exiting]:data-[placement=bottom]:-translate-y-1',\n 'data-[exiting]:data-[placement=top]:translate-y-1',\n 'data-[exiting]:data-[placement=left]:translate-x-1',\n 'data-[exiting]:data-[placement=right]:-translate-x-1'\n)\n\nexport function AnimatedPopover({ className, ...props }: PopoverProps) {\n return <Popover {...props} className={tw(animationClasses, className)} />\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/colors.ts","content":"export const COLORS = [\n '#f43f5e',\n '#3b82f6',\n '#22c55e',\n '#EAB308',\n '#8b5cf6',\n '#191970',\n '#f97316',\n '#6b7280',\n '#ec4899',\n '#06b6d4',\n '#84cc16',\n '#64748b',\n '#ffff00',\n '#6366f1',\n '#ff6347',\n '#737373',\n '#14b8a6',\n '#f59e0b',\n '#0ea5e9',\n '#ef4444',\n '#78716c',\n '#00ffff',\n '#d946ef',\n '#10b981',\n '#b22222',\n '#40e0d0',\n '#71717a',\n '#a855f7',\n '#7fff00'\n] as const\n\nexport function stringToNumber(str: string) {\n let h = 0x811c9dc5 // FNV offset basis\n for (const b of new TextEncoder().encode(str)) {\n h ^= b\n h = Math.imul(h, 0x01000193) // 16777619\n }\n return h >>> 0 // unsigned 32-bit\n}\n\nexport function getColor(seed: number) {\n return COLORS[seed % COLORS.length]\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"boolean-select","files":[{"name":"components/boolean-select.tsx","content":"import * as React from 'react'\nimport { Select, type SelectProps, type SelectOption } from './select'\n\nexport type BooleanSelectProps = Omit<SelectProps, 'options' | 'value' | 'defaultValue' | 'onChange'> & {\n // Value management\n value?: boolean | null\n defaultValue?: boolean | null\n onChange?: (value: boolean | null) => void\n\n // Customizable labels\n trueLabel?: string\n falseLabel?: string\n nullLabel?: string\n includeNull?: boolean\n}\n\nexport function BooleanSelect({\n trueLabel = 'Yes',\n falseLabel = 'No',\n nullLabel = 'Not Set',\n includeNull = false,\n value,\n defaultValue,\n onChange,\n ...restProps\n}: BooleanSelectProps) {\n const options: SelectOption[] = [\n ...(includeNull ? [{ value: 'null', label: nullLabel }] : []),\n { value: 'true', label: trueLabel },\n { value: 'false', label: falseLabel }\n ]\n\n // Convert boolean value to string for Select component\n const stringValue = value === null ? 'null' : value === undefined ? undefined : String(value)\n const stringDefaultValue = defaultValue === null ? 'null' : defaultValue === undefined ? undefined : String(defaultValue)\n\n // Convert string value back to boolean for onChange\n const handleChange = (val: string | number | null) => {\n if (!onChange) return\n if (val === 'null' || val === null) onChange(null)\n else if (val === 'true') onChange(true)\n else if (val === 'false') onChange(false)\n }\n\n return (\n <Select {...restProps} options={options} value={stringValue} defaultValue={stringDefaultValue} onChange={handleChange} />\n )\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/select.tsx","content":"import * as React from 'react'\nimport { Select as AriaSelect, Button, ListBoxItem } from 'react-aria-components'\nimport { AnimatedPopover } from './helpers/animated-popover'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { Icon } from './icon'\nimport { getColor, stringToNumber } from '../utils/colors'\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport type SelectOption = {\n value: string | number | null\n label: string\n secondary?: string\n isDisabled?: boolean\n}\n\nexport type SelectProps<T extends SelectOption = SelectOption> = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<T['value'] | null>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management\n value?: T['value'] | null\n defaultValue?: T['value'] | null\n onChange?: (value: T['value'] | null) => void\n\n // Options\n options: T[]\n renderOption?: (props: { option: T; colored: boolean; index: number }) => React.ReactNode\n\n colored?: boolean\n shape?: 'rectangle' | 'oval'\n}\n\n// ── Component ──────────────────────────────────────────────────────────────────\n\nexport function Select<T extends SelectOption = SelectOption>(props: SelectProps<T>) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const buttonRef = React.useRef<HTMLButtonElement>(null)\n const helpTextId = React.useId()\n const labelId = React.useId()\n\n // Controlled state management\n const [value, setValue] = HeadlessForm.useControlledState(props.value, props.defaultValue ?? null, props.onChange)\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n buttonRef.current?.focus()\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n // Find the selected option for display\n const selectedOption = React.useMemo(() => {\n return props.options.find((option) => option.value === value) ?? null\n }, [props.options, value])\n\n const selectedKey = value != null ? toKey(value) : null\n\n return (\n <div className=\"relative\">\n {/* Hidden input for form submission */}\n <HeadlessForm.HiddenInput ref={hiddenInputRef} name={props.name} value={value ?? ''} form={props.form} />\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} labelId={labelId} />\n\n <AriaSelect\n aria-labelledby={props.label ? labelId : undefined}\n aria-label={!props.label ? 'Select' : undefined}\n selectedKey={selectedKey}\n onSelectionChange={(key) => {\n const newValue = fromKey(key, props.options)\n setValue(newValue)\n commitValidation()\n }}\n isDisabled={props.isDisabled || props.isReadonly}\n isInvalid={isInvalid}\n isRequired={props.isRequired}\n >\n {/* Select button */}\n <Button\n ref={buttonRef}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n props.shape === 'oval' ? 'rounded-full' : 'rounded',\n 'focus:outline-none focus:ring-1',\n (props.isDisabled || props.isReadonly) && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n isInvalid && 'border-negative-200 bg-negative-50 focus:border-negative-500 focus:ring-negative-500/20',\n hasWarning && 'border-notice-200 bg-notice-50 focus:border-notice-500 focus:ring-notice-500/20',\n !isInvalid &&\n !hasWarning &&\n !props.isDisabled &&\n !props.isReadonly &&\n 'border-neutral-200 hover:border-neutral-300 focus:border-primary-500 focus:ring-primary-500/20',\n 'w-full cursor-default py-2 pl-3 pr-10 text-left placeholder:text-neutral-400'\n )}\n aria-invalid={isInvalid || undefined}\n aria-describedby={helpTextId}\n >\n {props.colored && selectedOption ? (\n <div\n className=\"absolute left-0 top-0 w-[3px] h-full\"\n style={{ backgroundColor: getColor(stringToNumber(String(selectedOption.value))) }}\n />\n ) : null}\n {selectedOption ? (\n <span className=\"block truncate text-sm min-h-5\">{selectedOption.label}</span>\n ) : (\n <span className=\"block truncate text-sm min-h-5 text-neutral-300 italic\">\n {props.placeholder ?? 'Select an option'}\n </span>\n )}\n <span className=\"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2\">\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400\" />\n </span>\n </Button>\n\n {/* Options dropdown */}\n <AnimatedPopover offset={4} className=\"w-(--trigger-width)\">\n <FormFieldComponents.DropdownListBox\n items={props.options.map((option, index) => ({ ...option, index, id: toKey(option.value) }))}\n renderEmptyState={() => <div className=\"px-3 py-2 text-neutral-500 text-sm\">No options available</div>}\n >\n {(item) => {\n const option = item as T & { index: number; id: string }\n if (props.renderOption) {\n return (\n <ListBoxItem\n id={option.id}\n textValue={option.label}\n isDisabled={option.isDisabled}\n className=\"outline-none\"\n >\n {props.renderOption({ option, colored: props.colored ?? false, index: option.index })}\n </ListBoxItem>\n )\n }\n\n const color = props.colored ? getColor(stringToNumber(String(option.value))) : null\n return (\n <FormFieldComponents.DropdownItem\n id={option.id}\n textValue={option.label}\n label={option.label}\n secondary={option.secondary}\n isDisabled={option.isDisabled}\n color={color}\n />\n )\n }}\n </FormFieldComponents.DropdownListBox>\n </AnimatedPopover>\n </AriaSelect>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────────\n\nconst NULL_KEY = '__select_null__'\n\nfunction toKey(value: string | number | null): string {\n if (value === null) return NULL_KEY\n return String(value)\n}\n\nfunction fromKey<T extends SelectOption>(key: React.Key | null, options: T[]): T['value'] | null {\n if (key === null || key === undefined) return null\n const keyStr = String(key)\n if (keyStr === NULL_KEY) return null\n const option = options.find((o) => toKey(o.value) === keyStr)\n return option?.value ?? null\n}\n"},{"name":"components/helpers/animated-popover.tsx","content":"import { Popover, type PopoverProps } from 'react-aria-components'\nimport { tw } from '../../utils/tw'\n\nconst animationClasses = tw(\n 'transition-all duration-150 ease-out',\n // resting state: keep transform properties defined so transitions have endpoints\n 'translate-x-0 translate-y-0',\n // entering: fade + slide from trigger direction\n 'data-[entering]:opacity-0',\n 'data-[entering]:data-[placement=bottom]:-translate-y-1',\n 'data-[entering]:data-[placement=top]:translate-y-1',\n 'data-[entering]:data-[placement=left]:translate-x-1',\n 'data-[entering]:data-[placement=right]:-translate-x-1',\n // exiting: fade + slide toward trigger direction\n 'data-[exiting]:opacity-0',\n 'data-[exiting]:data-[placement=bottom]:-translate-y-1',\n 'data-[exiting]:data-[placement=top]:translate-y-1',\n 'data-[exiting]:data-[placement=left]:translate-x-1',\n 'data-[exiting]:data-[placement=right]:-translate-x-1'\n)\n\nexport function AnimatedPopover({ className, ...props }: PopoverProps) {\n return <Popover {...props} className={tw(animationClasses, className)} />\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/colors.ts","content":"export const COLORS = [\n '#f43f5e',\n '#3b82f6',\n '#22c55e',\n '#EAB308',\n '#8b5cf6',\n '#191970',\n '#f97316',\n '#6b7280',\n '#ec4899',\n '#06b6d4',\n '#84cc16',\n '#64748b',\n '#ffff00',\n '#6366f1',\n '#ff6347',\n '#737373',\n '#14b8a6',\n '#f59e0b',\n '#0ea5e9',\n '#ef4444',\n '#78716c',\n '#00ffff',\n '#d946ef',\n '#10b981',\n '#b22222',\n '#40e0d0',\n '#71717a',\n '#a855f7',\n '#7fff00'\n] as const\n\nexport function stringToNumber(str: string) {\n let h = 0x811c9dc5 // FNV offset basis\n for (const b of new TextEncoder().encode(str)) {\n h ^= b\n h = Math.imul(h, 0x01000193) // 16777619\n }\n return h >>> 0 // unsigned 32-bit\n}\n\nexport function getColor(seed: number) {\n return COLORS[seed % COLORS.length]\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"autocomplete","files":[{"name":"components/autocomplete.tsx","content":"import * as React from 'react'\nimport { ComboBox as AriaComboBox, ListBoxItem } from 'react-aria-components'\nimport { AnimatedPopover } from './helpers/animated-popover'\nimport type { Key } from 'react-aria-components'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { getColor, stringToNumber } from '../utils/colors'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { matchSorter } from 'match-sorter'\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport type AutocompleteOption = {\n value: string | number\n label: string\n secondary?: string\n}\n\nexport type AutocompleteProps<T extends AutocompleteOption = AutocompleteOption> = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<T['value'] | null>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management\n value?: T['value'] | null\n defaultValue?: T['value'] | null\n onChange?: (value: T['value'] | null) => void\n\n // Options\n options: T[]\n renderOption?: (props: { option: T; colored: boolean; index: number }) => React.ReactNode\n\n colored?: boolean\n\n // Filtering\n filterFunction?: ((items: readonly T[], value: string) => T[]) | (keyof T)[]\n\n // Loading\n isLoading?: boolean\n\n // Input\n inputAutocomplete?: React.ComponentProps<'input'>['autoComplete']\n id?: string\n shape?: 'rectangle' | 'oval'\n}\n\n// ── Component ──────────────────────────────────────────────────────────────────\n\nexport function Autocomplete<T extends AutocompleteOption = AutocompleteOption>(props: AutocompleteProps<T>) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const inputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const labelId = React.useId()\n\n // Controlled state for the selected value\n const [selectedValue, setSelectedValue] = HeadlessForm.useControlledState(\n props.value,\n props.defaultValue ?? null,\n props.onChange\n )\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: selectedValue,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n inputRef.current?.focus()\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n // Find the selected option\n const selectedOption = React.useMemo(() => {\n return props.options.find((option) => option.value === selectedValue) ?? null\n }, [props.options, selectedValue])\n\n // Input value state (what's displayed in the input)\n const [inputValue, setInputValue] = React.useState(() => {\n const initial = props.value ?? props.defaultValue ?? null\n if (initial != null) {\n const option = props.options.find((o) => o.value === initial)\n return option?.label ?? ''\n }\n return ''\n })\n\n // Sync inputValue when external props.value changes\n const lastPropsValue = React.useRef(props.value)\n React.useEffect(() => {\n if (props.value !== lastPropsValue.current) {\n lastPropsValue.current = props.value\n if (props.value != null) {\n const option = props.options.find((o) => o.value === props.value)\n setInputValue(option?.label ?? '')\n } else {\n setInputValue('')\n }\n }\n }, [props.value, props.options])\n\n // Filter options based on input value\n const filteredOptions = React.useMemo(() => {\n if (props.isLoading) return []\n\n // When input matches the selected option's label, show all options (not filtered)\n const isShowingSelectedLabel = selectedOption && inputValue === selectedOption.label\n const searchTerm = isShowingSelectedLabel ? '' : inputValue\n\n const filterFn = props.filterFunction\n let filtered: T[]\n\n if (typeof filterFn === 'function') {\n filtered = filterFn(props.options, searchTerm).slice(0, 50)\n } else if (Array.isArray(filterFn)) {\n filtered = matchSorter(props.options, searchTerm, {\n keys: filterFn as string[]\n }).slice(0, 50)\n } else {\n filtered = defaultFilterFunction(props.options, searchTerm).slice(0, 50)\n }\n\n // Ensure selected option is always included\n if (selectedOption && !filtered.some((o) => o.value === selectedOption.value)) {\n filtered = [selectedOption, ...filtered]\n }\n\n return filtered\n }, [props.options, inputValue, props.filterFunction, props.isLoading, selectedOption])\n\n // Items for React Aria (with id for key management)\n const items = React.useMemo(\n () => filteredOptions.map((option, index) => ({ ...option, index, id: String(option.value) })),\n [filteredOptions]\n )\n\n // Selected key for React Aria\n const selectedKey = selectedValue != null ? String(selectedValue) : null\n\n const handleSelectionChange = (key: Key | null) => {\n if (key === null) {\n setSelectedValue(null)\n setInputValue('')\n } else {\n const option = props.options.find((o) => String(o.value) === String(key))\n setSelectedValue(option?.value ?? null)\n setInputValue(option?.label ?? '')\n }\n commitValidation()\n }\n\n const handleInputChange = (value: string) => {\n setInputValue(value)\n // Clear selection when input doesn't match any option label\n const matchingOption = props.options.find((o) => o.label === value)\n if (!matchingOption) {\n setSelectedValue(null)\n }\n }\n\n return (\n <div className=\"relative\">\n {/* Hidden input for form submission */}\n <HeadlessForm.HiddenInput ref={hiddenInputRef} name={props.name} value={selectedValue ?? ''} form={props.form} />\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} labelId={labelId} />\n\n <AriaComboBox\n aria-labelledby={props.label ? labelId : undefined}\n aria-label={!props.label ? 'Autocomplete' : undefined}\n selectedKey={selectedKey}\n onSelectionChange={handleSelectionChange}\n inputValue={inputValue}\n onInputChange={handleInputChange}\n items={items}\n isDisabled={props.isDisabled || props.isReadonly}\n isInvalid={isInvalid}\n isRequired={props.isRequired}\n allowsEmptyCollection\n menuTrigger=\"focus\"\n onBlur={() => commitValidation()}\n >\n {/* Input and button wrapper */}\n <FormFieldComponents.FieldGroup\n isDisabled={props.isDisabled}\n isReadonly={props.isReadonly}\n isInvalid={isInvalid}\n hasWarning={hasWarning}\n shape={props.shape}\n className=\"flex w-full items-center cursor-default\"\n >\n {props.colored && selectedOption ? (\n <div\n className=\"absolute left-0 top-0 w-[3px] h-full\"\n style={{ backgroundColor: getColor(stringToNumber(String(selectedOption.value))) }}\n />\n ) : null}\n <FormFieldComponents.FieldInput\n ref={inputRef}\n autoComplete={props.inputAutocomplete ?? 'off'}\n className=\"pr-1\"\n placeholder={props.placeholder ?? undefined}\n aria-describedby={helpTextId}\n id={props.id}\n />\n <FormFieldComponents.ChevronButton />\n </FormFieldComponents.FieldGroup>\n\n {/* Dropdown */}\n <AnimatedPopover offset={4} className=\"w-(--trigger-width)\">\n <FormFieldComponents.DropdownListBox\n renderEmptyState={() => (\n <div className=\"px-3 py-2 text-neutral-500 text-sm\">\n {props.isLoading ? 'Loading...' : inputValue ? 'No results found' : 'No options available'}\n </div>\n )}\n >\n {(item) => {\n const option = item as T & { index: number; id: string }\n if (props.renderOption) {\n return (\n <ListBoxItem id={option.id} textValue={option.label} className=\"outline-none\">\n {props.renderOption({ option, colored: props.colored ?? false, index: option.index })}\n </ListBoxItem>\n )\n }\n\n const color = props.colored ? getColor(stringToNumber(String(option.value))) : null\n return (\n <FormFieldComponents.DropdownItem\n id={option.id}\n textValue={option.label}\n label={option.label}\n secondary={option.secondary}\n color={color}\n />\n )\n }}\n </FormFieldComponents.DropdownListBox>\n </AnimatedPopover>\n </AriaComboBox>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────────────\n\nfunction defaultFilterFunction<T extends AutocompleteOption>(options: readonly T[], inputValue: string): T[] {\n if (!inputValue) return [...options]\n return matchSorter(options, inputValue, {\n keys: ['label', 'secondary']\n })\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/animated-popover.tsx","content":"import { Popover, type PopoverProps } from 'react-aria-components'\nimport { tw } from '../../utils/tw'\n\nconst animationClasses = tw(\n 'transition-all duration-150 ease-out',\n // resting state: keep transform properties defined so transitions have endpoints\n 'translate-x-0 translate-y-0',\n // entering: fade + slide from trigger direction\n 'data-[entering]:opacity-0',\n 'data-[entering]:data-[placement=bottom]:-translate-y-1',\n 'data-[entering]:data-[placement=top]:translate-y-1',\n 'data-[entering]:data-[placement=left]:translate-x-1',\n 'data-[entering]:data-[placement=right]:-translate-x-1',\n // exiting: fade + slide toward trigger direction\n 'data-[exiting]:opacity-0',\n 'data-[exiting]:data-[placement=bottom]:-translate-y-1',\n 'data-[exiting]:data-[placement=top]:translate-y-1',\n 'data-[exiting]:data-[placement=left]:translate-x-1',\n 'data-[exiting]:data-[placement=right]:-translate-x-1'\n)\n\nexport function AnimatedPopover({ className, ...props }: PopoverProps) {\n return <Popover {...props} className={tw(animationClasses, className)} />\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/colors.ts","content":"export const COLORS = [\n '#f43f5e',\n '#3b82f6',\n '#22c55e',\n '#EAB308',\n '#8b5cf6',\n '#191970',\n '#f97316',\n '#6b7280',\n '#ec4899',\n '#06b6d4',\n '#84cc16',\n '#64748b',\n '#ffff00',\n '#6366f1',\n '#ff6347',\n '#737373',\n '#14b8a6',\n '#f59e0b',\n '#0ea5e9',\n '#ef4444',\n '#78716c',\n '#00ffff',\n '#d946ef',\n '#10b981',\n '#b22222',\n '#40e0d0',\n '#71717a',\n '#a855f7',\n '#7fff00'\n] as const\n\nexport function stringToNumber(str: string) {\n let h = 0x811c9dc5 // FNV offset basis\n for (const b of new TextEncoder().encode(str)) {\n h ^= b\n h = Math.imul(h, 0x01000193) // 16777619\n }\n return h >>> 0 // unsigned 32-bit\n}\n\nexport function getColor(seed: number) {\n return COLORS[seed % COLORS.length]\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"autosuggest","files":[{"name":"components/autosuggest.tsx","content":"import * as React from 'react'\nimport { ComboBox as AriaComboBox, ListBoxItem } from 'react-aria-components'\nimport { AnimatedPopover } from './helpers/animated-popover'\nimport type { Key } from 'react-aria-components'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { matchSorter } from 'match-sorter'\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport type AutosuggestOption = {\n value: string\n label: string\n secondary?: string\n}\n\nexport type AutosuggestProps<T extends AutosuggestOption = AutosuggestOption> = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<string | null>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management\n value?: string | null\n defaultValue?: string | null\n onChange?: (value: string | null) => void\n onCustomValue?: (value: string) => void\n\n // Options\n options: T[]\n renderOption?: (props: { option: T; isCustom: boolean; index: number }) => React.ReactNode\n\n // Filtering\n filterFunction?: ((items: readonly T[], value: string) => T[]) | (keyof T)[]\n\n // Loading\n isLoading?: boolean\n\n // Input\n inputAutocomplete?: React.ComponentProps<'input'>['autoComplete']\n id?: string\n\n // Shape\n shape?: 'rectangle' | 'oval'\n}\n\n// ── Component ──────────────────────────────────────────────────────────────────\n\nconst CUSTOM_VALUE_KEY = '__autosuggest_custom__'\n\nexport function Autosuggest<T extends AutosuggestOption = AutosuggestOption>(props: AutosuggestProps<T>) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const inputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const labelId = React.useId()\n\n // Controlled state for the selected value\n const [selectedValue, setSelectedValue] = HeadlessForm.useControlledState(\n props.value,\n props.defaultValue ?? null,\n props.onChange\n )\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: selectedValue,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n inputRef.current?.focus()\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n // Find if selected value matches an option\n const selectedOption = React.useMemo(() => {\n return props.options.find((option) => option.value === selectedValue) ?? null\n }, [props.options, selectedValue])\n\n // Input value state (what's displayed in the input)\n const [inputValue, setInputValue] = React.useState(() => {\n const initial = props.value ?? props.defaultValue ?? null\n if (initial != null) {\n const option = props.options.find((o) => o.value === initial)\n return option?.label ?? initial\n }\n return ''\n })\n\n // Sync inputValue when external props.value changes\n const lastPropsValue = React.useRef(props.value)\n React.useEffect(() => {\n if (props.value !== lastPropsValue.current) {\n lastPropsValue.current = props.value\n if (props.value != null) {\n const option = props.options.find((o) => o.value === props.value)\n setInputValue(option?.label ?? props.value)\n } else {\n setInputValue('')\n }\n }\n }, [props.value, props.options])\n\n // Filter options based on input value\n const filteredOptions = React.useMemo(() => {\n if (props.isLoading) return []\n\n // When input matches the selected option's label, show all options (not filtered)\n const isShowingSelectedLabel = selectedOption && inputValue === selectedOption.label\n const searchTerm = isShowingSelectedLabel ? '' : inputValue\n\n const filterFn = props.filterFunction\n let filtered: T[]\n\n if (typeof filterFn === 'function') {\n filtered = filterFn(props.options, searchTerm).slice(0, 50)\n } else if (Array.isArray(filterFn)) {\n filtered = matchSorter(props.options, searchTerm, {\n keys: filterFn as string[]\n }).slice(0, 50)\n } else {\n filtered = defaultFilterFunction(props.options, searchTerm).slice(0, 50)\n }\n\n return filtered\n }, [props.options, inputValue, props.filterFunction, props.isLoading, selectedOption])\n\n // Check if we should show a custom value option\n const showCustomOption = React.useMemo(() => {\n if (!inputValue || props.isLoading) return false\n return !filteredOptions.some((option) => option.label === inputValue || option.value === inputValue)\n }, [inputValue, filteredOptions, props.isLoading])\n\n // Items for React Aria (with id for key management), including custom option\n const items = React.useMemo(() => {\n const mapped = filteredOptions.map((option, index) => ({\n ...option,\n index,\n id: option.value,\n isCustom: false\n }))\n\n if (showCustomOption) {\n return [\n { value: inputValue, label: inputValue, index: -1, id: CUSTOM_VALUE_KEY, isCustom: true } as T & {\n index: number\n id: string\n isCustom: boolean\n },\n ...mapped\n ]\n }\n\n return mapped\n }, [filteredOptions, showCustomOption, inputValue])\n\n // Selected key for React Aria\n const selectedKey = selectedValue != null ? (selectedOption ? selectedOption.value : CUSTOM_VALUE_KEY) : null\n\n const handleSelectionChange = (key: Key | null) => {\n if (key === null) {\n // Don't clear - keep the custom value in input\n return\n }\n\n if (key === CUSTOM_VALUE_KEY) {\n // Custom value selected\n setSelectedValue(inputValue)\n props.onCustomValue?.(inputValue)\n } else {\n const option = props.options.find((o) => o.value === String(key))\n setSelectedValue(option?.value ?? null)\n setInputValue(option?.label ?? '')\n }\n commitValidation()\n }\n\n const handleInputChange = (value: string) => {\n setInputValue(value)\n // Update selected value to the input value (allows custom values)\n setSelectedValue(value || null)\n }\n\n const handleBlur = () => {\n // Keep the custom value if entered\n if (inputValue && !selectedOption) {\n setSelectedValue(inputValue)\n props.onCustomValue?.(inputValue)\n }\n commitValidation()\n }\n\n return (\n <div className=\"relative\">\n {/* Hidden input for form submission */}\n <HeadlessForm.HiddenInput ref={hiddenInputRef} name={props.name} value={selectedValue ?? ''} form={props.form} />\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} labelId={labelId} />\n\n <AriaComboBox\n aria-labelledby={props.label ? labelId : undefined}\n aria-label={!props.label ? 'Autosuggest' : undefined}\n selectedKey={selectedKey}\n onSelectionChange={handleSelectionChange}\n inputValue={inputValue}\n onInputChange={handleInputChange}\n items={items}\n isDisabled={props.isDisabled || props.isReadonly}\n isInvalid={isInvalid}\n isRequired={props.isRequired}\n allowsCustomValue\n allowsEmptyCollection\n menuTrigger=\"focus\"\n onBlur={handleBlur}\n >\n {/* Input and button wrapper */}\n <FormFieldComponents.FieldGroup\n isDisabled={props.isDisabled}\n isReadonly={props.isReadonly}\n isInvalid={isInvalid}\n hasWarning={hasWarning}\n shape={props.shape}\n className=\"flex w-full items-center cursor-default\"\n >\n <FormFieldComponents.FieldInput\n ref={inputRef}\n autoComplete={props.inputAutocomplete ?? 'off'}\n className=\"pr-1\"\n placeholder={props.placeholder ?? undefined}\n aria-describedby={helpTextId}\n id={props.id}\n />\n <FormFieldComponents.ChevronButton />\n </FormFieldComponents.FieldGroup>\n\n {/* Dropdown */}\n <AnimatedPopover offset={4} className=\"w-(--trigger-width)\">\n <FormFieldComponents.DropdownListBox\n renderEmptyState={() => (\n <div className=\"px-3 py-2 text-neutral-500 text-sm\">\n {props.isLoading ? 'Loading...' : inputValue ? 'No matching suggestions' : 'No options available'}\n </div>\n )}\n >\n {(item) => {\n const option = item as T & { index: number; id: string; isCustom: boolean }\n if (props.renderOption) {\n return (\n <ListBoxItem id={option.id} textValue={option.label} className=\"outline-none\">\n {props.renderOption({ option, isCustom: option.isCustom, index: option.index })}\n </ListBoxItem>\n )\n }\n\n return (\n <FormFieldComponents.DropdownItem\n id={option.id}\n textValue={option.label}\n label={option.label}\n secondary={option.secondary}\n customValuePrefix={option.isCustom ? 'Use' : undefined}\n />\n )\n }}\n </FormFieldComponents.DropdownListBox>\n </AnimatedPopover>\n </AriaComboBox>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction defaultFilterFunction<T extends AutosuggestOption>(options: readonly T[], inputValue: string): T[] {\n if (!inputValue) return [...options]\n return matchSorter(options, inputValue, {\n keys: ['label', 'secondary']\n })\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/animated-popover.tsx","content":"import { Popover, type PopoverProps } from 'react-aria-components'\nimport { tw } from '../../utils/tw'\n\nconst animationClasses = tw(\n 'transition-all duration-150 ease-out',\n // resting state: keep transform properties defined so transitions have endpoints\n 'translate-x-0 translate-y-0',\n // entering: fade + slide from trigger direction\n 'data-[entering]:opacity-0',\n 'data-[entering]:data-[placement=bottom]:-translate-y-1',\n 'data-[entering]:data-[placement=top]:translate-y-1',\n 'data-[entering]:data-[placement=left]:translate-x-1',\n 'data-[entering]:data-[placement=right]:-translate-x-1',\n // exiting: fade + slide toward trigger direction\n 'data-[exiting]:opacity-0',\n 'data-[exiting]:data-[placement=bottom]:-translate-y-1',\n 'data-[exiting]:data-[placement=top]:translate-y-1',\n 'data-[exiting]:data-[placement=left]:translate-x-1',\n 'data-[exiting]:data-[placement=right]:-translate-x-1'\n)\n\nexport function AnimatedPopover({ className, ...props }: PopoverProps) {\n return <Popover {...props} className={tw(animationClasses, className)} />\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"multiselect","files":[{"name":"components/multiselect.tsx","content":"import * as React from 'react'\nimport { ListBox, ListBoxItem } from 'react-aria-components'\nimport { AnimatedPopover } from './helpers/animated-popover'\nimport type { Selection } from 'react-aria-components'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { getColor, stringToNumber } from '../utils/colors'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { useElementSize } from '../utils/use-element-size'\nimport { composeRefs } from '../utils/compose-refs'\nimport { Icon } from './icon'\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport type MultiselectOption = {\n value: string | number\n label: string\n secondary?: string\n isDisabled?: boolean\n}\n\nexport type MultiselectProps<T extends MultiselectOption = MultiselectOption> = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<T['value'][]>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management\n value?: T['value'][]\n defaultValue?: T['value'][]\n onChange?: (value: T['value'][]) => void\n\n // Options\n options: T[]\n showSelectAll?: boolean\n renderOption?: (props: { option: T; selected: boolean; colored: boolean; index: number }) => React.ReactNode\n\n colored?: boolean\n shape?: 'rectangle' | 'oval'\n}\n\n// ── Component ──────────────────────────────────────────────────────────────────\n\nexport function Multiselect<T extends MultiselectOption = MultiselectOption>(props: MultiselectProps<T>) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const buttonRef = React.useRef<HTMLButtonElement>(null)\n const [containerRef, setContainerRef] = React.useState<HTMLButtonElement | null>(null)\n const helpTextId = React.useId()\n const labelId = React.useId()\n const [isOpen, setIsOpen] = React.useState(false)\n\n // Controlled state management\n const [selectedValues, setSelectedValues] = HeadlessForm.useControlledState(\n props.value,\n props.defaultValue ?? [],\n props.onChange\n )\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: selectedValues,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n buttonRef.current?.focus()\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n // Find selected options\n const selectedOptions = React.useMemo(() => {\n return props.options.filter((option) => selectedValues.includes(option.value))\n }, [props.options, selectedValues])\n\n const containerSize = useElementSize(containerRef)\n\n const handleRemoveOption = (option: T) => {\n const newValues = selectedValues.filter((v) => v !== option.value)\n setSelectedValues(newValues)\n commitValidation()\n }\n\n const handleSelectAll = () => {\n const availableOptions = props.options.filter((o) => !o.isDisabled)\n if (selectedValues.length === availableOptions.length) {\n setSelectedValues([])\n } else {\n setSelectedValues(availableOptions.map((o) => o.value))\n }\n commitValidation()\n }\n\n // Convert selectedValues to a Set<string> for React Aria\n const selectedKeys = React.useMemo(() => {\n return new Set(selectedValues.map((v) => String(v)))\n }, [selectedValues])\n\n const handleSelectionChange = (keys: Selection) => {\n if (keys === 'all') {\n const availableOptions = props.options.filter((o) => !o.isDisabled)\n setSelectedValues(availableOptions.map((o) => o.value))\n } else {\n const keySet = keys as Set<string>\n const newValues = props.options.filter((o) => keySet.has(String(o.value))).map((o) => o.value)\n setSelectedValues(newValues)\n }\n commitValidation()\n }\n\n return (\n <div className=\"relative\">\n {/* Hidden inputs for form submission */}\n {selectedValues.map((value, i) => (\n <HeadlessForm.HiddenInput\n key={i}\n ref={i === 0 ? hiddenInputRef : undefined}\n name={props.name}\n value={value}\n form={props.form}\n />\n ))}\n {selectedValues.length === 0 && (\n <HeadlessForm.HiddenInput ref={hiddenInputRef} name={props.name} value=\"[]\" form={props.form} />\n )}\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} labelId={labelId} />\n\n {/* Trigger button */}\n <button\n ref={composeRefs(buttonRef, setContainerRef)}\n type=\"button\"\n role=\"combobox\"\n aria-expanded={isOpen}\n aria-haspopup=\"listbox\"\n aria-labelledby={props.label ? labelId : undefined}\n aria-label={!props.label ? 'Multiselect' : undefined}\n aria-invalid={isInvalid || undefined}\n aria-describedby={helpTextId}\n disabled={props.isDisabled}\n onClick={() => {\n if (!props.isDisabled) setIsOpen((prev) => !prev)\n }}\n onBlur={() => {\n if (!isOpen) {\n commitValidation()\n }\n }}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n props.shape === 'oval' ? 'rounded-full' : 'rounded',\n 'focus:outline-none focus:ring-1',\n props.isDisabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n isInvalid && 'border-negative-200 bg-negative-50 focus:border-negative-500 focus:ring-negative-500/20',\n hasWarning && 'border-notice-200 bg-notice-50 focus:border-notice-500 focus:ring-notice-500/20',\n !isInvalid &&\n !hasWarning &&\n !props.isDisabled &&\n 'border-neutral-200 hover:border-neutral-300 focus:border-primary-500 focus:ring-primary-500/20',\n 'w-full cursor-default bg-white py-2 pl-3 pr-10 text-left'\n )}\n >\n <div className=\"block text-sm min-h-5\">\n {selectedOptions.length === 0 ? (\n <span className=\"text-neutral-300 italic\">{props.placeholder ?? 'Select options'}</span>\n ) : (\n <div className=\"flex flex-wrap gap-1.5\">\n {selectedOptions.map((option) => {\n const chipColor = props.colored ? getColor(stringToNumber(String(option.value))) : undefined\n return (\n <span\n key={option.value}\n className={tw(\n 'inline-flex items-center rounded-full font-normal transition-colors max-w-full px-2 py-0.5 text-xs',\n !chipColor && 'bg-primary-500/10 text-primary-500',\n props.isDisabled && 'opacity-50'\n )}\n style={\n chipColor\n ? {\n backgroundColor: `color-mix(in srgb, ${chipColor} 15%, white)`,\n color: `color-mix(in srgb, ${chipColor} 50%, black)`\n }\n : undefined\n }\n >\n <span className=\"truncate\">{option.label}</span>\n {!props.isDisabled && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n handleRemoveOption(option)\n }}\n className=\"inline-flex items-center justify-center rounded-full transition-colors translate-x-1 h-4 w-4 -m-0.5 p-0.5 hover:bg-black/10\"\n aria-label={`Remove ${option.label}`}\n tabIndex={-1}\n >\n <Icon name=\"x-mark\" className=\"h-2 w-2\" />\n </button>\n )}\n </span>\n )\n })}\n </div>\n )}\n </div>\n <span className=\"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2\">\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400\" />\n </span>\n </button>\n\n {/* Options dropdown */}\n <AnimatedPopover\n triggerRef={buttonRef}\n isNonModal\n isOpen={isOpen && !props.isDisabled}\n onOpenChange={(open) => {\n setIsOpen(open)\n if (!open) commitValidation()\n }}\n placement=\"bottom start\"\n offset={4}\n style={{ width: containerSize.width, minWidth: containerSize.width }}\n >\n <div\n className={tw(\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none'\n )}\n >\n {props.showSelectAll && (\n <button\n type=\"button\"\n onClick={handleSelectAll}\n className=\"w-full px-3 py-2 text-left hover:bg-neutral-50 border-b border-neutral-100 text-sm font-medium\"\n >\n {selectedValues.length === props.options.filter((o) => !o.isDisabled).length ? 'Deselect All' : 'Select All'}\n </button>\n )}\n <ListBox\n aria-label={props.label ? String(props.label) : 'Multiselect'}\n selectionMode=\"multiple\"\n selectedKeys={selectedKeys}\n onSelectionChange={handleSelectionChange}\n className=\"max-h-60 overflow-y-auto focus:outline-none\"\n items={props.options.map((option, index) => ({ ...option, index, id: String(option.value) }))}\n renderEmptyState={() => <div className=\"px-3 py-2 text-neutral-500 text-sm\">No options available</div>}\n >\n {(item) => {\n const option = item as T & { index: number; id: string }\n const color = props.colored ? getColor(stringToNumber(String(option.value))) : null\n\n if (props.renderOption) {\n return (\n <ListBoxItem\n id={option.id}\n textValue={option.label}\n isDisabled={option.isDisabled}\n className=\"outline-none\"\n >\n {({ isSelected }) => (\n <>\n {props.renderOption!({\n option,\n selected: isSelected,\n colored: props.colored ?? false,\n index: option.index\n })}\n </>\n )}\n </ListBoxItem>\n )\n }\n\n return (\n <FormFieldComponents.DropdownItem\n id={option.id}\n textValue={option.label}\n label={option.label}\n secondary={option.secondary}\n isDisabled={option.isDisabled}\n color={color}\n className=\"px-3\"\n />\n )\n }}\n </ListBox>\n </div>\n </AnimatedPopover>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/animated-popover.tsx","content":"import { Popover, type PopoverProps } from 'react-aria-components'\nimport { tw } from '../../utils/tw'\n\nconst animationClasses = tw(\n 'transition-all duration-150 ease-out',\n // resting state: keep transform properties defined so transitions have endpoints\n 'translate-x-0 translate-y-0',\n // entering: fade + slide from trigger direction\n 'data-[entering]:opacity-0',\n 'data-[entering]:data-[placement=bottom]:-translate-y-1',\n 'data-[entering]:data-[placement=top]:translate-y-1',\n 'data-[entering]:data-[placement=left]:translate-x-1',\n 'data-[entering]:data-[placement=right]:-translate-x-1',\n // exiting: fade + slide toward trigger direction\n 'data-[exiting]:opacity-0',\n 'data-[exiting]:data-[placement=bottom]:-translate-y-1',\n 'data-[exiting]:data-[placement=top]:translate-y-1',\n 'data-[exiting]:data-[placement=left]:translate-x-1',\n 'data-[exiting]:data-[placement=right]:-translate-x-1'\n)\n\nexport function AnimatedPopover({ className, ...props }: PopoverProps) {\n return <Popover {...props} className={tw(animationClasses, className)} />\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/colors.ts","content":"export const COLORS = [\n '#f43f5e',\n '#3b82f6',\n '#22c55e',\n '#EAB308',\n '#8b5cf6',\n '#191970',\n '#f97316',\n '#6b7280',\n '#ec4899',\n '#06b6d4',\n '#84cc16',\n '#64748b',\n '#ffff00',\n '#6366f1',\n '#ff6347',\n '#737373',\n '#14b8a6',\n '#f59e0b',\n '#0ea5e9',\n '#ef4444',\n '#78716c',\n '#00ffff',\n '#d946ef',\n '#10b981',\n '#b22222',\n '#40e0d0',\n '#71717a',\n '#a855f7',\n '#7fff00'\n] as const\n\nexport function stringToNumber(str: string) {\n let h = 0x811c9dc5 // FNV offset basis\n for (const b of new TextEncoder().encode(str)) {\n h ^= b\n h = Math.imul(h, 0x01000193) // 16777619\n }\n return h >>> 0 // unsigned 32-bit\n}\n\nexport function getColor(seed: number) {\n return COLORS[seed % COLORS.length]\n}\n"},{"name":"utils/compose-refs.ts","content":"import React from 'react'\n\ntype PossibleRef<T> = React.Ref<T> | undefined\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n if (typeof ref === 'function') {\n ref(value)\n } else if (ref !== null && ref !== undefined) {\n ;(ref as React.MutableRefObject<T>).current = value\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nexport function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n return (node: T) => refs.forEach((ref) => setRef(ref, node))\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nexport function useComposedRefs<T>(...refs: PossibleRef<T>[]) {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return React.useCallback(composeRefs(...refs), refs)\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"},{"name":"utils/use-element-size.ts","content":"import { useMemo, useEffect, useReducer } from 'react'\n\nfunction computeSize(element: HTMLElement | null) {\n if (element === null) return { width: 0, height: 0 }\n const { width, height } = element.getBoundingClientRect()\n return { width, height }\n}\n\nexport function useElementSize(element: HTMLElement | null, unit?: false): { width: number; height: number }\nexport function useElementSize(element: HTMLElement | null, unit: true): { width: string; height: string }\nexport function useElementSize(element: HTMLElement | null, unit = false) {\n const [identity, forceRerender] = useReducer(() => ({}), {})\n\n const size = useMemo(() => computeSize(element), [element, identity])\n\n useEffect(() => {\n if (!element) return\n\n const observer = new ResizeObserver(forceRerender)\n observer.observe(element)\n\n return () => {\n observer.disconnect()\n }\n }, [element])\n\n if (unit) {\n return {\n width: `${size.width}px`,\n height: `${size.height}px`\n }\n }\n\n return size\n}\n"}]},{"name":"multicomplete","files":[{"name":"components/multicomplete.tsx","content":"import * as React from 'react'\nimport { ComboBox as AriaComboBox, Input, ListBox, ListBoxItem } from 'react-aria-components'\nimport { AnimatedPopover } from './helpers/animated-popover'\nimport type { Key } from 'react-aria-components'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { getColor, stringToNumber } from '../utils/colors'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { matchSorter } from 'match-sorter'\nimport { Icon } from './icon'\nimport { useElementSize } from '../utils/use-element-size'\nimport { composeRefs } from '../utils/compose-refs'\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport type MulticompleteOption = {\n value: string | number\n label: string\n secondary?: string\n}\n\nexport type MulticompleteProps<T extends MulticompleteOption = MulticompleteOption> = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<T['value'][]>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management\n value?: T['value'][]\n defaultValue?: T['value'][]\n onChange?: (value: T['value'][]) => void\n\n // Options\n options: T[]\n renderOption?: (props: { option: T; colored: boolean; index: number }) => React.ReactNode\n\n colored?: boolean\n\n // Filtering\n filterFunction?: ((items: readonly T[], value: string) => T[]) | (keyof T)[]\n\n // Loading\n isLoading?: boolean\n\n // Input\n inputAutocomplete?: React.ComponentProps<'input'>['autoComplete']\n id?: string\n shape?: 'rectangle' | 'oval'\n}\n\n// ── Component ──────────────────────────────────────────────────────────────────\n\nconst EMPTY_ARR: never[] = []\n\nexport function Multicomplete<T extends MulticompleteOption = MulticompleteOption>(props: MulticompleteProps<T>) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const inputRef = React.useRef<HTMLInputElement>(null)\n const fieldGroupRef = React.useRef<HTMLDivElement>(null)\n const [fieldGroupEl, setFieldGroupEl] = React.useState<HTMLDivElement | null>(null)\n const fieldGroupSize = useElementSize(fieldGroupEl)\n const helpTextId = React.useId()\n const labelId = React.useId()\n const [query, setQuery] = React.useState('')\n\n // Controlled state management (array of values)\n const [selectedValues, setSelectedValues] = HeadlessForm.useControlledState(\n props.value,\n props.defaultValue ?? EMPTY_ARR,\n props.onChange\n )\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: selectedValues,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n inputRef.current?.focus()\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n const isInteractive = !props.isDisabled && !props.isReadonly\n\n // Find selected options\n const selectedOptions = React.useMemo(() => {\n return props.options.filter((option) => selectedValues.includes(option.value))\n }, [props.options, selectedValues])\n\n // Filter unselected options based on query\n const availableOptions = React.useMemo(() => {\n if (props.isLoading) return []\n\n const unselected = props.options.filter((option) => !selectedValues.includes(option.value))\n const filterFn = props.filterFunction\n\n if (typeof filterFn === 'function') {\n return filterFn(unselected, query).slice(0, 50)\n }\n\n if (Array.isArray(filterFn)) {\n return matchSorter(unselected, query, {\n keys: filterFn as string[]\n }).slice(0, 50)\n }\n\n return defaultFilterFunction(unselected, query).slice(0, 50)\n }, [props.options, selectedValues, query, props.filterFunction, props.isLoading])\n\n // Items for React Aria (with id for key management)\n const items = React.useMemo(\n () => availableOptions.map((option, index) => ({ ...option, index, id: String(option.value) })),\n [availableOptions]\n )\n\n const handleRemoveOption = (value: T['value']) => {\n const newValues = selectedValues.filter((v) => v !== value)\n setSelectedValues(newValues)\n commitValidation()\n }\n\n const handleSelectionChange = (key: Key | null) => {\n if (key === null) return\n const option = props.options.find((o) => String(o.value) === String(key))\n if (option && !selectedValues.includes(option.value)) {\n setSelectedValues([...selectedValues, option.value])\n commitValidation()\n }\n setQuery('')\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Backspace' && !query && selectedOptions.length > 0) {\n e.preventDefault()\n handleRemoveOption(selectedOptions[selectedOptions.length - 1]!.value)\n }\n }\n\n return (\n <div className=\"relative\">\n {/* Hidden inputs for form submission */}\n <HeadlessForm.HiddenInput\n ref={hiddenInputRef}\n name={props.name}\n value={JSON.stringify(selectedValues)}\n form={props.form}\n />\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} labelId={labelId} />\n\n <AriaComboBox\n aria-labelledby={props.label ? labelId : undefined}\n aria-label={!props.label ? 'Multicomplete' : undefined}\n selectedKey={null}\n onSelectionChange={handleSelectionChange}\n inputValue={query}\n onInputChange={setQuery}\n items={items}\n isDisabled={props.isDisabled || props.isReadonly}\n isInvalid={isInvalid}\n allowsEmptyCollection\n menuTrigger=\"focus\"\n onBlur={() => commitValidation()}\n onKeyDown={handleKeyDown}\n >\n {/* Input container with chips */}\n <FormFieldComponents.FieldGroup\n ref={composeRefs(fieldGroupRef, setFieldGroupEl)}\n isDisabled={props.isDisabled}\n isReadonly={props.isReadonly}\n isInvalid={isInvalid}\n hasWarning={hasWarning}\n shape={props.shape}\n className=\"flex\"\n >\n <div className=\"flex flex-wrap items-center gap-1.5 px-3 py-1.5 w-full\">\n {/* Display selected chips */}\n {selectedOptions.map((option) => {\n const chipColor = props.colored ? getColor(stringToNumber(String(option.value))) : undefined\n return (\n <span\n key={option.value}\n className={tw(\n 'inline-flex items-center rounded-full font-normal transition-colors max-w-full px-2 py-0.5 text-xs',\n !chipColor && 'bg-primary-500/10 text-primary-500',\n props.isDisabled && 'opacity-50'\n )}\n style={\n chipColor\n ? {\n backgroundColor: `color-mix(in srgb, ${chipColor} 15%, white)`,\n color: `color-mix(in srgb, ${chipColor} 50%, black)`\n }\n : undefined\n }\n >\n <span className=\"truncate\">{option.label}</span>\n {isInteractive && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n handleRemoveOption(option.value)\n }}\n className=\"inline-flex items-center justify-center rounded-full transition-colors translate-x-1 h-4 w-4 -m-0.5 p-0.5 hover:bg-black/10\"\n aria-label={`Remove ${option.label}`}\n tabIndex={-1}\n >\n <Icon name=\"x-mark\" className=\"h-2 w-2\" />\n </button>\n )}\n </span>\n )\n })}\n\n {/* Search input */}\n <Input\n ref={inputRef}\n autoComplete={props.inputAutocomplete ?? 'off'}\n className=\"flex-1 min-w-[120px] px-0 bg-transparent border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400 py-0.5 disabled:cursor-not-allowed\"\n placeholder={selectedOptions.length === 0 ? (props.placeholder ?? 'Search to add...') : ''}\n aria-describedby={helpTextId}\n id={props.id}\n />\n </div>\n <FormFieldComponents.ChevronButton className=\"py-0\" />\n </FormFieldComponents.FieldGroup>\n\n {/* Options dropdown */}\n <AnimatedPopover\n triggerRef={fieldGroupRef}\n offset={4}\n style={{ width: fieldGroupSize.width, minWidth: fieldGroupSize.width }}\n >\n <div\n className={tw(\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none'\n )}\n >\n <ListBox\n aria-label={props.label ? String(props.label) : 'Multicomplete'}\n className=\"max-h-60 overflow-y-auto focus:outline-none\"\n renderEmptyState={() => (\n <div className=\"px-3 py-2 text-neutral-500 text-sm\">\n {props.isLoading ? 'Loading...' : query ? 'No results found' : 'No options available'}\n </div>\n )}\n >\n {(item) => {\n const option = item as T & { index: number; id: string }\n const color = props.colored ? getColor(stringToNumber(String(option.value))) : null\n\n if (props.renderOption) {\n return (\n <ListBoxItem id={option.id} textValue={option.label} className=\"outline-none\">\n {props.renderOption({ option, colored: props.colored ?? false, index: option.index })}\n </ListBoxItem>\n )\n }\n\n return (\n <FormFieldComponents.DropdownItem\n id={option.id}\n textValue={option.label}\n label={option.label}\n secondary={option.secondary}\n color={color}\n showCheckIcon={false}\n className=\"px-3\"\n />\n )\n }}\n </ListBox>\n </div>\n </AnimatedPopover>\n </AriaComboBox>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────────────\n\nfunction defaultFilterFunction<T extends MulticompleteOption>(options: readonly T[], inputValue: string): T[] {\n if (!inputValue) return [...options]\n return matchSorter(options, inputValue, {\n keys: ['label', 'secondary']\n })\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/animated-popover.tsx","content":"import { Popover, type PopoverProps } from 'react-aria-components'\nimport { tw } from '../../utils/tw'\n\nconst animationClasses = tw(\n 'transition-all duration-150 ease-out',\n // resting state: keep transform properties defined so transitions have endpoints\n 'translate-x-0 translate-y-0',\n // entering: fade + slide from trigger direction\n 'data-[entering]:opacity-0',\n 'data-[entering]:data-[placement=bottom]:-translate-y-1',\n 'data-[entering]:data-[placement=top]:translate-y-1',\n 'data-[entering]:data-[placement=left]:translate-x-1',\n 'data-[entering]:data-[placement=right]:-translate-x-1',\n // exiting: fade + slide toward trigger direction\n 'data-[exiting]:opacity-0',\n 'data-[exiting]:data-[placement=bottom]:-translate-y-1',\n 'data-[exiting]:data-[placement=top]:translate-y-1',\n 'data-[exiting]:data-[placement=left]:translate-x-1',\n 'data-[exiting]:data-[placement=right]:-translate-x-1'\n)\n\nexport function AnimatedPopover({ className, ...props }: PopoverProps) {\n return <Popover {...props} className={tw(animationClasses, className)} />\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/colors.ts","content":"export const COLORS = [\n '#f43f5e',\n '#3b82f6',\n '#22c55e',\n '#EAB308',\n '#8b5cf6',\n '#191970',\n '#f97316',\n '#6b7280',\n '#ec4899',\n '#06b6d4',\n '#84cc16',\n '#64748b',\n '#ffff00',\n '#6366f1',\n '#ff6347',\n '#737373',\n '#14b8a6',\n '#f59e0b',\n '#0ea5e9',\n '#ef4444',\n '#78716c',\n '#00ffff',\n '#d946ef',\n '#10b981',\n '#b22222',\n '#40e0d0',\n '#71717a',\n '#a855f7',\n '#7fff00'\n] as const\n\nexport function stringToNumber(str: string) {\n let h = 0x811c9dc5 // FNV offset basis\n for (const b of new TextEncoder().encode(str)) {\n h ^= b\n h = Math.imul(h, 0x01000193) // 16777619\n }\n return h >>> 0 // unsigned 32-bit\n}\n\nexport function getColor(seed: number) {\n return COLORS[seed % COLORS.length]\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"multisuggest","files":[{"name":"components/multisuggest.tsx","content":"import * as React from 'react'\nimport { ComboBox as AriaComboBox, Input, ListBox, ListBoxItem } from 'react-aria-components'\nimport { AnimatedPopover } from './helpers/animated-popover'\nimport type { Key } from 'react-aria-components'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { matchSorter } from 'match-sorter'\nimport { Icon } from './icon'\nimport { useElementSize } from '../utils/use-element-size'\nimport { composeRefs } from '../utils/compose-refs'\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport type MultisuggestOption = {\n value: string\n label: string\n secondary?: string\n}\n\nexport type MultisuggestProps<T extends MultisuggestOption = MultisuggestOption> = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n placeholder?: string\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<string[]>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management\n value?: string[]\n defaultValue?: string[]\n onChange?: (value: string[]) => void\n onCustomValue?: (value: string) => void\n\n // Options\n options: T[]\n renderOption?: (props: { option: T; isCustom: boolean; index: number }) => React.ReactNode\n\n // Filtering\n filterFunction?: ((items: readonly T[], value: string) => T[]) | (keyof T)[]\n\n // Loading\n isLoading?: boolean\n\n // Input\n inputAutocomplete?: React.ComponentProps<'input'>['autoComplete']\n id?: string\n shape?: 'rectangle' | 'oval'\n}\n\n// ── Component ──────────────────────────────────────────────────────────────────\n\nconst CUSTOM_VALUE_KEY = '__multisuggest_custom__'\nconst EMPTY_ARR: never[] = []\n\nexport function Multisuggest<T extends MultisuggestOption = MultisuggestOption>(props: MultisuggestProps<T>) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const inputRef = React.useRef<HTMLInputElement>(null)\n const fieldGroupRef = React.useRef<HTMLDivElement>(null)\n const [fieldGroupEl, setFieldGroupEl] = React.useState<HTMLDivElement | null>(null)\n const fieldGroupSize = useElementSize(fieldGroupEl)\n const helpTextId = React.useId()\n const labelId = React.useId()\n const [query, setQuery] = React.useState('')\n const queryRef = React.useRef(query)\n queryRef.current = query\n\n // Controlled state management (array of values)\n const [selectedValues, setSelectedValues] = HeadlessForm.useControlledState(\n props.value,\n props.defaultValue ?? EMPTY_ARR,\n props.onChange\n )\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: selectedValues,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n inputRef.current?.focus()\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n const isInteractive = !props.isDisabled && !props.isReadonly\n\n // Find selected options (both from options list and custom values)\n const selectedOptions = React.useMemo(() => {\n return selectedValues.map((val) => {\n const existingOption = props.options.find((opt) => opt.value === val)\n if (existingOption) return existingOption\n return { value: val, label: val } as T\n })\n }, [props.options, selectedValues])\n\n // Filter unselected options based on query\n const availableOptions = React.useMemo(() => {\n if (props.isLoading) return []\n\n const unselected = props.options.filter((option) => !selectedValues.includes(option.value))\n const filterFn = props.filterFunction\n\n if (typeof filterFn === 'function') {\n return filterFn(unselected, query).slice(0, 50)\n }\n\n if (Array.isArray(filterFn)) {\n return matchSorter(unselected, query, {\n keys: filterFn as string[]\n }).slice(0, 50)\n }\n\n return defaultFilterFunction(unselected, query).slice(0, 50)\n }, [props.options, selectedValues, query, props.filterFunction, props.isLoading])\n\n // Check if we should show a custom value option\n const showCustomOption = React.useMemo(() => {\n if (!query || props.isLoading) return false\n const queryNotInOptions = !availableOptions.some((option) => option.label === query || option.value === query)\n const queryNotSelected = !selectedValues.includes(query)\n return queryNotInOptions && queryNotSelected\n }, [query, availableOptions, selectedValues, props.isLoading])\n\n // Items for React Aria (with id for key management), including custom option\n const items = React.useMemo(() => {\n const mapped = availableOptions.map((option, index) => ({\n ...option,\n index,\n id: option.value,\n isCustom: false\n }))\n\n if (showCustomOption) {\n return [\n { value: query, label: query, index: -1, id: CUSTOM_VALUE_KEY, isCustom: true } as T & {\n index: number\n id: string\n isCustom: boolean\n },\n ...mapped\n ]\n }\n\n return mapped\n }, [availableOptions, showCustomOption, query])\n\n const handleRemoveOption = (value: string) => {\n const newValues = selectedValues.filter((v) => v !== value)\n setSelectedValues(newValues)\n commitValidation()\n }\n\n const addValue = (val: string, isCustom: boolean) => {\n if (selectedValues.includes(val)) return\n setSelectedValues([...selectedValues, val])\n if (isCustom) props.onCustomValue?.(val)\n setQuery('')\n commitValidation()\n }\n\n const handleSelectionChange = (key: Key | null) => {\n if (key === null) return\n const keyStr = String(key)\n\n if (keyStr === CUSTOM_VALUE_KEY) {\n addValue(queryRef.current, true)\n } else {\n const option = props.options.find((o) => o.value === keyStr)\n if (option && !selectedValues.includes(option.value)) {\n addValue(option.value, false)\n }\n }\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Backspace' && !query && selectedOptions.length > 0) {\n e.preventDefault()\n handleRemoveOption(selectedOptions[selectedOptions.length - 1]!.value)\n }\n }\n\n return (\n <div className=\"relative\">\n {/* Hidden inputs for form submission */}\n <HeadlessForm.HiddenInput\n ref={hiddenInputRef}\n name={props.name}\n value={JSON.stringify(selectedValues)}\n form={props.form}\n />\n\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} labelId={labelId} />\n\n <AriaComboBox\n aria-labelledby={props.label ? labelId : undefined}\n aria-label={!props.label ? 'Multisuggest' : undefined}\n selectedKey={null}\n onSelectionChange={handleSelectionChange}\n inputValue={query}\n onInputChange={setQuery}\n items={items}\n isDisabled={props.isDisabled || props.isReadonly}\n isInvalid={isInvalid}\n allowsEmptyCollection\n allowsCustomValue\n menuTrigger=\"focus\"\n onBlur={() => {\n if (queryRef.current && showCustomOption) {\n addValue(queryRef.current, true)\n }\n commitValidation()\n }}\n onKeyDown={handleKeyDown}\n >\n {/* Input container with chips */}\n <FormFieldComponents.FieldGroup\n ref={composeRefs(fieldGroupRef, setFieldGroupEl)}\n isDisabled={props.isDisabled}\n isReadonly={props.isReadonly}\n isInvalid={isInvalid}\n hasWarning={hasWarning}\n shape={props.shape}\n className=\"flex\"\n >\n <div className=\"flex flex-wrap items-center gap-1.5 px-3 py-1.5 w-full\">\n {/* Display selected chips */}\n {selectedOptions.map((option) => (\n <span\n key={option.value}\n className={tw(\n 'inline-flex items-center rounded-full font-normal transition-colors max-w-full px-2 py-0.5 text-xs',\n 'bg-primary-500/10 text-primary-500',\n props.isDisabled && 'opacity-50'\n )}\n >\n <span className=\"truncate\">{option.label}</span>\n {isInteractive && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n handleRemoveOption(option.value)\n }}\n className=\"inline-flex items-center justify-center rounded-full transition-colors translate-x-1 h-4 w-4 -m-0.5 p-0.5 hover:bg-black/10\"\n aria-label={`Remove ${option.label}`}\n tabIndex={-1}\n >\n <Icon name=\"x-mark\" className=\"h-2 w-2\" />\n </button>\n )}\n </span>\n ))}\n\n {/* Search input */}\n <Input\n ref={inputRef}\n autoComplete={props.inputAutocomplete ?? 'off'}\n className=\"flex-1 min-w-[120px] px-0 bg-transparent border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400 py-0.5 disabled:cursor-not-allowed\"\n placeholder={selectedOptions.length === 0 ? (props.placeholder ?? 'Search or type to add...') : ''}\n aria-describedby={helpTextId}\n id={props.id}\n />\n </div>\n <FormFieldComponents.ChevronButton className=\"py-0\" />\n </FormFieldComponents.FieldGroup>\n\n {/* Options dropdown */}\n <AnimatedPopover\n triggerRef={fieldGroupRef}\n offset={4}\n style={{ width: fieldGroupSize.width, minWidth: fieldGroupSize.width }}\n >\n <div\n className={tw(\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none'\n )}\n >\n <ListBox\n aria-label={props.label ? String(props.label) : 'Multisuggest'}\n className=\"max-h-60 overflow-y-auto focus:outline-none\"\n renderEmptyState={() => (\n <div className=\"px-3 py-2 text-neutral-500 text-sm\">\n {props.isLoading ? 'Loading...' : query ? 'No matching suggestions' : 'No options available'}\n </div>\n )}\n >\n {(item) => {\n const option = item as T & { index: number; id: string; isCustom: boolean }\n\n if (props.renderOption) {\n return (\n <ListBoxItem id={option.id} textValue={option.label} className=\"outline-none\">\n {props.renderOption({ option, isCustom: option.isCustom, index: option.index })}\n </ListBoxItem>\n )\n }\n\n return (\n <FormFieldComponents.DropdownItem\n id={option.id}\n textValue={option.label}\n label={option.label}\n secondary={option.secondary}\n showCheckIcon={false}\n customValuePrefix={option.isCustom ? 'Add' : undefined}\n />\n )\n }}\n </ListBox>\n </div>\n </AnimatedPopover>\n </AriaComboBox>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────────────\n\nfunction defaultFilterFunction<T extends MultisuggestOption>(options: readonly T[], inputValue: string): T[] {\n if (!inputValue) return [...options]\n return matchSorter(options, inputValue, {\n keys: ['label', 'secondary']\n })\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/animated-popover.tsx","content":"import { Popover, type PopoverProps } from 'react-aria-components'\nimport { tw } from '../../utils/tw'\n\nconst animationClasses = tw(\n 'transition-all duration-150 ease-out',\n // resting state: keep transform properties defined so transitions have endpoints\n 'translate-x-0 translate-y-0',\n // entering: fade + slide from trigger direction\n 'data-[entering]:opacity-0',\n 'data-[entering]:data-[placement=bottom]:-translate-y-1',\n 'data-[entering]:data-[placement=top]:translate-y-1',\n 'data-[entering]:data-[placement=left]:translate-x-1',\n 'data-[entering]:data-[placement=right]:-translate-x-1',\n // exiting: fade + slide toward trigger direction\n 'data-[exiting]:opacity-0',\n 'data-[exiting]:data-[placement=bottom]:-translate-y-1',\n 'data-[exiting]:data-[placement=top]:translate-y-1',\n 'data-[exiting]:data-[placement=left]:translate-x-1',\n 'data-[exiting]:data-[placement=right]:-translate-x-1'\n)\n\nexport function AnimatedPopover({ className, ...props }: PopoverProps) {\n return <Popover {...props} className={tw(animationClasses, className)} />\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"checkbox","files":[{"name":"components/checkbox.tsx","content":"import * as React from 'react'\nimport { Checkbox as AriaCheckbox } from 'react-aria-components'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { Icon } from './icon'\n\n// ── Props ─────────────────────────────────────────────────────────────────────\n\nexport type PlainCheckboxProps = {\n name?: string\n form?: string\n\n isDisabled?: boolean\n isRequired?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<boolean>\n\n checked?: boolean\n defaultChecked?: boolean\n onChange?: (checked: boolean) => void\n\n 'aria-label'?: string\n}\n\nexport type LabeledCheckboxProps = {\n name?: string\n form?: string\n\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n warningMessage?: React.ReactNode\n\n isRequired?: boolean\n isDisabled?: boolean\n isIndeterminate?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<boolean>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n checked?: boolean\n defaultChecked?: boolean\n onChange?: (checked: boolean) => void\n\n value?: string\n}\n\n// ── Plain Checkbox ─────────────────────────────────────────────────────────────\n\nexport function PlainCheckbox(props: PlainCheckboxProps) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n\n const [checked, setChecked] = HeadlessForm.useControlledState(props.checked, props.defaultChecked ?? false, props.onChange)\n\n const { isInvalid: validationInvalid, commitValidation } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: checked,\n name: props.name,\n isRequired: false,\n form: props.form,\n focus() {\n hiddenInputRef.current?.closest('label')?.focus()\n }\n },\n hiddenInputRef\n )\n\n return (\n <AriaCheckbox\n isSelected={checked}\n onChange={(v) => {\n setChecked(v)\n commitValidation()\n }}\n isDisabled={props.isDisabled}\n isInvalid={props.isInvalid || validationInvalid}\n aria-label={props['aria-label']}\n className=\"group inline-flex items-center focus:outline-none [&>div]:focus-visible:ring-2 [&>div]:focus-visible:ring-primary-500/20 [&>div]:focus-visible:ring-offset-2\"\n >\n {({ isSelected, isIndeterminate, isDisabled }) => (\n <>\n {props.isDisabled || !props.name ? null : (\n <HeadlessForm.HiddenInput\n ref={hiddenInputRef}\n name={props.name}\n form={props.form}\n value={checked ? 'true' : 'false'}\n />\n )}\n <CheckboxBox isSelected={isSelected} isIndeterminate={isIndeterminate} isDisabled={isDisabled} />\n </>\n )}\n </AriaCheckbox>\n )\n}\n\nPlainCheckbox.displayName = 'PlainCheckbox'\n\n// ── Labeled Checkbox ───────────────────────────────────────────────────────────\n\nexport function LabeledCheckbox(props: LabeledCheckboxProps) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n\n const [checked, setChecked] = HeadlessForm.useControlledState(props.checked, props.defaultChecked ?? false, props.onChange)\n\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: checked,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n hiddenInputRef.current?.closest('label')?.focus()\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n return (\n <AriaCheckbox\n isSelected={checked}\n isIndeterminate={props.isIndeterminate}\n onChange={(v) => {\n setChecked(v)\n commitValidation()\n }}\n isDisabled={props.isDisabled}\n isInvalid={isInvalid}\n aria-describedby={helpTextId}\n className=\"group relative flex items-start gap-3 select-none focus:outline-none [&>div:first-child>div]:focus-visible:ring-2 [&>div:first-child>div]:focus-visible:ring-primary-500/20 [&>div:first-child>div]:focus-visible:ring-offset-2\"\n >\n {({ isSelected, isIndeterminate, isDisabled }) => (\n <>\n {props.isDisabled || !props.name ? null : (\n <HeadlessForm.HiddenInput\n ref={hiddenInputRef}\n name={props.name}\n form={props.form}\n value={checked ? 'true' : 'false'}\n />\n )}\n <div className=\"shrink-0\">\n <CheckboxBox isSelected={isSelected} isIndeterminate={isIndeterminate} isDisabled={isDisabled} />\n </div>\n <div className=\"flex flex-col justify-center\">\n <div className=\"flex items-center gap-2 justify-between\">\n <FormFieldComponents.Label as=\"span\">{props.label}</FormFieldComponents.Label>\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n UNSAFE_className=\"mt-0.5\"\n />\n </div>\n </>\n )}\n </AriaCheckbox>\n )\n}\n\nLabeledCheckbox.displayName = 'LabeledCheckbox'\n\n// ── Helper components ──────────────────────────────────────────────────────────\n\nfunction CheckboxBox({\n isSelected,\n isIndeterminate,\n isDisabled\n}: {\n isSelected: boolean\n isIndeterminate: boolean\n isDisabled: boolean\n}) {\n return (\n <div\n className={tw(\n 'relative h-5 w-5 rounded border transition-all duration-50',\n isSelected || isIndeterminate ? 'bg-primary-500 border-primary-500' : 'bg-white border-neutral-300',\n !isDisabled && !(isSelected || isIndeterminate) && 'group-hovered:border-neutral-400',\n isDisabled && 'opacity-50 cursor-not-allowed'\n )}\n >\n {isIndeterminate ? (\n <Icon name=\"minus\" className=\"h-5/6 absolute inset-0 m-auto aspect-square text-white\" />\n ) : (\n <Icon name=\"check\" className=\"h-5/6 absolute inset-0 m-auto aspect-square text-white\" />\n )}\n </div>\n )\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"checkbox-group","files":[{"name":"components/checkbox-group.tsx","content":"import * as React from 'react'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { LabeledCheckbox } from './checkbox'\n\nexport type CheckboxGroupOption = {\n value: string\n label: React.ReactNode\n description?: React.ReactNode\n isDisabled?: boolean\n}\n\nexport type CheckboxGroupProps = {\n name?: string\n form?: string\n\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n warningMessage?: React.ReactNode\n\n isRequired?: boolean\n isDisabled?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<string[]>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n value?: string[]\n defaultValue?: string[]\n onChange?: (value: string[]) => void\n\n options: CheckboxGroupOption[]\n orientation?: 'horizontal' | 'vertical'\n showSelectAll?: boolean\n minSelection?: number\n maxSelection?: number\n}\n\nexport function CheckboxGroup(props: CheckboxGroupProps) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n\n const [value, setValue] = HeadlessForm.useControlledState(props.value, props.defaultValue ?? [], props.onChange)\n\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n // Focus is handled by individual checkboxes\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n const orientation = props.orientation ?? 'vertical'\n\n const enabledOptions = props.options.filter((o) => !o.isDisabled)\n const allSelected = value.length === enabledOptions.length\n const someSelected = value.length > 0 && !allSelected\n\n const handleOptionToggle = (optionValue: string) => {\n const newValue = value.includes(optionValue) ? value.filter((v) => v !== optionValue) : [...value, optionValue]\n\n if (props.minSelection && newValue.length < props.minSelection) {\n return\n }\n if (props.maxSelection && newValue.length > props.maxSelection) {\n return\n }\n\n setValue(newValue)\n commitValidation()\n }\n\n const handleSelectAll = () => {\n if (allSelected) {\n setValue([])\n } else {\n setValue(enabledOptions.map((o) => o.value))\n }\n commitValidation()\n }\n\n return (\n <div\n className=\"relative\"\n role=\"group\"\n aria-labelledby={props.label ? `${helpTextId}-label` : undefined}\n aria-describedby={helpTextId}\n >\n {value.map((v, i) => (\n <HeadlessForm.HiddenInput\n key={i}\n ref={i === 0 ? hiddenInputRef : undefined}\n name={props.name}\n value={v}\n form={props.form}\n />\n ))}\n {value.length === 0 && <HeadlessForm.HiddenInput ref={hiddenInputRef} name={props.name} value=\"\" form={props.form} />}\n\n {props.label && (\n <div className=\"flex justify-between items-center mb-2\">\n <FormFieldComponents.Label as=\"span\" id={`${helpTextId}-label`}>\n {props.label}\n </FormFieldComponents.Label>\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )}\n\n {props.showSelectAll && (\n <div className=\"mb-2 pb-2 border-b border-neutral-200\">\n <LabeledCheckbox\n checked={allSelected}\n isIndeterminate={someSelected}\n onChange={handleSelectAll}\n isDisabled={props.isDisabled}\n label={<span className=\"text-sm font-medium\">Select All</span>}\n />\n </div>\n )}\n\n <div className={tw('flex', orientation === 'vertical' ? 'flex-col space-y-1.5' : 'flex-row flex-wrap gap-4')}>\n {props.options.map((option) => (\n <LabeledCheckbox\n key={option.value}\n label={option.label}\n helpText={option.description ?? null}\n checked={value.includes(option.value)}\n onChange={() => handleOptionToggle(option.value)}\n isDisabled={option.isDisabled || props.isDisabled}\n />\n ))}\n </div>\n\n {(props.minSelection || props.maxSelection) && (\n <div className=\"mt-2 text-xs text-neutral-500\">\n {props.minSelection && props.maxSelection\n ? `Select between ${props.minSelection} and ${props.maxSelection} options`\n : props.minSelection\n ? `Select at least ${props.minSelection} option${props.minSelection > 1 ? 's' : ''}`\n : `Select up to ${props.maxSelection} option${props.maxSelection! > 1 ? 's' : ''}`}\n {' · '}\n {value.length} selected\n </div>\n )}\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n\nCheckboxGroup.displayName = 'CheckboxGroup'\n"},{"name":"components/checkbox.tsx","content":"import * as React from 'react'\nimport { Checkbox as AriaCheckbox } from 'react-aria-components'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { Icon } from './icon'\n\n// ── Props ─────────────────────────────────────────────────────────────────────\n\nexport type PlainCheckboxProps = {\n name?: string\n form?: string\n\n isDisabled?: boolean\n isRequired?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<boolean>\n\n checked?: boolean\n defaultChecked?: boolean\n onChange?: (checked: boolean) => void\n\n 'aria-label'?: string\n}\n\nexport type LabeledCheckboxProps = {\n name?: string\n form?: string\n\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n warningMessage?: React.ReactNode\n\n isRequired?: boolean\n isDisabled?: boolean\n isIndeterminate?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<boolean>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n checked?: boolean\n defaultChecked?: boolean\n onChange?: (checked: boolean) => void\n\n value?: string\n}\n\n// ── Plain Checkbox ─────────────────────────────────────────────────────────────\n\nexport function PlainCheckbox(props: PlainCheckboxProps) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n\n const [checked, setChecked] = HeadlessForm.useControlledState(props.checked, props.defaultChecked ?? false, props.onChange)\n\n const { isInvalid: validationInvalid, commitValidation } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: checked,\n name: props.name,\n isRequired: false,\n form: props.form,\n focus() {\n hiddenInputRef.current?.closest('label')?.focus()\n }\n },\n hiddenInputRef\n )\n\n return (\n <AriaCheckbox\n isSelected={checked}\n onChange={(v) => {\n setChecked(v)\n commitValidation()\n }}\n isDisabled={props.isDisabled}\n isInvalid={props.isInvalid || validationInvalid}\n aria-label={props['aria-label']}\n className=\"group inline-flex items-center focus:outline-none [&>div]:focus-visible:ring-2 [&>div]:focus-visible:ring-primary-500/20 [&>div]:focus-visible:ring-offset-2\"\n >\n {({ isSelected, isIndeterminate, isDisabled }) => (\n <>\n {props.isDisabled || !props.name ? null : (\n <HeadlessForm.HiddenInput\n ref={hiddenInputRef}\n name={props.name}\n form={props.form}\n value={checked ? 'true' : 'false'}\n />\n )}\n <CheckboxBox isSelected={isSelected} isIndeterminate={isIndeterminate} isDisabled={isDisabled} />\n </>\n )}\n </AriaCheckbox>\n )\n}\n\nPlainCheckbox.displayName = 'PlainCheckbox'\n\n// ── Labeled Checkbox ───────────────────────────────────────────────────────────\n\nexport function LabeledCheckbox(props: LabeledCheckboxProps) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n\n const [checked, setChecked] = HeadlessForm.useControlledState(props.checked, props.defaultChecked ?? false, props.onChange)\n\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value: checked,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n hiddenInputRef.current?.closest('label')?.focus()\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n\n return (\n <AriaCheckbox\n isSelected={checked}\n isIndeterminate={props.isIndeterminate}\n onChange={(v) => {\n setChecked(v)\n commitValidation()\n }}\n isDisabled={props.isDisabled}\n isInvalid={isInvalid}\n aria-describedby={helpTextId}\n className=\"group relative flex items-start gap-3 select-none focus:outline-none [&>div:first-child>div]:focus-visible:ring-2 [&>div:first-child>div]:focus-visible:ring-primary-500/20 [&>div:first-child>div]:focus-visible:ring-offset-2\"\n >\n {({ isSelected, isIndeterminate, isDisabled }) => (\n <>\n {props.isDisabled || !props.name ? null : (\n <HeadlessForm.HiddenInput\n ref={hiddenInputRef}\n name={props.name}\n form={props.form}\n value={checked ? 'true' : 'false'}\n />\n )}\n <div className=\"shrink-0\">\n <CheckboxBox isSelected={isSelected} isIndeterminate={isIndeterminate} isDisabled={isDisabled} />\n </div>\n <div className=\"flex flex-col justify-center\">\n <div className=\"flex items-center gap-2 justify-between\">\n <FormFieldComponents.Label as=\"span\">{props.label}</FormFieldComponents.Label>\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n UNSAFE_className=\"mt-0.5\"\n />\n </div>\n </>\n )}\n </AriaCheckbox>\n )\n}\n\nLabeledCheckbox.displayName = 'LabeledCheckbox'\n\n// ── Helper components ──────────────────────────────────────────────────────────\n\nfunction CheckboxBox({\n isSelected,\n isIndeterminate,\n isDisabled\n}: {\n isSelected: boolean\n isIndeterminate: boolean\n isDisabled: boolean\n}) {\n return (\n <div\n className={tw(\n 'relative h-5 w-5 rounded border transition-all duration-50',\n isSelected || isIndeterminate ? 'bg-primary-500 border-primary-500' : 'bg-white border-neutral-300',\n !isDisabled && !(isSelected || isIndeterminate) && 'group-hovered:border-neutral-400',\n isDisabled && 'opacity-50 cursor-not-allowed'\n )}\n >\n {isIndeterminate ? (\n <Icon name=\"minus\" className=\"h-5/6 absolute inset-0 m-auto aspect-square text-white\" />\n ) : (\n <Icon name=\"check\" className=\"h-5/6 absolute inset-0 m-auto aspect-square text-white\" />\n )}\n </div>\n )\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"radio","files":[{"name":"components/radio.tsx","content":"import * as React from 'react'\nimport { Radio as AriaRadio } from 'react-aria-components'\nimport { tw } from '../utils/tw'\n\n// ── Props ─────────────────────────────────────────────────────────────────────\n\nexport type PlainRadioProps = {\n value: string\n isDisabled?: boolean\n 'aria-label'?: string\n}\n\nexport type LabeledRadioProps = {\n value: string\n label?: React.ReactNode\n description?: React.ReactNode\n isDisabled?: boolean\n}\n\n// ── Plain Radio ──────────────────────────────────────────────────────────────\n\nexport function PlainRadio(props: PlainRadioProps) {\n return (\n <AriaRadio\n value={props.value}\n isDisabled={props.isDisabled}\n aria-label={props['aria-label']}\n className=\"group inline-flex items-center cursor-pointer data-[disabled]:cursor-not-allowed focus:outline-none [&>div]:focus-visible:ring-2 [&>div]:focus-visible:ring-primary-500/20 [&>div]:focus-visible:ring-offset-2\"\n >\n {({ isSelected, isDisabled }) => <RadioCircle isSelected={isSelected} isDisabled={isDisabled} />}\n </AriaRadio>\n )\n}\n\nPlainRadio.displayName = 'PlainRadio'\n\n// ── Labeled Radio ────────────────────────────────────────────────────────────\n\nexport function LabeledRadio(props: LabeledRadioProps) {\n return (\n <AriaRadio\n value={props.value}\n isDisabled={props.isDisabled}\n className=\"group flex items-start gap-3 cursor-pointer data-[disabled]:cursor-not-allowed select-none focus:outline-none [&>div:first-child>div]:focus-visible:ring-2 [&>div:first-child>div]:focus-visible:ring-primary-500/20 [&>div:first-child>div]:focus-visible:ring-offset-2\"\n >\n {({ isSelected, isDisabled }) => (\n <>\n <div className=\"flex-shrink-0\">\n <RadioCircle isSelected={isSelected} isDisabled={isDisabled} />\n </div>\n {props.label && (\n <div className=\"flex flex-col\">\n <span className={tw('text-sm font-medium', isDisabled ? 'text-neutral-400' : 'text-neutral-700')}>\n {props.label}\n </span>\n {props.description && (\n <span className={tw('text-xs mt-0.5', isDisabled ? 'text-neutral-400' : 'text-neutral-500')}>\n {props.description}\n </span>\n )}\n </div>\n )}\n </>\n )}\n </AriaRadio>\n )\n}\n\nLabeledRadio.displayName = 'LabeledRadio'\n\n// ── Helper components ─────────────────────────────────────────────────────────\n\nfunction RadioCircle({ isSelected, isDisabled }: { isSelected: boolean; isDisabled: boolean }) {\n return (\n <div\n className={tw(\n 'h-5 w-5 rounded-full border-2 transition-all duration-50 flex items-center justify-center',\n isSelected ? 'border-primary-500 bg-primary-500' : 'border-neutral-300 bg-white',\n !isDisabled && !isSelected && 'group-hovered:border-neutral-400',\n isDisabled && 'opacity-50 cursor-not-allowed'\n )}\n >\n <div\n className={tw(\n 'h-2 w-2 rounded-full bg-white transition-all duration-50',\n isSelected ? 'scale-100 opacity-100' : 'scale-0 opacity-0'\n )}\n />\n </div>\n )\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"radio-group","files":[{"name":"components/radio-group.tsx","content":"import * as React from 'react'\nimport { RadioGroup as AriaRadioGroup } from 'react-aria-components'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\n\nexport type RadioGroupProps = {\n name?: string\n form?: string\n\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n warningMessage?: React.ReactNode\n\n isRequired?: boolean\n isDisabled?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<string | null>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n value?: string | null\n defaultValue?: string | null\n onChange?: (value: string | null) => void\n\n children?: React.ReactNode\n orientation?: 'horizontal' | 'vertical' | 'grid'\n className?: string\n}\n\nexport function RadioGroup(props: RadioGroupProps) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const labelId = React.useId()\n\n const [value, setValue] = HeadlessForm.useControlledState(props.value, props.defaultValue ?? null, props.onChange)\n\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n // Focus is handled by AriaRadioGroup\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n const orientation = props.orientation ?? 'vertical'\n\n return (\n <div className=\"relative\">\n <HeadlessForm.HiddenInput ref={hiddenInputRef} name={props.name} value={value ?? ''} form={props.form} />\n\n {props.label && (\n <div className=\"flex justify-between items-center mb-2\">\n <FormFieldComponents.Label as=\"span\" id={labelId}>\n {props.label}\n </FormFieldComponents.Label>\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )}\n\n <AriaRadioGroup\n aria-labelledby={props.label ? labelId : undefined}\n aria-describedby={helpTextId}\n value={value}\n onChange={(newValue) => {\n setValue(newValue)\n commitValidation()\n }}\n isDisabled={props.isDisabled}\n orientation={orientation === 'grid' ? undefined : orientation}\n >\n <div\n className={tw(\n 'flex',\n orientation === 'vertical'\n ? 'flex-col space-y-2'\n : orientation === 'horizontal'\n ? 'flex-row flex-wrap gap-4'\n : 'grid gap-4',\n props.className\n )}\n >\n {props.children}\n </div>\n </AriaRadioGroup>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n\nRadioGroup.displayName = 'RadioGroup'\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"switch","files":[{"name":"components/switch.tsx","content":"import * as React from 'react'\nimport { Switch as AriaSwitch } from 'react-aria-components'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { FormFieldComponents } from './helpers/form-field'\n\n// ── Props ──────────────────────────────────────────────────────────────────────\n\nexport type PlainSwitchProps = {\n name?: string\n form?: string\n\n isDisabled?: boolean\n\n value?: boolean\n defaultValue?: boolean\n onChange?: (value: boolean) => void\n\n 'aria-label'?: string\n}\n\nexport type LabeledSwitchProps = {\n name?: string\n form?: string\n\n label?: React.ReactNode\n description?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n warningMessage?: React.ReactNode\n\n isRequired?: boolean\n isDisabled?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<boolean>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n value?: boolean\n defaultValue?: boolean\n onChange?: (value: boolean) => void\n\n labelPosition?: 'left' | 'right'\n}\n\n// ── Plain Switch ───────────────────────────────────────────────────────────────\n\nexport function PlainSwitch(props: PlainSwitchProps) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n\n const [value, setValue] = HeadlessForm.useControlledState(props.value, props.defaultValue ?? false, props.onChange)\n\n return (\n <AriaSwitch\n isSelected={value}\n onChange={setValue}\n isDisabled={props.isDisabled}\n aria-label={props['aria-label']}\n className=\"group inline-flex items-center cursor-pointer data-[disabled]:cursor-not-allowed focus:outline-none [&>div]:focus-visible:ring-2 [&>div]:focus-visible:ring-primary-500/20 [&>div]:focus-visible:ring-offset-2 [&>div]:focus-visible:rounded-full\"\n >\n {({ isSelected, isDisabled }) => (\n <>\n {props.isDisabled || !props.name ? null : (\n <HeadlessForm.HiddenInput\n ref={hiddenInputRef}\n name={props.name}\n form={props.form}\n value={value ? 'true' : 'false'}\n />\n )}\n <SwitchTrack isSelected={isSelected} isDisabled={isDisabled} />\n </>\n )}\n </AriaSwitch>\n )\n}\n\nPlainSwitch.displayName = 'PlainSwitch'\n\n// ── Labeled Switch ─────────────────────────────────────────────────────────────\n\nexport function LabeledSwitch(props: LabeledSwitchProps) {\n const hiddenInputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n\n const [value, setValue] = HeadlessForm.useControlledState(props.value, props.defaultValue ?? false, props.onChange)\n\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n hiddenInputRef.current?.closest('label')?.focus()\n }\n },\n hiddenInputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n const labelPosition = props.labelPosition ?? 'right'\n\n const labelElement = props.label && (\n <div className=\"flex flex-col text-left\">\n <FormFieldComponents.Label as=\"span\">{props.label}</FormFieldComponents.Label>\n {props.description && (\n <span className={tw('text-xs mt-0.5', props.isDisabled ? 'text-neutral-400' : 'text-neutral-500')}>\n {props.description}\n </span>\n )}\n </div>\n )\n\n return (\n <div className=\"relative\">\n <AriaSwitch\n isSelected={value}\n onChange={(checked) => {\n setValue(checked)\n commitValidation()\n }}\n isDisabled={props.isDisabled}\n aria-describedby={helpTextId}\n className=\"group flex items-center gap-3 cursor-pointer data-[disabled]:cursor-not-allowed select-none focus:outline-none [&>div:first-child>div]:focus-visible:ring-2 [&>div:first-child>div]:focus-visible:ring-primary-500/20 [&>div:first-child>div]:focus-visible:ring-offset-2 [&>div:first-child>div]:focus-visible:rounded-full\"\n >\n {({ isSelected, isDisabled }) => (\n <>\n {props.isDisabled || !props.name ? null : (\n <HeadlessForm.HiddenInput\n ref={hiddenInputRef}\n name={props.name}\n form={props.form}\n value={value ? 'true' : 'false'}\n />\n )}\n {labelPosition === 'left' && labelElement}\n <div className=\"flex-shrink-0 flex items-center\">\n <SwitchTrack isSelected={isSelected} isDisabled={isDisabled} />\n </div>\n {labelPosition === 'right' && labelElement}\n {props.cornerHint && props.label && <div className=\"ml-auto text-sm text-neutral-500\">{props.cornerHint}</div>}\n </>\n )}\n </AriaSwitch>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={validationMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n )\n}\n\nLabeledSwitch.displayName = 'LabeledSwitch'\n\n// ── Helpers ────────────────────────────────────────────────────────────────────\n\nfunction SwitchTrack({ isSelected, isDisabled }: { isSelected: boolean; isDisabled: boolean }) {\n return (\n <div\n className={tw(\n 'relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out',\n isSelected ? 'bg-primary-500' : 'bg-neutral-200',\n isDisabled && 'opacity-50 cursor-not-allowed'\n )}\n >\n <span\n className={tw(\n 'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',\n isSelected ? 'translate-x-5' : 'translate-x-0'\n )}\n />\n </div>\n )\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"file-input","files":[{"name":"components/file-input.tsx","content":"import * as React from 'react'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { formatFileSize, validateFileMaxSize, validateFileType } from '../utils/file-input'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { HeadlessFileInput } from './headless-file-input'\nimport { Icon } from './icon'\n\nexport type FileInputProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n warningMessage?: React.ReactNode\n renderPreview?: (props: FileInputPreviewProps) => React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<string | File | null>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management (File object or string URL, held in memory until form submit)\n value?: string | File | null\n defaultValue?: string | File | null\n onChange?: (value: string | File | null, hasError: boolean) => void\n\n // File specific\n accept?: string\n maxSize?: number // in bytes\n}\n\nexport interface FileInputPreviewProps {\n file: File | null\n fileUrl: string | null\n isDisabled: boolean\n onChange(value: string | null | File): unknown\n inputId: string\n name: string | undefined\n accept: string | undefined\n inputRef: React.Ref<HTMLInputElement>\n formatFileSize: (bytes: number) => string\n setDragging: (dragging: boolean) => void\n maxSize?: number\n}\n\nexport function FileInput(props: FileInputProps) {\n const inputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const inputId = React.useId()\n const [, setDragging] = React.useState(false)\n\n // Controlled state management\n const [value, setValue] = HeadlessForm.useControlledState(props.value, props.defaultValue ?? null, (value) => {\n if (props.onChange) {\n props.onChange(value, Boolean(validateFileMaxSize(value, props.maxSize) || validateFileType(value, props.accept)))\n }\n })\n\n const exceededMaxSize = React.useMemo(() => validateFileMaxSize(value, props.maxSize), [value, props.maxSize])\n const formatNotSupported = React.useMemo(() => validateFileType(value, props.accept), [value, props.accept])\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n inputRef.current?.focus()\n }\n },\n inputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid || Boolean(exceededMaxSize || formatNotSupported)\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n const errorMessage = exceededMaxSize || formatNotSupported || validationMessage\n\n const FilePreview = props.renderPreview ?? DefaultFileInputFilePreview\n\n return (\n <HeadlessFileInput\n value={value}\n onChange={(v) => {\n setValue(v)\n commitValidation()\n }}\n >\n <div className=\"relative\">\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} htmlFor={inputId} />\n\n <HeadlessFileInput.Preview>\n {(files) => {\n const file = files.length !== 0 ? files[0]!.file : null\n const fileUrl = files.length !== 0 ? files[0]!.fileUrl : null\n\n return (\n <div\n className={tw(\n 'relative',\n hasWarning &&\n '[&_.file-preview-border]:border-notice-300 [&_.file-preview-border]:hover:border-notice-400 [&_.file-preview-border]:text-notice-900 [&_.file-preview-border]:placeholder-notice-300 [&_.file-preview-border]:focus-within:border-notice-500 [&_.file-preview-border]:focus-within:ring-notice-500',\n isInvalid &&\n '[&_.file-preview-border]:border-negative-300 [&_.file-preview-border]:hover:border-negative-400 [&_.file-preview-border]:text-negative-900 [&_.file-preview-border]:placeholder-negative-300 [&_.file-preview-border]:focus-within:border-negative-500 [&_.file-preview-border]:focus-within:ring-negative-500'\n )}\n >\n {FilePreview({\n file,\n fileUrl,\n isDisabled: !!props.isDisabled,\n onChange: (v) => {\n setValue(v)\n commitValidation()\n },\n inputId,\n inputRef,\n name: props.name,\n accept: props.accept,\n formatFileSize,\n setDragging,\n maxSize: props.maxSize\n })}\n </div>\n )\n }}\n </HeadlessFileInput.Preview>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={errorMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n </HeadlessFileInput>\n )\n}\n\nfunction DragOverContent() {\n return (\n <div\n className=\"absolute inset-0 p-12 rounded flex items-center justify-center\"\n style={{\n background: `repeating-linear-gradient(45deg, #e5e7eb, #e5e7eb 20px, #d1d5db 20px, #d1d5db 50px)`\n }}\n >\n <div>\n <Icon name=\"cloud-arrow-up\" className=\"h-12 w-12 mx-auto text-gray-400\" />\n <span className=\"mt-2 block text-sm font-semibold text-gray-900\">Upload a File</span>\n </div>\n </div>\n )\n}\n\nexport function DefaultFileInputFilePreview({\n file,\n isDisabled,\n onChange,\n inputId,\n name,\n accept,\n formatFileSize,\n setDragging,\n maxSize\n}: FileInputPreviewProps) {\n return (\n <>\n <HeadlessFileInput.DropZone\n accept={accept}\n name={name}\n inputId={inputId}\n onDragChange={setDragging}\n disabled={isDisabled}\n >\n {({ dragOver }) => (\n <div\n className={tw(\n 'file-preview-border relative block w-full rounded-md text-center focus:outline-none focus-within:ring-2 focus-within:ring-primary-500 focus-within:ring-offset-2',\n 'border-2 border-neutral-200 hover:border-neutral-300',\n file ? '' : 'border-dashed',\n isDisabled && 'border-neutral-100 cursor-not-allowed'\n )}\n >\n {file ? (\n <div className=\"relative text-left\">\n <div className=\"flex items-center justify-between p-3 rounded\">\n <div className=\"flex items-center gap-3\">\n <Icon name=\"document\" className=\"h-8 w-8 text-neutral-400\" />\n <div>\n <p className=\"text-sm font-medium text-neutral-900\">{file.name || 'File'}</p>\n <p className=\"text-xs text-neutral-500\">{formatFileSize(file.size)}</p>\n </div>\n </div>\n </div>\n {dragOver && <DragOverContent />}\n </div>\n ) : dragOver ? (\n <DragOverContent />\n ) : (\n <div className=\"flex items-center justify-between p-3\">\n <div className=\"flex items-center gap-3\">\n <Icon name=\"cloud-arrow-up\" className=\"h-8 w-8 text-neutral-400\" />\n <div className=\"text-left\">\n <p className=\"text-sm text-neutral-600 font-medium\">Click to upload or drag and drop</p>\n <div className=\"text-xs text-neutral-500 mt-0.5\">\n {accept && <span>{accept}</span>}\n {accept && maxSize && <span> • </span>}\n {maxSize && <span>Max: {formatFileSize(maxSize)}</span>}\n </div>\n </div>\n </div>\n </div>\n )}\n </div>\n )}\n </HeadlessFileInput.DropZone>\n\n {file && !isDisabled && (\n <button\n type=\"button\"\n aria-label=\"Remove file\"\n onClick={() => onChange(null)}\n className=\"absolute top-1/2 -translate-y-1/2 right-3 p-1 hover:bg-neutral-50 rounded transition-colors\"\n >\n <Icon name=\"x-mark\" className=\"h-5 w-5 text-neutral-500\" />\n </button>\n )}\n </>\n )\n}\n"},{"name":"components/headless-file-input.tsx","content":"import React from 'react'\nimport { useComposedRefs } from '../utils/compose-refs'\n\nexport type HeadlessFileInputProps = {\n children?: React.ReactNode\n} & (\n | {\n multiple: true\n value?: (string | File)[]\n defaultValue?: (string | File)[]\n onChange?(value: (string | File)[]): unknown\n }\n | {\n multiple?: false\n value?: string | File | null\n defaultValue?: string | File | null\n onChange?(value: string | File | null): unknown\n }\n)\n\ntype FileInputContext =\n | { multiple?: false; value: File | string | null; onChange(value: File | string | null): unknown }\n | { multiple: true; value: (File | string)[]; onChange(value: (File | string)[]): unknown }\n\nconst fileInputContext = React.createContext<FileInputContext>({\n multiple: false,\n value: null,\n onChange: () => null\n})\n\nfunction FileInputContainer({ multiple, value, defaultValue, onChange, children }: HeadlessFileInputProps) {\n const controlled = useControlled({ multiple, value, defaultValue, onChange }) as FileInputContext\n return <fileInputContext.Provider value={controlled}>{children}</fileInputContext.Provider>\n}\n\nFileInputContainer.displayName = 'HeadlessFileInput'\n\nconst FileInputDropZone = React.forwardRef<\n HTMLLabelElement,\n {\n name?: string\n children: ((props: { dragOver: boolean }) => React.ReactElement) | React.ReactElement\n inputId?: string\n accept?: string | undefined\n onDragChange?(value: boolean): unknown\n capture?: React.InputHTMLAttributes<'file'>['capture']\n disabled?: React.InputHTMLAttributes<'file'>['disabled']\n processFile?: (file: File) => Promise<File>\n inputRef?: React.Ref<HTMLInputElement>\n } & Omit<React.ComponentProps<'label'>, 'children'>\n>(({ children, inputId, inputRef, onDragChange, name, accept, capture, disabled, processFile, ...props }, ref) => {\n const { onChange, value, multiple } = React.useContext(fileInputContext)\n const [dragOver, setDragOver] = React.useState(false)\n const startDrag = React.useCallback(\n (e: React.DragEvent<HTMLInputElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(true)\n onDragChange && onDragChange(true)\n },\n [setDragOver, onDragChange]\n )\n const stopDrag = React.useCallback(\n (e: React.DragEvent<HTMLInputElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(false)\n onDragChange && onDragChange(false)\n },\n [setDragOver, onDragChange]\n )\n\n const childrenResult = typeof children === 'function' ? children({ dragOver }) : children\n const childProps = childrenResult.props as Record<string, unknown>\n const cloned = React.cloneElement(\n childrenResult,\n {\n ...childProps\n },\n [\n ...React.Children.toArray(childProps.children as React.ReactNode),\n <FileInputInput\n key=\"file-input-hidden\"\n id={inputId}\n name={name}\n accept={accept}\n ref={inputRef}\n style={{ width: '0.1px', height: '0.1px', opacity: 0, overflow: 'hidden', position: 'absolute', zIndex: -10 }}\n capture={capture}\n disabled={disabled}\n processFile={processFile}\n />\n ]\n )\n\n return (\n <label {...props} ref={ref}>\n <div\n onDrag={cancel}\n onDragStart={cancel}\n onDragOver={cancel}\n onDragEnter={startDrag}\n onDragLeave={stopDrag}\n onDrop={(e) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(false)\n onDragChange && onDragChange(false)\n if (e.dataTransfer.files.length) {\n if (multiple) {\n onChange([...value, ...Array.from(e.dataTransfer.files)])\n } else {\n onChange(e.dataTransfer.files.item(0) || null)\n }\n } else {\n const uri = e.dataTransfer.getData('text/uri-list')\n if (uri) {\n fetchFileFromUri(uri).then((file) => {\n if (multiple) {\n onChange([...value, file])\n } else {\n onChange(file)\n }\n })\n }\n }\n }}\n >\n {cloned}\n </div>\n </label>\n )\n})\nFileInputDropZone.displayName = 'FileInputDropZone'\n\nconst FileInputInput = React.forwardRef<\n HTMLInputElement,\n React.ComponentPropsWithoutRef<'input'> & { processFile?: (file: File) => Promise<File> }\n>(({ name, processFile, form, ...props }, refProp) => {\n const { multiple, value, onChange } = React.useContext(fileInputContext)\n const localRef = React.useRef<HTMLInputElement>(null)\n const ref = useComposedRefs(localRef, refProp)\n\n React.useEffect(() => {\n if (localRef.current) {\n const dt = new DataTransfer()\n if (Array.isArray(value)) {\n value.forEach(async (v) => v instanceof File && dt.items.add(v))\n } else if (value && value instanceof File) {\n dt.items.add(value)\n }\n localRef.current.files = dt.files\n }\n }, [value, processFile])\n\n const urls = React.useMemo(() => {\n if (Array.isArray(value)) {\n return value.map((v, i) => (typeof v === 'string' ? { name: `${name}.${i}`, value: v } : null)).filter(Boolean) as {\n name: string\n value: string\n }[]\n } else if (value && typeof value === 'string') {\n return [{ name: name, value: value }]\n } else {\n return []\n }\n }, [value, name])\n\n const hasFiles =\n typeof value !== 'string' &&\n !!value &&\n !(Array.isArray(value) && !value.filter((v) => v && typeof v !== 'string').length)\n const hasUrls = !!urls.length\n\n return (\n <>\n <input\n {...props}\n type=\"file\"\n form={form}\n ref={hasFiles ? ref : undefined}\n name={hasFiles ? name : undefined}\n onChange={async (e) => {\n const newValue = (\n multiple\n ? e.currentTarget.files\n ? [\n ...value,\n ...(await Promise.all(\n Array.from(e.currentTarget.files).map(async (file) => (processFile ? processFile(file) : file))\n ))\n ]\n : []\n : processFile && e.currentTarget.files?.item(0)\n ? await processFile(e.currentTarget.files?.item(0) as File)\n : e.currentTarget.files?.item(0)\n ) as any\n\n onChange(newValue)\n }}\n multiple={multiple}\n />\n {urls.map((url, i) => (\n <input\n type=\"hidden\"\n ref={i === 0 && !hasFiles ? ref : undefined}\n form={form}\n value={url.value}\n name={url.name}\n key={url.name}\n />\n ))}\n {!hasUrls && !hasFiles ? <input type=\"hidden\" ref={ref} form={form} name={name} value=\"\" /> : null}\n </>\n )\n})\nFileInputInput.displayName = 'FileInputInput'\n\nfunction FilePreview({\n children\n}: {\n children(\n props: {\n file: File | null\n fileUrl: string\n remove(): unknown\n replace(newFile: File | string): unknown\n }[]\n ): React.ReactNode\n}) {\n const { value, onChange } = React.useContext(fileInputContext)\n const urlMap = useFileUrls(value)\n const removeSingle = React.useCallback(() => onChange(null as any), [onChange])\n const replaceSingle = React.useCallback((file: File | string) => onChange(file as any), [onChange])\n\n const removeMultiple = React.useCallback(\n (index: number) => onChange((value as File[]).filter((f, i) => i !== index) as any),\n [value, onChange]\n )\n const replaceMultiple = React.useCallback(\n (index: number, file: File | string) =>\n onChange((value as (File | string)[]).map((f, i) => (i === index ? file : f)) as any),\n [value, onChange]\n )\n\n if (!value) {\n return <>{children([])}</>\n } else if (Array.isArray(value)) {\n return (\n <>\n {children(\n value.map((v, i) => ({\n file: typeof v === 'string' ? null : v,\n fileUrl: urlMap.get(v) ?? (typeof v === 'string' ? v : ''),\n remove: () => removeMultiple(i),\n replace: (f) => replaceMultiple(i, f)\n }))\n )}\n </>\n )\n } else {\n return (\n <>\n {children([\n {\n file: typeof value === 'string' ? null : value,\n fileUrl: urlMap.get(value) ?? (typeof value === 'string' ? value : ''),\n remove: removeSingle,\n replace: replaceSingle\n }\n ])}\n </>\n )\n }\n}\n\nexport const HeadlessFileInput = Object.assign(FileInputContainer, {\n Input: FileInputInput,\n Preview: FilePreview,\n DropZone: FileInputDropZone\n})\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction cancel(e: React.SyntheticEvent) {\n e.stopPropagation()\n e.preventDefault()\n}\n\nfunction useControlled<T extends string | File | null | (string | File)[]>({\n defaultValue,\n value,\n onChange: onChangeRaw,\n multiple\n}: {\n value?: T\n defaultValue?: T\n onChange?(value: null | string | File | (string | File)[]): unknown\n multiple?: boolean\n}) {\n const [state, setState] = React.useState(defaultValue)\n\n const onChange = React.useCallback(\n (v: T) => {\n setState(v)\n onChangeRaw && onChangeRaw(v)\n },\n [onChangeRaw, setState]\n )\n\n const proposed = value === undefined ? state : value\n\n return { value: !proposed && multiple ? [] : proposed || null, onChange, multiple: !!multiple }\n}\n\nfunction useFileUrls(value: (File | string)[] | File | string | null): Map<File | string, string> {\n const urlMapRef = React.useRef(new Map<File | string, string>())\n\n const items = value === null ? [] : Array.isArray(value) ? value : [value]\n\n // Create blob URLs for new items only\n for (const item of items) {\n if (!urlMapRef.current.has(item)) {\n urlMapRef.current.set(item, typeof item === 'string' ? item : URL.createObjectURL(item))\n }\n }\n\n // Prune stale entries and revoke their blob URLs\n const currentKeys = new Set(items)\n React.useEffect(() => {\n for (const [key, url] of urlMapRef.current) {\n if (!currentKeys.has(key)) {\n if (typeof key !== 'string') URL.revokeObjectURL(url)\n urlMapRef.current.delete(key)\n }\n }\n })\n\n // Revoke all blob URLs on unmount\n React.useEffect(() => {\n const map = urlMapRef.current\n return () => {\n for (const [key, url] of map) {\n if (typeof key !== 'string') URL.revokeObjectURL(url)\n }\n map.clear()\n }\n }, [])\n\n return urlMapRef.current\n}\n\nasync function fetchFileFromUri(uri: string): Promise<File> {\n const res = await fetch(uri)\n const blob = await res.blob()\n const contentType = res.headers.get('content-type') || blob.type || ''\n const contentDisposition = res.headers.get('content-disposition')\n let filename = new URL(uri).pathname.split('/').pop() || 'file'\n const match = contentDisposition?.match(/filename\\*?=(?:UTF-8'')?[\"']?([^\"';\\n]+)/)\n if (match) filename = decodeURIComponent(match[1]!)\n return new File([blob], filename, { type: contentType })\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/compose-refs.ts","content":"import React from 'react'\n\ntype PossibleRef<T> = React.Ref<T> | undefined\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n if (typeof ref === 'function') {\n ref(value)\n } else if (ref !== null && ref !== undefined) {\n ;(ref as React.MutableRefObject<T>).current = value\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nexport function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n return (node: T) => refs.forEach((ref) => setRef(ref, node))\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nexport function useComposedRefs<T>(...refs: PossibleRef<T>[]) {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return React.useCallback(composeRefs(...refs), refs)\n}\n"},{"name":"utils/file-input.ts","content":"/**\n * Shared utilities for file-input components.\n */\n\nexport function formatFileSize(bytes: number) {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\nexport function checkFileType(file: File, accept: string): string | null {\n const acceptedTypes = accept.split(',').map((type) => type.trim())\n const fileType = file.type\n const fileExtension = `.${file.name.split('.').pop()?.toLowerCase() || ''}`\n\n const isValid = acceptedTypes.some((acceptType) => {\n if (acceptType.includes('/*')) {\n const prefix = acceptType.split('/')[0]\n return fileType.startsWith(`${prefix}/`)\n }\n if (acceptType.startsWith('.')) {\n return acceptType.toLowerCase() === fileExtension\n }\n return fileType === acceptType\n })\n\n return isValid ? null : `Unsupported file type. Supported types: ${acceptedTypes.join(', ')}`\n}\n\nexport function checkFileMaxSize(file: File, maxSize: number): string | null {\n if (file.size > maxSize) {\n return `File exceeded max size of ${formatFileSize(maxSize)}`\n }\n return null\n}\n\nexport function validateFileMaxSize(value: string | File | null, maxSize?: number): string | null {\n if (value instanceof File && maxSize) {\n return checkFileMaxSize(value, maxSize)\n }\n return null\n}\n\nexport function validateFileType(value: string | File | null, accept?: string): string | null {\n if (value instanceof File && accept) {\n return checkFileType(value, accept)\n }\n return null\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"multi-file-input","files":[{"name":"components/multi-file-input.tsx","content":"import * as React from 'react'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { formatFileSize, checkFileType, checkFileMaxSize } from '../utils/file-input'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { HeadlessFileInput } from './headless-file-input'\nimport { Icon } from './icon'\n\nexport interface UrlFileReference {\n name: string\n url: string\n}\n\nexport type FileLike = File | string | UrlFileReference\n\nexport type MultiFileInputProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n warningMessage?: React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<FileLike[]>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management (File objects array or strings, held in memory until form submit)\n value?: FileLike[]\n defaultValue?: FileLike[]\n onChange?: (value: FileLike[], hasError: boolean) => void\n\n // File specific\n accept?: string | null\n maxSize?: number // in bytes per file\n maxFiles?: number\n}\n\nexport function MultiFileInput(props: MultiFileInputProps) {\n const inputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const inputId = React.useId()\n\n const exceededMaxSizeFn = React.useCallback(\n (value: FileLike[]) => {\n for (let i = 0; i < value.length; i++) {\n const v = value[i]\n if (v instanceof File && props.maxSize) {\n const error = checkFileMaxSize(v, props.maxSize)\n if (error) return `File ${i + 1} exceeded max size of ${formatFileSize(props.maxSize)}`\n }\n }\n return null\n },\n [props.maxSize]\n )\n\n const formatNotSupportedFn = React.useCallback(\n (value: FileLike[]) => {\n if (!props.accept) return null\n for (const v of value) {\n if (v instanceof File) {\n const error = checkFileType(v, props.accept)\n if (error) return error\n }\n }\n return null\n },\n [props.accept]\n )\n\n // Controlled state management\n const [value, setValue] = HeadlessForm.useControlledState(props.value, props.defaultValue ?? [], (value) => {\n if (props.onChange) {\n props.onChange(value, Boolean(exceededMaxSizeFn(value) || formatNotSupportedFn(value)))\n }\n })\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate(files) {\n const exceededMaxSize = exceededMaxSizeFn(files)\n const formatNotSupported = formatNotSupportedFn(files)\n const exceededMaxFiles = props.maxFiles !== undefined && files.length > props.maxFiles ? 'Too many files' : null\n const custom = [props.validate ? props.validate(files) : []].flat()\n\n const errors = [exceededMaxSize, formatNotSupported, exceededMaxFiles, ...custom].filter(Boolean)\n return errors as string[]\n },\n value,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n inputRef.current?.focus()\n }\n },\n inputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n const errorMessage = validationMessage\n\n const handleRemove = (index: number) => {\n const newFiles = value.filter((_, i) => i !== index)\n setValue(newFiles)\n }\n\n return (\n <HeadlessFileInput\n value={value.map((v) => (v instanceof File ? v : typeof v === 'string' ? v : v.url))}\n onChange={(v) => {\n setValue(\n v.map((a) =>\n a instanceof File\n ? a\n : (value.find((b) => (b instanceof File || typeof b === 'string' ? false : b.url === a)) ?? {\n name: a,\n url: a\n })\n )\n )\n commitValidation()\n }}\n multiple\n >\n <HeadlessForm.HiddenInput value=\"\" form={props.form} name={undefined} ref={inputRef} />\n <div className=\"relative\">\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} htmlFor={inputId} />\n\n <HeadlessFileInput.Preview>\n {(files) => (\n <>\n {files.length > 0 && (\n <div className=\"space-y-2 mb-3\">\n {files.map((fileItem, index) => {\n const fileName = fileItem.file\n ? fileItem.file.name\n : (value.filter((v) => !(v instanceof File) && typeof v !== 'string') as UrlFileReference[]).find(\n (v) => v.url === fileItem.fileUrl\n )?.name\n return (\n <div\n key={index}\n className=\"flex items-center text-left justify-between px-6 py-2 rounded border border-neutral-200 bg-white\"\n >\n <div className=\"flex items-center gap-2\">\n <Icon name=\"document\" className=\"h-5 w-5 text-neutral-400\" />\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-neutral-900 truncate\">{fileName || 'File'}</p>\n {fileItem.file && <p className=\"text-xs text-neutral-500\">{formatFileSize(fileItem.file.size)}</p>}\n </div>\n </div>\n {!props.isDisabled && !props.isReadonly && (\n <button\n type=\"button\"\n onClick={() => handleRemove(index)}\n className=\"p-1 hover:bg-neutral-100 rounded transition-colors\"\n >\n <Icon name=\"x-mark\" className=\"h-4 w-4 text-neutral-500\" />\n </button>\n )}\n </div>\n )\n })}\n </div>\n )}\n </>\n )}\n </HeadlessFileInput.Preview>\n\n <HeadlessFileInput.DropZone\n accept={props.accept ?? undefined}\n name={props.name}\n inputId={inputId}\n disabled={props.isDisabled || props.isReadonly}\n >\n {({ dragOver }) => (\n <div\n className={tw(\n 'relative block w-full rounded-md text-center focus:outline-none focus-within:ring-2 focus-within:ring-primary-500 focus-within:ring-offset-2',\n 'border-2 border-dashed border-neutral-200 hover:border-neutral-300',\n 'px-6 py-2 min-h-[60px] flex items-center justify-center',\n (props.isDisabled || props.isReadonly) && 'border-neutral-100 cursor-not-allowed',\n hasWarning &&\n 'border-notice-300 hover:border-notice-400 text-notice-900 placeholder-notice-300 focus-within:border-notice-500 focus-within:ring-notice-500',\n isInvalid &&\n 'border-negative-300 hover:border-negative-400 text-negative-900 placeholder-negative-300 focus-within:border-negative-500 focus-within:ring-negative-500'\n )}\n >\n {dragOver ? (\n <DragOverContent />\n ) : (\n <div className=\"flex items-center w-full gap-3\">\n <Icon name=\"cloud-arrow-up\" className=\"h-5 w-5 text-neutral-400 flex-shrink-0\" />\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-neutral-600\">Click to upload or drag and drop</p>\n <p className=\"text-xs text-neutral-500\">\n {value.length > 0 ? 'Add more files' : 'Select files'}\n {props.accept && ` (${props.accept})`}\n {props.maxSize && ` • Max: ${formatFileSize(props.maxSize)}`}\n </p>\n </div>\n </div>\n )}\n </div>\n )}\n </HeadlessFileInput.DropZone>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={errorMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n </HeadlessFileInput>\n )\n}\n\nfunction DragOverContent() {\n return (\n <div className=\"flex items-center gap-3\">\n <Icon name=\"cloud-arrow-up\" className=\"h-5 w-5 text-gray-400 flex-shrink-0\" />\n <span className=\"text-sm font-semibold text-gray-900\">Drop files here</span>\n </div>\n )\n}\n"},{"name":"components/headless-file-input.tsx","content":"import React from 'react'\nimport { useComposedRefs } from '../utils/compose-refs'\n\nexport type HeadlessFileInputProps = {\n children?: React.ReactNode\n} & (\n | {\n multiple: true\n value?: (string | File)[]\n defaultValue?: (string | File)[]\n onChange?(value: (string | File)[]): unknown\n }\n | {\n multiple?: false\n value?: string | File | null\n defaultValue?: string | File | null\n onChange?(value: string | File | null): unknown\n }\n)\n\ntype FileInputContext =\n | { multiple?: false; value: File | string | null; onChange(value: File | string | null): unknown }\n | { multiple: true; value: (File | string)[]; onChange(value: (File | string)[]): unknown }\n\nconst fileInputContext = React.createContext<FileInputContext>({\n multiple: false,\n value: null,\n onChange: () => null\n})\n\nfunction FileInputContainer({ multiple, value, defaultValue, onChange, children }: HeadlessFileInputProps) {\n const controlled = useControlled({ multiple, value, defaultValue, onChange }) as FileInputContext\n return <fileInputContext.Provider value={controlled}>{children}</fileInputContext.Provider>\n}\n\nFileInputContainer.displayName = 'HeadlessFileInput'\n\nconst FileInputDropZone = React.forwardRef<\n HTMLLabelElement,\n {\n name?: string\n children: ((props: { dragOver: boolean }) => React.ReactElement) | React.ReactElement\n inputId?: string\n accept?: string | undefined\n onDragChange?(value: boolean): unknown\n capture?: React.InputHTMLAttributes<'file'>['capture']\n disabled?: React.InputHTMLAttributes<'file'>['disabled']\n processFile?: (file: File) => Promise<File>\n inputRef?: React.Ref<HTMLInputElement>\n } & Omit<React.ComponentProps<'label'>, 'children'>\n>(({ children, inputId, inputRef, onDragChange, name, accept, capture, disabled, processFile, ...props }, ref) => {\n const { onChange, value, multiple } = React.useContext(fileInputContext)\n const [dragOver, setDragOver] = React.useState(false)\n const startDrag = React.useCallback(\n (e: React.DragEvent<HTMLInputElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(true)\n onDragChange && onDragChange(true)\n },\n [setDragOver, onDragChange]\n )\n const stopDrag = React.useCallback(\n (e: React.DragEvent<HTMLInputElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(false)\n onDragChange && onDragChange(false)\n },\n [setDragOver, onDragChange]\n )\n\n const childrenResult = typeof children === 'function' ? children({ dragOver }) : children\n const childProps = childrenResult.props as Record<string, unknown>\n const cloned = React.cloneElement(\n childrenResult,\n {\n ...childProps\n },\n [\n ...React.Children.toArray(childProps.children as React.ReactNode),\n <FileInputInput\n key=\"file-input-hidden\"\n id={inputId}\n name={name}\n accept={accept}\n ref={inputRef}\n style={{ width: '0.1px', height: '0.1px', opacity: 0, overflow: 'hidden', position: 'absolute', zIndex: -10 }}\n capture={capture}\n disabled={disabled}\n processFile={processFile}\n />\n ]\n )\n\n return (\n <label {...props} ref={ref}>\n <div\n onDrag={cancel}\n onDragStart={cancel}\n onDragOver={cancel}\n onDragEnter={startDrag}\n onDragLeave={stopDrag}\n onDrop={(e) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(false)\n onDragChange && onDragChange(false)\n if (e.dataTransfer.files.length) {\n if (multiple) {\n onChange([...value, ...Array.from(e.dataTransfer.files)])\n } else {\n onChange(e.dataTransfer.files.item(0) || null)\n }\n } else {\n const uri = e.dataTransfer.getData('text/uri-list')\n if (uri) {\n fetchFileFromUri(uri).then((file) => {\n if (multiple) {\n onChange([...value, file])\n } else {\n onChange(file)\n }\n })\n }\n }\n }}\n >\n {cloned}\n </div>\n </label>\n )\n})\nFileInputDropZone.displayName = 'FileInputDropZone'\n\nconst FileInputInput = React.forwardRef<\n HTMLInputElement,\n React.ComponentPropsWithoutRef<'input'> & { processFile?: (file: File) => Promise<File> }\n>(({ name, processFile, form, ...props }, refProp) => {\n const { multiple, value, onChange } = React.useContext(fileInputContext)\n const localRef = React.useRef<HTMLInputElement>(null)\n const ref = useComposedRefs(localRef, refProp)\n\n React.useEffect(() => {\n if (localRef.current) {\n const dt = new DataTransfer()\n if (Array.isArray(value)) {\n value.forEach(async (v) => v instanceof File && dt.items.add(v))\n } else if (value && value instanceof File) {\n dt.items.add(value)\n }\n localRef.current.files = dt.files\n }\n }, [value, processFile])\n\n const urls = React.useMemo(() => {\n if (Array.isArray(value)) {\n return value.map((v, i) => (typeof v === 'string' ? { name: `${name}.${i}`, value: v } : null)).filter(Boolean) as {\n name: string\n value: string\n }[]\n } else if (value && typeof value === 'string') {\n return [{ name: name, value: value }]\n } else {\n return []\n }\n }, [value, name])\n\n const hasFiles =\n typeof value !== 'string' &&\n !!value &&\n !(Array.isArray(value) && !value.filter((v) => v && typeof v !== 'string').length)\n const hasUrls = !!urls.length\n\n return (\n <>\n <input\n {...props}\n type=\"file\"\n form={form}\n ref={hasFiles ? ref : undefined}\n name={hasFiles ? name : undefined}\n onChange={async (e) => {\n const newValue = (\n multiple\n ? e.currentTarget.files\n ? [\n ...value,\n ...(await Promise.all(\n Array.from(e.currentTarget.files).map(async (file) => (processFile ? processFile(file) : file))\n ))\n ]\n : []\n : processFile && e.currentTarget.files?.item(0)\n ? await processFile(e.currentTarget.files?.item(0) as File)\n : e.currentTarget.files?.item(0)\n ) as any\n\n onChange(newValue)\n }}\n multiple={multiple}\n />\n {urls.map((url, i) => (\n <input\n type=\"hidden\"\n ref={i === 0 && !hasFiles ? ref : undefined}\n form={form}\n value={url.value}\n name={url.name}\n key={url.name}\n />\n ))}\n {!hasUrls && !hasFiles ? <input type=\"hidden\" ref={ref} form={form} name={name} value=\"\" /> : null}\n </>\n )\n})\nFileInputInput.displayName = 'FileInputInput'\n\nfunction FilePreview({\n children\n}: {\n children(\n props: {\n file: File | null\n fileUrl: string\n remove(): unknown\n replace(newFile: File | string): unknown\n }[]\n ): React.ReactNode\n}) {\n const { value, onChange } = React.useContext(fileInputContext)\n const urlMap = useFileUrls(value)\n const removeSingle = React.useCallback(() => onChange(null as any), [onChange])\n const replaceSingle = React.useCallback((file: File | string) => onChange(file as any), [onChange])\n\n const removeMultiple = React.useCallback(\n (index: number) => onChange((value as File[]).filter((f, i) => i !== index) as any),\n [value, onChange]\n )\n const replaceMultiple = React.useCallback(\n (index: number, file: File | string) =>\n onChange((value as (File | string)[]).map((f, i) => (i === index ? file : f)) as any),\n [value, onChange]\n )\n\n if (!value) {\n return <>{children([])}</>\n } else if (Array.isArray(value)) {\n return (\n <>\n {children(\n value.map((v, i) => ({\n file: typeof v === 'string' ? null : v,\n fileUrl: urlMap.get(v) ?? (typeof v === 'string' ? v : ''),\n remove: () => removeMultiple(i),\n replace: (f) => replaceMultiple(i, f)\n }))\n )}\n </>\n )\n } else {\n return (\n <>\n {children([\n {\n file: typeof value === 'string' ? null : value,\n fileUrl: urlMap.get(value) ?? (typeof value === 'string' ? value : ''),\n remove: removeSingle,\n replace: replaceSingle\n }\n ])}\n </>\n )\n }\n}\n\nexport const HeadlessFileInput = Object.assign(FileInputContainer, {\n Input: FileInputInput,\n Preview: FilePreview,\n DropZone: FileInputDropZone\n})\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction cancel(e: React.SyntheticEvent) {\n e.stopPropagation()\n e.preventDefault()\n}\n\nfunction useControlled<T extends string | File | null | (string | File)[]>({\n defaultValue,\n value,\n onChange: onChangeRaw,\n multiple\n}: {\n value?: T\n defaultValue?: T\n onChange?(value: null | string | File | (string | File)[]): unknown\n multiple?: boolean\n}) {\n const [state, setState] = React.useState(defaultValue)\n\n const onChange = React.useCallback(\n (v: T) => {\n setState(v)\n onChangeRaw && onChangeRaw(v)\n },\n [onChangeRaw, setState]\n )\n\n const proposed = value === undefined ? state : value\n\n return { value: !proposed && multiple ? [] : proposed || null, onChange, multiple: !!multiple }\n}\n\nfunction useFileUrls(value: (File | string)[] | File | string | null): Map<File | string, string> {\n const urlMapRef = React.useRef(new Map<File | string, string>())\n\n const items = value === null ? [] : Array.isArray(value) ? value : [value]\n\n // Create blob URLs for new items only\n for (const item of items) {\n if (!urlMapRef.current.has(item)) {\n urlMapRef.current.set(item, typeof item === 'string' ? item : URL.createObjectURL(item))\n }\n }\n\n // Prune stale entries and revoke their blob URLs\n const currentKeys = new Set(items)\n React.useEffect(() => {\n for (const [key, url] of urlMapRef.current) {\n if (!currentKeys.has(key)) {\n if (typeof key !== 'string') URL.revokeObjectURL(url)\n urlMapRef.current.delete(key)\n }\n }\n })\n\n // Revoke all blob URLs on unmount\n React.useEffect(() => {\n const map = urlMapRef.current\n return () => {\n for (const [key, url] of map) {\n if (typeof key !== 'string') URL.revokeObjectURL(url)\n }\n map.clear()\n }\n }, [])\n\n return urlMapRef.current\n}\n\nasync function fetchFileFromUri(uri: string): Promise<File> {\n const res = await fetch(uri)\n const blob = await res.blob()\n const contentType = res.headers.get('content-type') || blob.type || ''\n const contentDisposition = res.headers.get('content-disposition')\n let filename = new URL(uri).pathname.split('/').pop() || 'file'\n const match = contentDisposition?.match(/filename\\*?=(?:UTF-8'')?[\"']?([^\"';\\n]+)/)\n if (match) filename = decodeURIComponent(match[1]!)\n return new File([blob], filename, { type: contentType })\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/compose-refs.ts","content":"import React from 'react'\n\ntype PossibleRef<T> = React.Ref<T> | undefined\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n if (typeof ref === 'function') {\n ref(value)\n } else if (ref !== null && ref !== undefined) {\n ;(ref as React.MutableRefObject<T>).current = value\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nexport function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n return (node: T) => refs.forEach((ref) => setRef(ref, node))\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nexport function useComposedRefs<T>(...refs: PossibleRef<T>[]) {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return React.useCallback(composeRefs(...refs), refs)\n}\n"},{"name":"utils/file-input.ts","content":"/**\n * Shared utilities for file-input components.\n */\n\nexport function formatFileSize(bytes: number) {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\nexport function checkFileType(file: File, accept: string): string | null {\n const acceptedTypes = accept.split(',').map((type) => type.trim())\n const fileType = file.type\n const fileExtension = `.${file.name.split('.').pop()?.toLowerCase() || ''}`\n\n const isValid = acceptedTypes.some((acceptType) => {\n if (acceptType.includes('/*')) {\n const prefix = acceptType.split('/')[0]\n return fileType.startsWith(`${prefix}/`)\n }\n if (acceptType.startsWith('.')) {\n return acceptType.toLowerCase() === fileExtension\n }\n return fileType === acceptType\n })\n\n return isValid ? null : `Unsupported file type. Supported types: ${acceptedTypes.join(', ')}`\n}\n\nexport function checkFileMaxSize(file: File, maxSize: number): string | null {\n if (file.size > maxSize) {\n return `File exceeded max size of ${formatFileSize(maxSize)}`\n }\n return null\n}\n\nexport function validateFileMaxSize(value: string | File | null, maxSize?: number): string | null {\n if (value instanceof File && maxSize) {\n return checkFileMaxSize(value, maxSize)\n }\n return null\n}\n\nexport function validateFileType(value: string | File | null, accept?: string): string | null {\n if (value instanceof File && accept) {\n return checkFileType(value, accept)\n }\n return null\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"image-input","files":[{"name":"components/image-input.tsx","content":"import * as React from 'react'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { formatFileSize, checkFileType, checkFileMaxSize } from '../utils/file-input'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { HeadlessFileInput } from './headless-file-input'\nimport { Icon } from './icon'\n\nexport type ImageInputProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n warningMessage?: React.ReactNode\n emptyState?: React.ReactNode\n renderPreview?: (props: ImageInputPreviewProps) => React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<string | File | null>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management (File object or string URL, held in memory until form submit)\n value?: string | File | null\n defaultValue?: string | File | null\n onChange?: (value: string | File | null) => void\n\n // Image specific\n accept?: string\n maxSize?: number // in bytes\n}\n\nexport interface ImageInputPreviewProps {\n fileUrl: string\n isDisabled: boolean\n onChange(value: string | null | File): unknown\n inputId: string\n name: string | undefined\n accept: string | undefined\n inputRef: React.Ref<HTMLInputElement>\n}\n\nexport function ImageInput(props: ImageInputProps) {\n const inputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const inputId = React.useId()\n\n const exceededMaxSizeFn = React.useCallback(\n (value: string | File | null) => {\n if (value && props.maxSize && value instanceof File) {\n return checkFileMaxSize(value, props.maxSize)\n }\n return null\n },\n [props.maxSize]\n )\n\n const formatNotSupportedFn = React.useCallback(\n (value: string | File | null) => {\n if (value instanceof File) {\n return checkFileType(value, props.accept ?? 'image/*')\n }\n return null\n },\n [props.accept]\n )\n\n // Controlled state management\n const [value, setValue] = HeadlessForm.useControlledState(props.value, props.defaultValue ?? null, (value) => {\n if (props.onChange) {\n props.onChange(value)\n }\n })\n\n const exceededMaxSize = React.useMemo(() => exceededMaxSizeFn(value), [value, exceededMaxSizeFn])\n const formatNotSupported = React.useMemo(() => formatNotSupportedFn(value), [value, formatNotSupportedFn])\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n inputRef.current?.focus()\n }\n },\n inputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid || Boolean(exceededMaxSize || formatNotSupported)\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n const errorMessage = exceededMaxSize || formatNotSupported || validationMessage\n\n const ImagePreview = props.renderPreview ?? DefaultImageInputPreview\n\n const accept = props.accept ?? 'image/*'\n\n const emptyState = props.emptyState ?? (\n <div className=\"px-6 py-12\">\n <Icon name=\"photo\" className=\"h-12 w-12 mx-auto mb-3 text-neutral-400\" />\n <p className=\"text-sm text-neutral-600 font-medium\">Click to upload or drag and drop</p>\n <p className=\"text-xs text-neutral-500 mt-1\">{accept}</p>\n {props.maxSize ? <p className=\"text-xs text-neutral-500 mt-1\">Max size: {formatFileSize(props.maxSize)}</p> : null}\n </div>\n )\n\n return (\n <HeadlessFileInput\n value={value}\n onChange={(v) => {\n setValue(v)\n commitValidation()\n }}\n >\n <div className=\"relative\">\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} htmlFor={inputId} />\n\n <HeadlessFileInput.Preview>\n {(files) => {\n const hasFile = files.length > 0\n return (\n <div\n className={tw(\n 'image-input-border relative block w-full rounded-md overflow-hidden text-center focus:outline-none focus-within:ring-2 focus-within:ring-primary-500 focus-within:ring-offset-2',\n 'border-2 border-neutral-200 hover:border-neutral-300 min-h-56',\n !hasFile && 'border-dashed',\n props.isDisabled && 'border-neutral-100 cursor-not-allowed',\n hasWarning &&\n 'border-notice-300 hover:border-notice-400 text-notice-900 placeholder-notice-300 focus-within:border-notice-500 focus-within:ring-notice-500',\n isInvalid &&\n 'border-negative-300 hover:border-negative-400 text-negative-900 placeholder-negative-300 focus-within:border-negative-500 focus-within:ring-negative-500'\n )}\n >\n {hasFile ? (\n ImagePreview({\n isDisabled: !!props.isDisabled,\n fileUrl: files[0]!.fileUrl,\n onChange: (v) => {\n setValue(v)\n commitValidation()\n },\n inputId,\n inputRef,\n name: props.name,\n accept\n })\n ) : (\n <HeadlessFileInput.DropZone\n inputRef={inputRef}\n accept={accept}\n name={props.name}\n inputId={inputId}\n disabled={props.isDisabled}\n className=\"size-full\"\n >\n {({ dragOver }) => (\n <div className=\"relative pointer-events-none\">\n {emptyState}\n {dragOver ? (\n <div className=\"absolute inset-0\">\n <DragOverContent />\n </div>\n ) : null}\n </div>\n )}\n </HeadlessFileInput.DropZone>\n )}\n </div>\n )\n }}\n </HeadlessFileInput.Preview>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={errorMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n </HeadlessFileInput>\n )\n}\n\nfunction DragOverContent() {\n return (\n <div\n className=\"absolute inset-0 p-12 rounded flex items-center justify-center\"\n style={{\n background: `repeating-linear-gradient(45deg, #e5e7eb, #e5e7eb 20px, #d1d5db 20px, #d1d5db 50px)`\n }}\n >\n <div>\n <Icon name=\"cloud-arrow-up\" className=\"h-12 w-12 mx-auto text-gray-400\" />\n <span className=\"mt-2 block text-sm font-semibold text-gray-900\">Upload an Image</span>\n </div>\n </div>\n )\n}\n\nexport function DefaultImageInputPreview({\n name,\n accept,\n fileUrl,\n inputRef,\n isDisabled,\n onChange,\n inputId\n}: ImageInputPreviewProps) {\n return (\n <>\n <HeadlessFileInput.DropZone\n inputRef={inputRef}\n accept={accept ?? 'image/*'}\n name={name}\n inputId={inputId}\n disabled={isDisabled}\n >\n {({ dragOver }) => (\n <div className=\"min-h-56 pointer-events-none\">\n {dragOver ? (\n <div className=\"absolute inset-0\">\n <DragOverContent />\n </div>\n ) : null}\n <div className=\"relative\">\n <div\n aria-hidden=\"true\"\n className=\"absolute inset-x-0 top-0 rounded-t-md h-20 bg-gradient-to-b from-white opacity-75 mix-blend-lighten\"\n />\n <img src={fileUrl} alt=\"Preview\" className=\"w-full object-cover rounded-md\" />\n </div>\n </div>\n )}\n </HeadlessFileInput.DropZone>\n <div className=\"absolute top-2 right-2\">\n {!isDisabled ? (\n <button\n type=\"button\"\n aria-label=\"Remove\"\n onClick={() => onChange(null)}\n className=\"p-1 h-5 w-5 flex items-center justify-center bg-gray-800/60 hover:bg-gray-500/60 rounded-full transition-colors\"\n >\n <Icon name=\"x-mark\" className=\"h-4 w-4 text-gray-200\" />\n </button>\n ) : null}\n </div>\n </>\n )\n}\n"},{"name":"components/headless-file-input.tsx","content":"import React from 'react'\nimport { useComposedRefs } from '../utils/compose-refs'\n\nexport type HeadlessFileInputProps = {\n children?: React.ReactNode\n} & (\n | {\n multiple: true\n value?: (string | File)[]\n defaultValue?: (string | File)[]\n onChange?(value: (string | File)[]): unknown\n }\n | {\n multiple?: false\n value?: string | File | null\n defaultValue?: string | File | null\n onChange?(value: string | File | null): unknown\n }\n)\n\ntype FileInputContext =\n | { multiple?: false; value: File | string | null; onChange(value: File | string | null): unknown }\n | { multiple: true; value: (File | string)[]; onChange(value: (File | string)[]): unknown }\n\nconst fileInputContext = React.createContext<FileInputContext>({\n multiple: false,\n value: null,\n onChange: () => null\n})\n\nfunction FileInputContainer({ multiple, value, defaultValue, onChange, children }: HeadlessFileInputProps) {\n const controlled = useControlled({ multiple, value, defaultValue, onChange }) as FileInputContext\n return <fileInputContext.Provider value={controlled}>{children}</fileInputContext.Provider>\n}\n\nFileInputContainer.displayName = 'HeadlessFileInput'\n\nconst FileInputDropZone = React.forwardRef<\n HTMLLabelElement,\n {\n name?: string\n children: ((props: { dragOver: boolean }) => React.ReactElement) | React.ReactElement\n inputId?: string\n accept?: string | undefined\n onDragChange?(value: boolean): unknown\n capture?: React.InputHTMLAttributes<'file'>['capture']\n disabled?: React.InputHTMLAttributes<'file'>['disabled']\n processFile?: (file: File) => Promise<File>\n inputRef?: React.Ref<HTMLInputElement>\n } & Omit<React.ComponentProps<'label'>, 'children'>\n>(({ children, inputId, inputRef, onDragChange, name, accept, capture, disabled, processFile, ...props }, ref) => {\n const { onChange, value, multiple } = React.useContext(fileInputContext)\n const [dragOver, setDragOver] = React.useState(false)\n const startDrag = React.useCallback(\n (e: React.DragEvent<HTMLInputElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(true)\n onDragChange && onDragChange(true)\n },\n [setDragOver, onDragChange]\n )\n const stopDrag = React.useCallback(\n (e: React.DragEvent<HTMLInputElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(false)\n onDragChange && onDragChange(false)\n },\n [setDragOver, onDragChange]\n )\n\n const childrenResult = typeof children === 'function' ? children({ dragOver }) : children\n const childProps = childrenResult.props as Record<string, unknown>\n const cloned = React.cloneElement(\n childrenResult,\n {\n ...childProps\n },\n [\n ...React.Children.toArray(childProps.children as React.ReactNode),\n <FileInputInput\n key=\"file-input-hidden\"\n id={inputId}\n name={name}\n accept={accept}\n ref={inputRef}\n style={{ width: '0.1px', height: '0.1px', opacity: 0, overflow: 'hidden', position: 'absolute', zIndex: -10 }}\n capture={capture}\n disabled={disabled}\n processFile={processFile}\n />\n ]\n )\n\n return (\n <label {...props} ref={ref}>\n <div\n onDrag={cancel}\n onDragStart={cancel}\n onDragOver={cancel}\n onDragEnter={startDrag}\n onDragLeave={stopDrag}\n onDrop={(e) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(false)\n onDragChange && onDragChange(false)\n if (e.dataTransfer.files.length) {\n if (multiple) {\n onChange([...value, ...Array.from(e.dataTransfer.files)])\n } else {\n onChange(e.dataTransfer.files.item(0) || null)\n }\n } else {\n const uri = e.dataTransfer.getData('text/uri-list')\n if (uri) {\n fetchFileFromUri(uri).then((file) => {\n if (multiple) {\n onChange([...value, file])\n } else {\n onChange(file)\n }\n })\n }\n }\n }}\n >\n {cloned}\n </div>\n </label>\n )\n})\nFileInputDropZone.displayName = 'FileInputDropZone'\n\nconst FileInputInput = React.forwardRef<\n HTMLInputElement,\n React.ComponentPropsWithoutRef<'input'> & { processFile?: (file: File) => Promise<File> }\n>(({ name, processFile, form, ...props }, refProp) => {\n const { multiple, value, onChange } = React.useContext(fileInputContext)\n const localRef = React.useRef<HTMLInputElement>(null)\n const ref = useComposedRefs(localRef, refProp)\n\n React.useEffect(() => {\n if (localRef.current) {\n const dt = new DataTransfer()\n if (Array.isArray(value)) {\n value.forEach(async (v) => v instanceof File && dt.items.add(v))\n } else if (value && value instanceof File) {\n dt.items.add(value)\n }\n localRef.current.files = dt.files\n }\n }, [value, processFile])\n\n const urls = React.useMemo(() => {\n if (Array.isArray(value)) {\n return value.map((v, i) => (typeof v === 'string' ? { name: `${name}.${i}`, value: v } : null)).filter(Boolean) as {\n name: string\n value: string\n }[]\n } else if (value && typeof value === 'string') {\n return [{ name: name, value: value }]\n } else {\n return []\n }\n }, [value, name])\n\n const hasFiles =\n typeof value !== 'string' &&\n !!value &&\n !(Array.isArray(value) && !value.filter((v) => v && typeof v !== 'string').length)\n const hasUrls = !!urls.length\n\n return (\n <>\n <input\n {...props}\n type=\"file\"\n form={form}\n ref={hasFiles ? ref : undefined}\n name={hasFiles ? name : undefined}\n onChange={async (e) => {\n const newValue = (\n multiple\n ? e.currentTarget.files\n ? [\n ...value,\n ...(await Promise.all(\n Array.from(e.currentTarget.files).map(async (file) => (processFile ? processFile(file) : file))\n ))\n ]\n : []\n : processFile && e.currentTarget.files?.item(0)\n ? await processFile(e.currentTarget.files?.item(0) as File)\n : e.currentTarget.files?.item(0)\n ) as any\n\n onChange(newValue)\n }}\n multiple={multiple}\n />\n {urls.map((url, i) => (\n <input\n type=\"hidden\"\n ref={i === 0 && !hasFiles ? ref : undefined}\n form={form}\n value={url.value}\n name={url.name}\n key={url.name}\n />\n ))}\n {!hasUrls && !hasFiles ? <input type=\"hidden\" ref={ref} form={form} name={name} value=\"\" /> : null}\n </>\n )\n})\nFileInputInput.displayName = 'FileInputInput'\n\nfunction FilePreview({\n children\n}: {\n children(\n props: {\n file: File | null\n fileUrl: string\n remove(): unknown\n replace(newFile: File | string): unknown\n }[]\n ): React.ReactNode\n}) {\n const { value, onChange } = React.useContext(fileInputContext)\n const urlMap = useFileUrls(value)\n const removeSingle = React.useCallback(() => onChange(null as any), [onChange])\n const replaceSingle = React.useCallback((file: File | string) => onChange(file as any), [onChange])\n\n const removeMultiple = React.useCallback(\n (index: number) => onChange((value as File[]).filter((f, i) => i !== index) as any),\n [value, onChange]\n )\n const replaceMultiple = React.useCallback(\n (index: number, file: File | string) =>\n onChange((value as (File | string)[]).map((f, i) => (i === index ? file : f)) as any),\n [value, onChange]\n )\n\n if (!value) {\n return <>{children([])}</>\n } else if (Array.isArray(value)) {\n return (\n <>\n {children(\n value.map((v, i) => ({\n file: typeof v === 'string' ? null : v,\n fileUrl: urlMap.get(v) ?? (typeof v === 'string' ? v : ''),\n remove: () => removeMultiple(i),\n replace: (f) => replaceMultiple(i, f)\n }))\n )}\n </>\n )\n } else {\n return (\n <>\n {children([\n {\n file: typeof value === 'string' ? null : value,\n fileUrl: urlMap.get(value) ?? (typeof value === 'string' ? value : ''),\n remove: removeSingle,\n replace: replaceSingle\n }\n ])}\n </>\n )\n }\n}\n\nexport const HeadlessFileInput = Object.assign(FileInputContainer, {\n Input: FileInputInput,\n Preview: FilePreview,\n DropZone: FileInputDropZone\n})\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction cancel(e: React.SyntheticEvent) {\n e.stopPropagation()\n e.preventDefault()\n}\n\nfunction useControlled<T extends string | File | null | (string | File)[]>({\n defaultValue,\n value,\n onChange: onChangeRaw,\n multiple\n}: {\n value?: T\n defaultValue?: T\n onChange?(value: null | string | File | (string | File)[]): unknown\n multiple?: boolean\n}) {\n const [state, setState] = React.useState(defaultValue)\n\n const onChange = React.useCallback(\n (v: T) => {\n setState(v)\n onChangeRaw && onChangeRaw(v)\n },\n [onChangeRaw, setState]\n )\n\n const proposed = value === undefined ? state : value\n\n return { value: !proposed && multiple ? [] : proposed || null, onChange, multiple: !!multiple }\n}\n\nfunction useFileUrls(value: (File | string)[] | File | string | null): Map<File | string, string> {\n const urlMapRef = React.useRef(new Map<File | string, string>())\n\n const items = value === null ? [] : Array.isArray(value) ? value : [value]\n\n // Create blob URLs for new items only\n for (const item of items) {\n if (!urlMapRef.current.has(item)) {\n urlMapRef.current.set(item, typeof item === 'string' ? item : URL.createObjectURL(item))\n }\n }\n\n // Prune stale entries and revoke their blob URLs\n const currentKeys = new Set(items)\n React.useEffect(() => {\n for (const [key, url] of urlMapRef.current) {\n if (!currentKeys.has(key)) {\n if (typeof key !== 'string') URL.revokeObjectURL(url)\n urlMapRef.current.delete(key)\n }\n }\n })\n\n // Revoke all blob URLs on unmount\n React.useEffect(() => {\n const map = urlMapRef.current\n return () => {\n for (const [key, url] of map) {\n if (typeof key !== 'string') URL.revokeObjectURL(url)\n }\n map.clear()\n }\n }, [])\n\n return urlMapRef.current\n}\n\nasync function fetchFileFromUri(uri: string): Promise<File> {\n const res = await fetch(uri)\n const blob = await res.blob()\n const contentType = res.headers.get('content-type') || blob.type || ''\n const contentDisposition = res.headers.get('content-disposition')\n let filename = new URL(uri).pathname.split('/').pop() || 'file'\n const match = contentDisposition?.match(/filename\\*?=(?:UTF-8'')?[\"']?([^\"';\\n]+)/)\n if (match) filename = decodeURIComponent(match[1]!)\n return new File([blob], filename, { type: contentType })\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/compose-refs.ts","content":"import React from 'react'\n\ntype PossibleRef<T> = React.Ref<T> | undefined\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n if (typeof ref === 'function') {\n ref(value)\n } else if (ref !== null && ref !== undefined) {\n ;(ref as React.MutableRefObject<T>).current = value\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nexport function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n return (node: T) => refs.forEach((ref) => setRef(ref, node))\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nexport function useComposedRefs<T>(...refs: PossibleRef<T>[]) {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return React.useCallback(composeRefs(...refs), refs)\n}\n"},{"name":"utils/file-input.ts","content":"/**\n * Shared utilities for file-input components.\n */\n\nexport function formatFileSize(bytes: number) {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\nexport function checkFileType(file: File, accept: string): string | null {\n const acceptedTypes = accept.split(',').map((type) => type.trim())\n const fileType = file.type\n const fileExtension = `.${file.name.split('.').pop()?.toLowerCase() || ''}`\n\n const isValid = acceptedTypes.some((acceptType) => {\n if (acceptType.includes('/*')) {\n const prefix = acceptType.split('/')[0]\n return fileType.startsWith(`${prefix}/`)\n }\n if (acceptType.startsWith('.')) {\n return acceptType.toLowerCase() === fileExtension\n }\n return fileType === acceptType\n })\n\n return isValid ? null : `Unsupported file type. Supported types: ${acceptedTypes.join(', ')}`\n}\n\nexport function checkFileMaxSize(file: File, maxSize: number): string | null {\n if (file.size > maxSize) {\n return `File exceeded max size of ${formatFileSize(maxSize)}`\n }\n return null\n}\n\nexport function validateFileMaxSize(value: string | File | null, maxSize?: number): string | null {\n if (value instanceof File && maxSize) {\n return checkFileMaxSize(value, maxSize)\n }\n return null\n}\n\nexport function validateFileType(value: string | File | null, accept?: string): string | null {\n if (value instanceof File && accept) {\n return checkFileType(value, accept)\n }\n return null\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"multi-image-input","files":[{"name":"components/multi-image-input.tsx","content":"import * as React from 'react'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { formatFileSize, checkFileType, checkFileMaxSize } from '../utils/file-input'\nimport { useStableAccessor } from '../utils/use-stable-accessor'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { HeadlessFileInput } from './headless-file-input'\nimport { Icon } from './icon'\n\nexport type MultiImageInputProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n warningMessage?: React.ReactNode\n placeholder?: string\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<(string | File)[]>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management (File objects array or strings, held in memory until form submit)\n value?: (string | File)[]\n defaultValue?: (string | File)[]\n onChange?: (value: (string | File)[]) => void\n\n // Image specific\n accept?: string\n maxSize?: number // in bytes per image\n maxImages?: number\n aspectRatio?: number // width/height ratio\n columns?: number // Grid columns for display\n allowReordering?: boolean\n\n renderPreview?: (props: MultiImageInputPreviewProps) => React.ReactNode\n}\n\nexport interface MultiImageInputPreviewProps {\n fileUrl: string\n isDisabled: boolean\n index: number\n onChange(value: string | null | File): unknown\n}\n\nexport function MultiImageInput(props: MultiImageInputProps) {\n const inputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const inputId = React.useId()\n const labelRef = React.useRef<HTMLLabelElement>(null)\n\n const exceededMaxSizeFn = React.useCallback(\n (value: (string | File)[]) => {\n for (let i = 0; i < value.length; i++) {\n const v = value[i]\n if (v instanceof File && props.maxSize) {\n const error = checkFileMaxSize(v, props.maxSize)\n if (error) return `Image ${i + 1} exceeded max size of ${formatFileSize(props.maxSize)}`\n }\n }\n return null\n },\n [props.maxSize]\n )\n\n const formatNotSupportedFn = React.useCallback(\n (value: (string | File)[]) => {\n const accept = props.accept ?? 'image/*'\n for (const v of value) {\n if (v instanceof File) {\n const error = checkFileType(v, accept)\n if (error) return error\n }\n }\n return null\n },\n [props.accept]\n )\n\n // Controlled state management\n const [value, setValue] = HeadlessForm.useControlledState(props.value, props.defaultValue ?? [], (value) => {\n if (props.onChange) {\n props.onChange(value)\n }\n })\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate(files) {\n const exceededMaxSize = exceededMaxSizeFn(files)\n const formatNotSupported = formatNotSupportedFn(files)\n const exceededMaxImages = props.maxImages !== undefined && files.length > props.maxImages ? 'Too many images' : null\n const custom = [props.validate ? props.validate(files) : []].flat()\n\n const errors = [exceededMaxSize, formatNotSupported, exceededMaxImages, ...custom].filter(Boolean)\n return errors as string[]\n },\n value,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n labelRef.current?.focus()\n }\n },\n inputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n const errorMessage = validationMessage\n\n // Stable handlers via useStableAccessor to avoid re-creating on every render\n const getValue = useStableAccessor(value)\n const getSetValue = useStableAccessor(setValue)\n\n const handleReorder = React.useCallback(\n (fromIndex: number, toIndex: number) => {\n const newFiles = [...getValue()]\n const [movedFile] = newFiles.splice(fromIndex, 1)\n newFiles.splice(toIndex, 0, movedFile!)\n getSetValue()(newFiles)\n },\n [getValue, getSetValue]\n )\n\n const handleRemove = React.useCallback(\n (index: number) => {\n getSetValue()(getValue().filter((_: string | File, i: number) => i !== index))\n },\n [getValue, getSetValue]\n )\n\n const handleReplace = React.useCallback(\n (index: number, file: string | File) => {\n getSetValue()(getValue().map((f: string | File, i: number) => (i === index ? file : f)))\n },\n [getValue, getSetValue]\n )\n\n const getCommitValidation = useStableAccessor(commitValidation)\n const handleFileInputChange = React.useCallback(\n (v: (string | File)[]) => {\n getSetValue()(v)\n getCommitValidation()()\n },\n [getSetValue, getCommitValidation]\n )\n\n // Stable component reference\n const imagePreview = React.useMemo(() => props.renderPreview ?? DefaultImagePreview, [props.renderPreview])\n\n // Stable keys for grid items\n const fileKeyMapRef = React.useRef(new WeakMap<File, string>())\n const fileKeyCounterRef = React.useRef(0)\n const getFileKey = React.useCallback((item: string | File): string => {\n if (typeof item === 'string') return item\n let key = fileKeyMapRef.current.get(item)\n if (!key) {\n key = `file-${fileKeyCounterRef.current++}`\n fileKeyMapRef.current.set(item, key)\n }\n return key\n }, [])\n\n // Memoize grid style\n const gridStyle = React.useMemo(\n () => ({ gridTemplateColumns: `repeat(${props.columns ?? 4}, minmax(0, 1fr))` }),\n [props.columns]\n )\n\n return (\n <HeadlessFileInput value={value} onChange={handleFileInputChange} multiple>\n <div className=\"relative\">\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} htmlFor={inputId} />\n\n <HeadlessFileInput.Preview>\n {(files) => {\n const canAddMore = !props.maxImages || files.length < props.maxImages\n\n return (\n <>\n {/* Drop zone */}\n {canAddMore && (\n <HeadlessFileInput.DropZone\n accept={props.accept ?? 'image/*'}\n name={props.name}\n inputId={inputId}\n inputRef={inputRef}\n ref={labelRef}\n disabled={props.isDisabled}\n >\n {({ dragOver }) => (\n <div\n className={tw(\n 'relative flex flex-col items-center justify-center rounded-md border-2 border-dashed cursor-pointer transition-all duration-200 mb-2 p-8',\n 'focus:outline-none focus-within:ring-2 focus-within:ring-primary-500 focus-within:ring-offset-2 pointer-events-none',\n 'border-neutral-200 hover:border-neutral-300',\n props.isDisabled && 'border-neutral-100 cursor-not-allowed',\n hasWarning &&\n 'border-notice-300 hover:border-notice-400 text-notice-900 placeholder-notice-300 focus-within:border-notice-500 focus-within:ring-notice-500',\n isInvalid &&\n 'border-negative-300 hover:border-negative-400 text-negative-900 placeholder-negative-300 focus-within:border-negative-500 focus-within:ring-negative-500'\n )}\n >\n {dragOver ? (\n <div className=\"min-h-20\">\n <DragOverContent />\n </div>\n ) : (\n <div className=\"text-center\">\n <Icon name=\"cloud-arrow-up\" className=\"h-10 w-10 mx-auto mb-3 text-neutral-400\" />\n <p className=\"text-sm text-neutral-600 font-medium\">\n {props.placeholder || 'Drop images here or click to browse'}\n </p>\n {props.maxSize && (\n <p className=\"text-xs text-neutral-500 mt-1\">\n Max size: {formatFileSize(props.maxSize)} per image\n </p>\n )}\n {props.maxImages && (\n <p className=\"text-xs text-neutral-500 mt-1\">\n {files.length} / {props.maxImages} images uploaded\n </p>\n )}\n </div>\n )}\n </div>\n )}\n </HeadlessFileInput.DropZone>\n )}\n\n {/* Image previews grid */}\n {files.length > 0 && (\n <div className={tw('grid gap-3')} style={gridStyle}>\n {files.map((fileItem, index) => (\n <GridItem\n key={getFileKey(value[index]!)}\n fileUrl={fileItem.fileUrl}\n index={index}\n aspectRatio={props.aspectRatio ?? 1}\n isDisabled={props.isDisabled ?? false}\n allowReordering={props.allowReordering ?? false}\n onReorder={handleReorder}\n onRemove={handleRemove}\n onReplace={handleReplace}\n ImagePreview={imagePreview}\n />\n ))}\n </div>\n )}\n\n {/* Message when at max capacity */}\n {!canAddMore && (\n <div className=\"mt-3 text-sm text-neutral-500 text-center\">\n Maximum of {props.maxImages} images reached\n </div>\n )}\n </>\n )\n }}\n </HeadlessFileInput.Preview>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={errorMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n </HeadlessFileInput>\n )\n}\n\n// ── Memoized sub-components ─────────────────────────────────────────────────\n\nconst GridItem = React.memo(function GridItem({\n fileUrl,\n index,\n aspectRatio,\n isDisabled,\n allowReordering,\n onReorder,\n onRemove,\n onReplace,\n ImagePreview\n}: {\n fileUrl: string\n index: number\n aspectRatio: number\n isDisabled: boolean\n allowReordering: boolean\n onReorder: (fromIndex: number, toIndex: number) => void\n onRemove: (index: number) => void\n onReplace: (index: number, file: string | File) => void\n ImagePreview: (props: MultiImageInputPreviewProps) => React.ReactNode\n}) {\n const handleDragStart = React.useCallback(\n (e: React.DragEvent) => e.dataTransfer.setData('index', index.toString()),\n [index]\n )\n\n const handleDrop = React.useCallback(\n (e: React.DragEvent) => {\n e.preventDefault()\n const fromIndex = parseInt(e.dataTransfer.getData('index'))\n if (fromIndex !== index) onReorder(fromIndex, index)\n },\n [index, onReorder]\n )\n\n const handleChange = React.useCallback(\n (v: string | null | File) => {\n if (v === null) onRemove(index)\n else onReplace(index, v)\n },\n [index, onRemove, onReplace]\n )\n\n return (\n <div\n className=\"relative group overflow-hidden rounded-md\"\n style={{ aspectRatio }}\n draggable={!isDisabled && allowReordering}\n onDragStart={handleDragStart}\n onDragOver={preventDefaultHandler}\n onDrop={handleDrop}\n >\n <ImagePreview isDisabled={isDisabled} fileUrl={fileUrl} index={index} onChange={handleChange} />\n </div>\n )\n})\n\nconst DefaultImagePreview = React.memo(function DefaultImagePreview({\n fileUrl,\n isDisabled,\n onChange\n}: MultiImageInputPreviewProps) {\n return (\n <>\n <img src={fileUrl} alt=\"Preview\" className=\"w-full h-full object-cover rounded-md\" />\n {!isDisabled && (\n <button\n type=\"button\"\n aria-label=\"Remove image\"\n onClick={() => onChange(null)}\n className=\"absolute top-1 right-1 p-1 bg-black/50 hover:bg-red-600 rounded-full transition-all duration-200 hover:scale-110\"\n >\n <Icon name=\"x-mark\" className=\"h-4 w-4 text-white\" />\n </button>\n )}\n </>\n )\n})\n\n// ── Static helpers ──────────────────────────────────────────────────────────\n\nconst DRAG_OVER_STYLE = {\n background: 'repeating-linear-gradient(45deg, #e5e7eb, #e5e7eb 20px, #d1d5db 20px, #d1d5db 50px)'\n} as const\n\nfunction DragOverContent() {\n return (\n <div className=\"absolute inset-0 p-6 rounded-md flex items-center justify-center text-center\" style={DRAG_OVER_STYLE}>\n <div>\n <Icon name=\"cloud-arrow-up\" className=\"h-12 w-12 mx-auto text-gray-400\" />\n <span className=\"mt-2 block text-sm font-semibold text-gray-900\">Drop Image</span>\n </div>\n </div>\n )\n}\n\nfunction preventDefaultHandler(e: React.DragEvent) {\n e.preventDefault()\n}\n"},{"name":"components/headless-file-input.tsx","content":"import React from 'react'\nimport { useComposedRefs } from '../utils/compose-refs'\n\nexport type HeadlessFileInputProps = {\n children?: React.ReactNode\n} & (\n | {\n multiple: true\n value?: (string | File)[]\n defaultValue?: (string | File)[]\n onChange?(value: (string | File)[]): unknown\n }\n | {\n multiple?: false\n value?: string | File | null\n defaultValue?: string | File | null\n onChange?(value: string | File | null): unknown\n }\n)\n\ntype FileInputContext =\n | { multiple?: false; value: File | string | null; onChange(value: File | string | null): unknown }\n | { multiple: true; value: (File | string)[]; onChange(value: (File | string)[]): unknown }\n\nconst fileInputContext = React.createContext<FileInputContext>({\n multiple: false,\n value: null,\n onChange: () => null\n})\n\nfunction FileInputContainer({ multiple, value, defaultValue, onChange, children }: HeadlessFileInputProps) {\n const controlled = useControlled({ multiple, value, defaultValue, onChange }) as FileInputContext\n return <fileInputContext.Provider value={controlled}>{children}</fileInputContext.Provider>\n}\n\nFileInputContainer.displayName = 'HeadlessFileInput'\n\nconst FileInputDropZone = React.forwardRef<\n HTMLLabelElement,\n {\n name?: string\n children: ((props: { dragOver: boolean }) => React.ReactElement) | React.ReactElement\n inputId?: string\n accept?: string | undefined\n onDragChange?(value: boolean): unknown\n capture?: React.InputHTMLAttributes<'file'>['capture']\n disabled?: React.InputHTMLAttributes<'file'>['disabled']\n processFile?: (file: File) => Promise<File>\n inputRef?: React.Ref<HTMLInputElement>\n } & Omit<React.ComponentProps<'label'>, 'children'>\n>(({ children, inputId, inputRef, onDragChange, name, accept, capture, disabled, processFile, ...props }, ref) => {\n const { onChange, value, multiple } = React.useContext(fileInputContext)\n const [dragOver, setDragOver] = React.useState(false)\n const startDrag = React.useCallback(\n (e: React.DragEvent<HTMLInputElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(true)\n onDragChange && onDragChange(true)\n },\n [setDragOver, onDragChange]\n )\n const stopDrag = React.useCallback(\n (e: React.DragEvent<HTMLInputElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(false)\n onDragChange && onDragChange(false)\n },\n [setDragOver, onDragChange]\n )\n\n const childrenResult = typeof children === 'function' ? children({ dragOver }) : children\n const childProps = childrenResult.props as Record<string, unknown>\n const cloned = React.cloneElement(\n childrenResult,\n {\n ...childProps\n },\n [\n ...React.Children.toArray(childProps.children as React.ReactNode),\n <FileInputInput\n key=\"file-input-hidden\"\n id={inputId}\n name={name}\n accept={accept}\n ref={inputRef}\n style={{ width: '0.1px', height: '0.1px', opacity: 0, overflow: 'hidden', position: 'absolute', zIndex: -10 }}\n capture={capture}\n disabled={disabled}\n processFile={processFile}\n />\n ]\n )\n\n return (\n <label {...props} ref={ref}>\n <div\n onDrag={cancel}\n onDragStart={cancel}\n onDragOver={cancel}\n onDragEnter={startDrag}\n onDragLeave={stopDrag}\n onDrop={(e) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(false)\n onDragChange && onDragChange(false)\n if (e.dataTransfer.files.length) {\n if (multiple) {\n onChange([...value, ...Array.from(e.dataTransfer.files)])\n } else {\n onChange(e.dataTransfer.files.item(0) || null)\n }\n } else {\n const uri = e.dataTransfer.getData('text/uri-list')\n if (uri) {\n fetchFileFromUri(uri).then((file) => {\n if (multiple) {\n onChange([...value, file])\n } else {\n onChange(file)\n }\n })\n }\n }\n }}\n >\n {cloned}\n </div>\n </label>\n )\n})\nFileInputDropZone.displayName = 'FileInputDropZone'\n\nconst FileInputInput = React.forwardRef<\n HTMLInputElement,\n React.ComponentPropsWithoutRef<'input'> & { processFile?: (file: File) => Promise<File> }\n>(({ name, processFile, form, ...props }, refProp) => {\n const { multiple, value, onChange } = React.useContext(fileInputContext)\n const localRef = React.useRef<HTMLInputElement>(null)\n const ref = useComposedRefs(localRef, refProp)\n\n React.useEffect(() => {\n if (localRef.current) {\n const dt = new DataTransfer()\n if (Array.isArray(value)) {\n value.forEach(async (v) => v instanceof File && dt.items.add(v))\n } else if (value && value instanceof File) {\n dt.items.add(value)\n }\n localRef.current.files = dt.files\n }\n }, [value, processFile])\n\n const urls = React.useMemo(() => {\n if (Array.isArray(value)) {\n return value.map((v, i) => (typeof v === 'string' ? { name: `${name}.${i}`, value: v } : null)).filter(Boolean) as {\n name: string\n value: string\n }[]\n } else if (value && typeof value === 'string') {\n return [{ name: name, value: value }]\n } else {\n return []\n }\n }, [value, name])\n\n const hasFiles =\n typeof value !== 'string' &&\n !!value &&\n !(Array.isArray(value) && !value.filter((v) => v && typeof v !== 'string').length)\n const hasUrls = !!urls.length\n\n return (\n <>\n <input\n {...props}\n type=\"file\"\n form={form}\n ref={hasFiles ? ref : undefined}\n name={hasFiles ? name : undefined}\n onChange={async (e) => {\n const newValue = (\n multiple\n ? e.currentTarget.files\n ? [\n ...value,\n ...(await Promise.all(\n Array.from(e.currentTarget.files).map(async (file) => (processFile ? processFile(file) : file))\n ))\n ]\n : []\n : processFile && e.currentTarget.files?.item(0)\n ? await processFile(e.currentTarget.files?.item(0) as File)\n : e.currentTarget.files?.item(0)\n ) as any\n\n onChange(newValue)\n }}\n multiple={multiple}\n />\n {urls.map((url, i) => (\n <input\n type=\"hidden\"\n ref={i === 0 && !hasFiles ? ref : undefined}\n form={form}\n value={url.value}\n name={url.name}\n key={url.name}\n />\n ))}\n {!hasUrls && !hasFiles ? <input type=\"hidden\" ref={ref} form={form} name={name} value=\"\" /> : null}\n </>\n )\n})\nFileInputInput.displayName = 'FileInputInput'\n\nfunction FilePreview({\n children\n}: {\n children(\n props: {\n file: File | null\n fileUrl: string\n remove(): unknown\n replace(newFile: File | string): unknown\n }[]\n ): React.ReactNode\n}) {\n const { value, onChange } = React.useContext(fileInputContext)\n const urlMap = useFileUrls(value)\n const removeSingle = React.useCallback(() => onChange(null as any), [onChange])\n const replaceSingle = React.useCallback((file: File | string) => onChange(file as any), [onChange])\n\n const removeMultiple = React.useCallback(\n (index: number) => onChange((value as File[]).filter((f, i) => i !== index) as any),\n [value, onChange]\n )\n const replaceMultiple = React.useCallback(\n (index: number, file: File | string) =>\n onChange((value as (File | string)[]).map((f, i) => (i === index ? file : f)) as any),\n [value, onChange]\n )\n\n if (!value) {\n return <>{children([])}</>\n } else if (Array.isArray(value)) {\n return (\n <>\n {children(\n value.map((v, i) => ({\n file: typeof v === 'string' ? null : v,\n fileUrl: urlMap.get(v) ?? (typeof v === 'string' ? v : ''),\n remove: () => removeMultiple(i),\n replace: (f) => replaceMultiple(i, f)\n }))\n )}\n </>\n )\n } else {\n return (\n <>\n {children([\n {\n file: typeof value === 'string' ? null : value,\n fileUrl: urlMap.get(value) ?? (typeof value === 'string' ? value : ''),\n remove: removeSingle,\n replace: replaceSingle\n }\n ])}\n </>\n )\n }\n}\n\nexport const HeadlessFileInput = Object.assign(FileInputContainer, {\n Input: FileInputInput,\n Preview: FilePreview,\n DropZone: FileInputDropZone\n})\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction cancel(e: React.SyntheticEvent) {\n e.stopPropagation()\n e.preventDefault()\n}\n\nfunction useControlled<T extends string | File | null | (string | File)[]>({\n defaultValue,\n value,\n onChange: onChangeRaw,\n multiple\n}: {\n value?: T\n defaultValue?: T\n onChange?(value: null | string | File | (string | File)[]): unknown\n multiple?: boolean\n}) {\n const [state, setState] = React.useState(defaultValue)\n\n const onChange = React.useCallback(\n (v: T) => {\n setState(v)\n onChangeRaw && onChangeRaw(v)\n },\n [onChangeRaw, setState]\n )\n\n const proposed = value === undefined ? state : value\n\n return { value: !proposed && multiple ? [] : proposed || null, onChange, multiple: !!multiple }\n}\n\nfunction useFileUrls(value: (File | string)[] | File | string | null): Map<File | string, string> {\n const urlMapRef = React.useRef(new Map<File | string, string>())\n\n const items = value === null ? [] : Array.isArray(value) ? value : [value]\n\n // Create blob URLs for new items only\n for (const item of items) {\n if (!urlMapRef.current.has(item)) {\n urlMapRef.current.set(item, typeof item === 'string' ? item : URL.createObjectURL(item))\n }\n }\n\n // Prune stale entries and revoke their blob URLs\n const currentKeys = new Set(items)\n React.useEffect(() => {\n for (const [key, url] of urlMapRef.current) {\n if (!currentKeys.has(key)) {\n if (typeof key !== 'string') URL.revokeObjectURL(url)\n urlMapRef.current.delete(key)\n }\n }\n })\n\n // Revoke all blob URLs on unmount\n React.useEffect(() => {\n const map = urlMapRef.current\n return () => {\n for (const [key, url] of map) {\n if (typeof key !== 'string') URL.revokeObjectURL(url)\n }\n map.clear()\n }\n }, [])\n\n return urlMapRef.current\n}\n\nasync function fetchFileFromUri(uri: string): Promise<File> {\n const res = await fetch(uri)\n const blob = await res.blob()\n const contentType = res.headers.get('content-type') || blob.type || ''\n const contentDisposition = res.headers.get('content-disposition')\n let filename = new URL(uri).pathname.split('/').pop() || 'file'\n const match = contentDisposition?.match(/filename\\*?=(?:UTF-8'')?[\"']?([^\"';\\n]+)/)\n if (match) filename = decodeURIComponent(match[1]!)\n return new File([blob], filename, { type: contentType })\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/compose-refs.ts","content":"import React from 'react'\n\ntype PossibleRef<T> = React.Ref<T> | undefined\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n if (typeof ref === 'function') {\n ref(value)\n } else if (ref !== null && ref !== undefined) {\n ;(ref as React.MutableRefObject<T>).current = value\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nexport function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n return (node: T) => refs.forEach((ref) => setRef(ref, node))\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nexport function useComposedRefs<T>(...refs: PossibleRef<T>[]) {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return React.useCallback(composeRefs(...refs), refs)\n}\n"},{"name":"utils/file-input.ts","content":"/**\n * Shared utilities for file-input components.\n */\n\nexport function formatFileSize(bytes: number) {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\nexport function checkFileType(file: File, accept: string): string | null {\n const acceptedTypes = accept.split(',').map((type) => type.trim())\n const fileType = file.type\n const fileExtension = `.${file.name.split('.').pop()?.toLowerCase() || ''}`\n\n const isValid = acceptedTypes.some((acceptType) => {\n if (acceptType.includes('/*')) {\n const prefix = acceptType.split('/')[0]\n return fileType.startsWith(`${prefix}/`)\n }\n if (acceptType.startsWith('.')) {\n return acceptType.toLowerCase() === fileExtension\n }\n return fileType === acceptType\n })\n\n return isValid ? null : `Unsupported file type. Supported types: ${acceptedTypes.join(', ')}`\n}\n\nexport function checkFileMaxSize(file: File, maxSize: number): string | null {\n if (file.size > maxSize) {\n return `File exceeded max size of ${formatFileSize(maxSize)}`\n }\n return null\n}\n\nexport function validateFileMaxSize(value: string | File | null, maxSize?: number): string | null {\n if (value instanceof File && maxSize) {\n return checkFileMaxSize(value, maxSize)\n }\n return null\n}\n\nexport function validateFileType(value: string | File | null, accept?: string): string | null {\n if (value instanceof File && accept) {\n return checkFileType(value, accept)\n }\n return null\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"pdf-input","files":[{"name":"components/pdf-input.tsx","content":"import * as React from 'react'\nimport { HeadlessForm } from '@maestro-js/form'\nimport type { ValidationFunction, ValidationResult } from '@maestro-js/form'\nimport { tw } from '../utils/tw'\nimport { formatFileSize, checkFileType, checkFileMaxSize } from '../utils/file-input'\nimport { FormFieldComponents } from './helpers/form-field'\nimport { HeadlessFileInput } from './headless-file-input'\nimport { Icon } from './icon'\nimport { Pdf } from './pdf'\n\nexport type PdfInputProps = {\n // Form identification\n name?: string\n form?: string\n\n // Display elements\n label?: React.ReactNode\n cornerHint?: React.ReactNode\n helpText?: React.ReactNode\n warningMessage?: React.ReactNode\n emptyState?: React.ReactNode\n renderPreview?: (props: PdfInputPreviewProps) => React.ReactNode\n\n // Validation and state\n isRequired?: boolean\n isDisabled?: boolean\n isInvalid?: boolean\n validate?: ValidationFunction<string | File | null>\n errorMessage?: React.ReactNode | ((v: ValidationResult) => React.ReactNode)\n\n // Value management (File object or string URL, held in memory until form submit)\n value?: string | File | null\n defaultValue?: string | File | null\n onChange?: (value: string | File | null) => void\n\n maxSize?: number // in bytes\n}\n\nexport interface PdfInputPreviewProps {\n fileUrl: string\n isDisabled: boolean\n onChange(value: string | null | File): unknown\n inputId: string\n name: string | undefined\n inputRef: React.Ref<HTMLInputElement>\n}\n\nexport function PdfInput(props: PdfInputProps) {\n const inputRef = React.useRef<HTMLInputElement>(null)\n const helpTextId = React.useId()\n const inputId = React.useId()\n\n const exceededMaxSizeFn = React.useCallback(\n (value: string | File | null) => {\n if (value && props.maxSize && value instanceof File) {\n return checkFileMaxSize(value, props.maxSize)\n }\n return null\n },\n [props.maxSize]\n )\n\n const formatNotSupportedFn = React.useCallback((value: string | File | null) => {\n if (value instanceof File) {\n return checkFileType(value, 'application/pdf')\n }\n return null\n }, [])\n\n // Controlled state management\n const [value, setValue] = HeadlessForm.useControlledState(props.value, props.defaultValue ?? null, (value) => {\n if (props.onChange) {\n props.onChange(value)\n }\n })\n\n const exceededMaxSize = React.useMemo(() => exceededMaxSizeFn(value), [value, exceededMaxSizeFn])\n const formatNotSupported = React.useMemo(() => formatNotSupportedFn(value), [value, formatNotSupportedFn])\n\n // Form validation\n const {\n validationMessage,\n isInvalid: validationInvalid,\n commitValidation\n } = HeadlessForm.useValidation(\n {\n validate: props.validate,\n value,\n name: props.name,\n isRequired: props.isRequired,\n form: props.form,\n focus() {\n inputRef.current?.focus()\n }\n },\n inputRef\n )\n\n const isInvalid = props.isInvalid || validationInvalid || Boolean(exceededMaxSize || formatNotSupported)\n const hasWarning = Boolean(props.warningMessage) && !isInvalid\n const errorMessage = exceededMaxSize || formatNotSupported || validationMessage\n\n const PdfPreview = props.renderPreview ?? DefaultPdfInputPreview\n\n const emptyState = props.emptyState ?? (\n <div className=\"px-6 py-12\">\n <Icon name=\"document\" className=\"h-12 w-12 mx-auto mb-3 text-neutral-400\" />\n <p className=\"text-sm text-neutral-600 font-medium\">Click to upload or drag and drop</p>\n <p className=\"text-xs text-neutral-500 mt-1\">PDF</p>\n {props.maxSize ? <p className=\"text-xs text-neutral-500 mt-1\">Max size: {formatFileSize(props.maxSize)}</p> : null}\n </div>\n )\n\n return (\n <HeadlessFileInput\n value={value}\n onChange={(v) => {\n setValue(v)\n commitValidation()\n }}\n >\n <div className=\"relative\">\n <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} htmlFor={inputId} />\n\n <HeadlessFileInput.Preview>\n {(files) => {\n const hasFile = files.length > 0\n return (\n <div\n className={tw(\n 'pdf-input-border relative block w-full rounded-md overflow-hidden text-center focus:outline-none focus-within:ring-2 focus-within:ring-primary-500 focus-within:ring-offset-2',\n 'border-2 border-neutral-200 hover:border-neutral-300 min-h-56',\n !hasFile && 'border-dashed',\n props.isDisabled && 'border-neutral-100 cursor-not-allowed',\n hasWarning &&\n 'border-notice-300 hover:border-notice-400 text-notice-900 placeholder-notice-300 focus-within:border-notice-500 focus-within:ring-notice-500',\n isInvalid &&\n 'border-negative-300 hover:border-negative-400 text-negative-900 placeholder-negative-300 focus-within:border-negative-500 focus-within:ring-negative-500'\n )}\n >\n {hasFile ? (\n PdfPreview({\n isDisabled: !!props.isDisabled,\n fileUrl: files[0]!.fileUrl,\n onChange: (v) => {\n setValue(v)\n commitValidation()\n },\n inputId,\n inputRef,\n name: props.name\n })\n ) : (\n <HeadlessFileInput.DropZone\n inputRef={inputRef}\n accept=\"application/pdf\"\n name={props.name}\n inputId={inputId}\n disabled={props.isDisabled}\n className=\"size-full\"\n >\n {({ dragOver }) => (\n <div className=\"relative pointer-events-none\">\n {emptyState}\n {dragOver ? (\n <div className=\"absolute inset-0\">\n <DragOverContent />\n </div>\n ) : null}\n </div>\n )}\n </HeadlessFileInput.DropZone>\n )}\n </div>\n )\n }}\n </HeadlessFileInput.Preview>\n\n <FormFieldComponents.FormFieldHelpText\n id={helpTextId}\n isInvalid={isInvalid}\n validationMessage={errorMessage}\n hasWarning={hasWarning}\n warningMessage={props.warningMessage}\n helpText={props.helpText}\n />\n </div>\n </HeadlessFileInput>\n )\n}\n\nfunction DragOverContent() {\n return (\n <div\n className=\"absolute inset-0 p-12 rounded flex items-center justify-center\"\n style={{\n background: `repeating-linear-gradient(45deg, #e5e7eb, #e5e7eb 20px, #d1d5db 20px, #d1d5db 50px)`\n }}\n >\n <div>\n <Icon name=\"cloud-arrow-up\" className=\"h-12 w-12 mx-auto text-gray-400\" />\n <span className=\"mt-2 block text-sm font-semibold text-gray-900\">Upload a PDF</span>\n </div>\n </div>\n )\n}\n\nexport function DefaultPdfInputPreview({ name, fileUrl, inputRef, isDisabled, onChange, inputId }: PdfInputPreviewProps) {\n return (\n <>\n <HeadlessFileInput.DropZone\n inputRef={inputRef}\n accept=\"application/pdf\"\n name={name}\n inputId={inputId}\n disabled={isDisabled}\n >\n {({ dragOver }) => (\n <div className=\"min-h-56 pointer-events-none\">\n {dragOver ? (\n <div className=\"absolute inset-0\">\n <DragOverContent />\n </div>\n ) : null}\n <div className=\"relative\">\n <div\n aria-hidden=\"true\"\n className=\"absolute inset-x-0 top-0 rounded-t-md h-20 bg-gradient-to-b from-white opacity-75 mix-blend-lighten\"\n />\n <Pdf src={fileUrl} className=\"w-full min-h-96 rounded-md border-0\" />\n </div>\n </div>\n )}\n </HeadlessFileInput.DropZone>\n <div className=\"absolute top-2 right-2\">\n {!isDisabled ? (\n <button\n type=\"button\"\n aria-label=\"Remove\"\n onClick={() => onChange(null)}\n className=\"p-1 h-5 w-5 flex items-center justify-center bg-gray-800/60 hover:bg-gray-500/60 rounded-full transition-colors\"\n >\n <Icon name=\"x-mark\" className=\"h-4 w-4 text-gray-200\" />\n </button>\n ) : null}\n </div>\n </>\n )\n}\n"},{"name":"components/headless-file-input.tsx","content":"import React from 'react'\nimport { useComposedRefs } from '../utils/compose-refs'\n\nexport type HeadlessFileInputProps = {\n children?: React.ReactNode\n} & (\n | {\n multiple: true\n value?: (string | File)[]\n defaultValue?: (string | File)[]\n onChange?(value: (string | File)[]): unknown\n }\n | {\n multiple?: false\n value?: string | File | null\n defaultValue?: string | File | null\n onChange?(value: string | File | null): unknown\n }\n)\n\ntype FileInputContext =\n | { multiple?: false; value: File | string | null; onChange(value: File | string | null): unknown }\n | { multiple: true; value: (File | string)[]; onChange(value: (File | string)[]): unknown }\n\nconst fileInputContext = React.createContext<FileInputContext>({\n multiple: false,\n value: null,\n onChange: () => null\n})\n\nfunction FileInputContainer({ multiple, value, defaultValue, onChange, children }: HeadlessFileInputProps) {\n const controlled = useControlled({ multiple, value, defaultValue, onChange }) as FileInputContext\n return <fileInputContext.Provider value={controlled}>{children}</fileInputContext.Provider>\n}\n\nFileInputContainer.displayName = 'HeadlessFileInput'\n\nconst FileInputDropZone = React.forwardRef<\n HTMLLabelElement,\n {\n name?: string\n children: ((props: { dragOver: boolean }) => React.ReactElement) | React.ReactElement\n inputId?: string\n accept?: string | undefined\n onDragChange?(value: boolean): unknown\n capture?: React.InputHTMLAttributes<'file'>['capture']\n disabled?: React.InputHTMLAttributes<'file'>['disabled']\n processFile?: (file: File) => Promise<File>\n inputRef?: React.Ref<HTMLInputElement>\n } & Omit<React.ComponentProps<'label'>, 'children'>\n>(({ children, inputId, inputRef, onDragChange, name, accept, capture, disabled, processFile, ...props }, ref) => {\n const { onChange, value, multiple } = React.useContext(fileInputContext)\n const [dragOver, setDragOver] = React.useState(false)\n const startDrag = React.useCallback(\n (e: React.DragEvent<HTMLInputElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(true)\n onDragChange && onDragChange(true)\n },\n [setDragOver, onDragChange]\n )\n const stopDrag = React.useCallback(\n (e: React.DragEvent<HTMLInputElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(false)\n onDragChange && onDragChange(false)\n },\n [setDragOver, onDragChange]\n )\n\n const childrenResult = typeof children === 'function' ? children({ dragOver }) : children\n const childProps = childrenResult.props as Record<string, unknown>\n const cloned = React.cloneElement(\n childrenResult,\n {\n ...childProps\n },\n [\n ...React.Children.toArray(childProps.children as React.ReactNode),\n <FileInputInput\n key=\"file-input-hidden\"\n id={inputId}\n name={name}\n accept={accept}\n ref={inputRef}\n style={{ width: '0.1px', height: '0.1px', opacity: 0, overflow: 'hidden', position: 'absolute', zIndex: -10 }}\n capture={capture}\n disabled={disabled}\n processFile={processFile}\n />\n ]\n )\n\n return (\n <label {...props} ref={ref}>\n <div\n onDrag={cancel}\n onDragStart={cancel}\n onDragOver={cancel}\n onDragEnter={startDrag}\n onDragLeave={stopDrag}\n onDrop={(e) => {\n e.preventDefault()\n e.stopPropagation()\n setDragOver(false)\n onDragChange && onDragChange(false)\n if (e.dataTransfer.files.length) {\n if (multiple) {\n onChange([...value, ...Array.from(e.dataTransfer.files)])\n } else {\n onChange(e.dataTransfer.files.item(0) || null)\n }\n } else {\n const uri = e.dataTransfer.getData('text/uri-list')\n if (uri) {\n fetchFileFromUri(uri).then((file) => {\n if (multiple) {\n onChange([...value, file])\n } else {\n onChange(file)\n }\n })\n }\n }\n }}\n >\n {cloned}\n </div>\n </label>\n )\n})\nFileInputDropZone.displayName = 'FileInputDropZone'\n\nconst FileInputInput = React.forwardRef<\n HTMLInputElement,\n React.ComponentPropsWithoutRef<'input'> & { processFile?: (file: File) => Promise<File> }\n>(({ name, processFile, form, ...props }, refProp) => {\n const { multiple, value, onChange } = React.useContext(fileInputContext)\n const localRef = React.useRef<HTMLInputElement>(null)\n const ref = useComposedRefs(localRef, refProp)\n\n React.useEffect(() => {\n if (localRef.current) {\n const dt = new DataTransfer()\n if (Array.isArray(value)) {\n value.forEach(async (v) => v instanceof File && dt.items.add(v))\n } else if (value && value instanceof File) {\n dt.items.add(value)\n }\n localRef.current.files = dt.files\n }\n }, [value, processFile])\n\n const urls = React.useMemo(() => {\n if (Array.isArray(value)) {\n return value.map((v, i) => (typeof v === 'string' ? { name: `${name}.${i}`, value: v } : null)).filter(Boolean) as {\n name: string\n value: string\n }[]\n } else if (value && typeof value === 'string') {\n return [{ name: name, value: value }]\n } else {\n return []\n }\n }, [value, name])\n\n const hasFiles =\n typeof value !== 'string' &&\n !!value &&\n !(Array.isArray(value) && !value.filter((v) => v && typeof v !== 'string').length)\n const hasUrls = !!urls.length\n\n return (\n <>\n <input\n {...props}\n type=\"file\"\n form={form}\n ref={hasFiles ? ref : undefined}\n name={hasFiles ? name : undefined}\n onChange={async (e) => {\n const newValue = (\n multiple\n ? e.currentTarget.files\n ? [\n ...value,\n ...(await Promise.all(\n Array.from(e.currentTarget.files).map(async (file) => (processFile ? processFile(file) : file))\n ))\n ]\n : []\n : processFile && e.currentTarget.files?.item(0)\n ? await processFile(e.currentTarget.files?.item(0) as File)\n : e.currentTarget.files?.item(0)\n ) as any\n\n onChange(newValue)\n }}\n multiple={multiple}\n />\n {urls.map((url, i) => (\n <input\n type=\"hidden\"\n ref={i === 0 && !hasFiles ? ref : undefined}\n form={form}\n value={url.value}\n name={url.name}\n key={url.name}\n />\n ))}\n {!hasUrls && !hasFiles ? <input type=\"hidden\" ref={ref} form={form} name={name} value=\"\" /> : null}\n </>\n )\n})\nFileInputInput.displayName = 'FileInputInput'\n\nfunction FilePreview({\n children\n}: {\n children(\n props: {\n file: File | null\n fileUrl: string\n remove(): unknown\n replace(newFile: File | string): unknown\n }[]\n ): React.ReactNode\n}) {\n const { value, onChange } = React.useContext(fileInputContext)\n const urlMap = useFileUrls(value)\n const removeSingle = React.useCallback(() => onChange(null as any), [onChange])\n const replaceSingle = React.useCallback((file: File | string) => onChange(file as any), [onChange])\n\n const removeMultiple = React.useCallback(\n (index: number) => onChange((value as File[]).filter((f, i) => i !== index) as any),\n [value, onChange]\n )\n const replaceMultiple = React.useCallback(\n (index: number, file: File | string) =>\n onChange((value as (File | string)[]).map((f, i) => (i === index ? file : f)) as any),\n [value, onChange]\n )\n\n if (!value) {\n return <>{children([])}</>\n } else if (Array.isArray(value)) {\n return (\n <>\n {children(\n value.map((v, i) => ({\n file: typeof v === 'string' ? null : v,\n fileUrl: urlMap.get(v) ?? (typeof v === 'string' ? v : ''),\n remove: () => removeMultiple(i),\n replace: (f) => replaceMultiple(i, f)\n }))\n )}\n </>\n )\n } else {\n return (\n <>\n {children([\n {\n file: typeof value === 'string' ? null : value,\n fileUrl: urlMap.get(value) ?? (typeof value === 'string' ? value : ''),\n remove: removeSingle,\n replace: replaceSingle\n }\n ])}\n </>\n )\n }\n}\n\nexport const HeadlessFileInput = Object.assign(FileInputContainer, {\n Input: FileInputInput,\n Preview: FilePreview,\n DropZone: FileInputDropZone\n})\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction cancel(e: React.SyntheticEvent) {\n e.stopPropagation()\n e.preventDefault()\n}\n\nfunction useControlled<T extends string | File | null | (string | File)[]>({\n defaultValue,\n value,\n onChange: onChangeRaw,\n multiple\n}: {\n value?: T\n defaultValue?: T\n onChange?(value: null | string | File | (string | File)[]): unknown\n multiple?: boolean\n}) {\n const [state, setState] = React.useState(defaultValue)\n\n const onChange = React.useCallback(\n (v: T) => {\n setState(v)\n onChangeRaw && onChangeRaw(v)\n },\n [onChangeRaw, setState]\n )\n\n const proposed = value === undefined ? state : value\n\n return { value: !proposed && multiple ? [] : proposed || null, onChange, multiple: !!multiple }\n}\n\nfunction useFileUrls(value: (File | string)[] | File | string | null): Map<File | string, string> {\n const urlMapRef = React.useRef(new Map<File | string, string>())\n\n const items = value === null ? [] : Array.isArray(value) ? value : [value]\n\n // Create blob URLs for new items only\n for (const item of items) {\n if (!urlMapRef.current.has(item)) {\n urlMapRef.current.set(item, typeof item === 'string' ? item : URL.createObjectURL(item))\n }\n }\n\n // Prune stale entries and revoke their blob URLs\n const currentKeys = new Set(items)\n React.useEffect(() => {\n for (const [key, url] of urlMapRef.current) {\n if (!currentKeys.has(key)) {\n if (typeof key !== 'string') URL.revokeObjectURL(url)\n urlMapRef.current.delete(key)\n }\n }\n })\n\n // Revoke all blob URLs on unmount\n React.useEffect(() => {\n const map = urlMapRef.current\n return () => {\n for (const [key, url] of map) {\n if (typeof key !== 'string') URL.revokeObjectURL(url)\n }\n map.clear()\n }\n }, [])\n\n return urlMapRef.current\n}\n\nasync function fetchFileFromUri(uri: string): Promise<File> {\n const res = await fetch(uri)\n const blob = await res.blob()\n const contentType = res.headers.get('content-type') || blob.type || ''\n const contentDisposition = res.headers.get('content-disposition')\n let filename = new URL(uri).pathname.split('/').pop() || 'file'\n const match = contentDisposition?.match(/filename\\*?=(?:UTF-8'')?[\"']?([^\"';\\n]+)/)\n if (match) filename = decodeURIComponent(match[1]!)\n return new File([blob], filename, { type: contentType })\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/pdf.tsx","content":"import React from 'react'\nimport { usePdf } from 'react-pdf-hook'\nimport { tw } from '../utils/tw'\nimport { usePagination } from '../utils/use-pagination'\nimport { Icon } from './icon'\nimport pdfjs from './helpers/pdf-dist.client'\n\nexport interface PdfProps extends React.ComponentProps<'div'> {\n src: string\n onDownload?: () => void\n}\n\nexport function Pdf({ src, onDownload, className, ...props }: PdfProps) {\n const containerRef = React.useRef<HTMLDivElement>(null)\n const [pageNumber, setPageNumber] = React.useState(0)\n\n const { pdf, canvasRef } = usePdf(pdfjs, src, pageNumber + 1, { width: containerRef.current?.clientWidth })\n\n const numPages = pdf?.numPages\n\n return (\n <div ref={containerRef} className={tw('relative overflow-hidden border border-neutral-300', className)} {...props}>\n <canvas\n ref={canvasRef}\n style={{ display: !pdf ? 'none' : undefined, maxWidth: '100%' }}\n className={tw('pdf-page', onDownload && 'cursor-pointer')}\n onClick={onDownload}\n />\n {!pdf || !containerRef.current?.clientWidth ? <div className=\"w-full h-full m-auto rounded\" /> : null}\n {numPages && numPages > 1 ? (\n <div className=\"flex absolute bottom-5 w-full justify-center\">\n <Pagination page={pageNumber} setPageNumber={setPageNumber} count={numPages} />\n </div>\n ) : null}\n </div>\n )\n}\n\nPdf.displayName = 'Pdf'\n\nfunction Pagination({ page, count, setPageNumber }: { page: number; count: number; setPageNumber: (n: number) => void }) {\n const pages = usePagination({ currentPage: page, pageCount: count })\n\n return (\n <div className=\"inline-flex items-center gap-1 pointer-events-auto\">\n <button\n type=\"button\"\n onClick={() => setPageNumber(page > 0 ? page - 1 : 0)}\n disabled={page === 0}\n className=\"p-1 rounded bg-white border border-neutral-300 hover:bg-neutral-50 disabled:opacity-50 disabled:cursor-not-allowed\"\n aria-label=\"Previous page\"\n >\n <Icon name=\"chevron-left\" className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n {pages.map((p, i) => (\n <button\n type=\"button\"\n key={p.readonly ? `ellipsis-${i}` : p.label}\n onClick={() => setPageNumber(p.readonly ? page : (p.value as number))}\n disabled={p.readonly}\n className={tw(\n 'min-w-[28px] bg-white h-7 px-2 text-xs font-medium rounded border transition-colors',\n p.selected\n ? 'bg-primary-500 text-white border-primary-500'\n : p.readonly\n ? 'border-transparent cursor-default'\n : 'border-neutral-300 hover:bg-neutral-50'\n )}\n >\n {p.label}\n </button>\n ))}\n\n <button\n type=\"button\"\n onClick={() => setPageNumber(page < count - 1 ? page + 1 : page)}\n disabled={page === count - 1}\n className=\"p-1 rounded bg-white border border-neutral-300 hover:bg-neutral-50 disabled:opacity-50 disabled:cursor-not-allowed\"\n aria-label=\"Next page\"\n >\n <Icon name=\"chevron-right\" className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n </div>\n )\n}\n"},{"name":"components/helpers/form-field.tsx","content":"import * as React from 'react'\nimport {\n Button,\n Collection,\n Group,\n Header,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n type InputProps\n} from 'react-aria-components'\nimport { composeTailwindRenderProps, tw } from '../../utils/tw'\nimport { Icon } from '../icon'\n\nconst colorClasses = { neutral: 'text-neutral-500', negative: 'text-negative-600', notice: 'text-notice-600' } as const\n\ntype LabelProps = {\n as?: React.ElementType<React.ComponentPropsWithRef<'label'>>\n} & React.ComponentPropsWithRef<'label'>\n\nconst Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ as = 'label', ...props }, ref) => {\n return React.createElement(as, {\n ...props,\n ref,\n className: tw('block text-sm font-medium text-neutral-700', props.className)\n })\n})\nLabel.displayName = 'Label'\n\nfunction FormFieldHelpText(props: {\n id: string\n isInvalid: boolean\n validationMessage: React.ReactNode\n hasWarning: boolean\n warningMessage?: React.ReactNode\n helpText?: React.ReactNode\n UNSAFE_className?: string\n}) {\n const text = props.isInvalid ? props.validationMessage : props.hasWarning ? props.warningMessage : props.helpText\n const color = props.isInvalid ? 'negative' : props.hasWarning ? 'notice' : 'neutral'\n return text === null ? null : (\n <div\n id={props.id}\n className={tw('mt-1 text-sm', !text && 'h-5', colorClasses[color], 'text-wrap', props.UNSAFE_className)}\n >\n {text ?? ''}\n </div>\n )\n}\n\nfunction LabelRow(\n props: { label?: React.ReactNode; cornerHint?: React.ReactNode } & ({ labelId: string } | { htmlFor: string })\n): React.JSX.Element | null {\n if (!props.label) return null\n return (\n <div className=\"flex justify-between items-center mb-1\">\n {'labelId' in props ? (\n <Label id={props.labelId}>{props.label}</Label>\n ) : (\n <Label htmlFor={props.htmlFor}>{props.label}</Label>\n )}\n {props.cornerHint && <div className=\"text-sm text-neutral-500\">{props.cornerHint}</div>}\n </div>\n )\n}\n\ntype FieldGroupProps = {\n isDisabled?: boolean\n isReadonly?: boolean\n isInvalid?: boolean\n hasWarning?: boolean\n shape?: 'rectangle' | 'oval'\n className?: string\n children: React.ReactNode\n onClick?: React.MouseEventHandler<HTMLDivElement>\n}\n\nconst FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(\n ({ children, className, onClick, ...opts }, ref) => {\n const disabled = opts.isDisabled || opts.isReadonly\n const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'\n\n return (\n <Group\n ref={ref}\n className={tw(\n 'relative overflow-hidden border transition-all duration-200 max-w-96',\n 'focus-within:ring-1',\n shape,\n disabled && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',\n opts.isInvalid && 'border-negative-200 bg-negative-50 focus-within:border-negative-500 focus-within:ring-negative-500/20',\n opts.hasWarning &&\n 'border-notice-200 bg-notice-50 focus-within:border-notice-500 focus-within:ring-notice-500/20',\n !opts.isInvalid &&\n !opts.hasWarning &&\n !disabled &&\n 'border-neutral-200 hover:border-neutral-300 focus-within:border-primary-500 focus-within:ring-primary-500/20',\n className\n )}\n onClick={onClick}\n >\n {children}\n </Group>\n )\n }\n)\n\nconst FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {\n return (\n <Input\n {...props}\n ref={ref}\n className={composeTailwindRenderProps(\n props.className,\n tw(\n 'flex-1 bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',\n (props.disabled || props.readOnly) && 'cursor-not-allowed'\n )\n )}\n />\n )\n})\n\nfunction DropdownListBox<T extends object>(props: React.ComponentProps<typeof ListBox<T>>) {\n return (\n <ListBox<T>\n {...props}\n className={composeTailwindRenderProps(\n props.className,\n 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none max-h-60 overflow-y-auto'\n )}\n />\n )\n}\n\nfunction DropdownItem(props: {\n id: string\n textValue: string\n label: React.ReactNode\n secondary?: React.ReactNode\n isDisabled?: boolean\n color?: string | null\n showCheckIcon?: boolean\n customValuePrefix?: string\n className?: string\n}) {\n const isCustom = Boolean(props.customValuePrefix)\n const showCheck = props.showCheckIcon !== false && !isCustom\n\n return (\n <ListBoxItem\n id={props.id}\n textValue={props.textValue}\n isDisabled={props.isDisabled}\n className={({ isFocused, isSelected }) =>\n tw(\n isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-900 bg-white',\n 'relative cursor-pointer text-sm transition-colors duration-150',\n 'pr-2 pl-3 py-1.5 outline-none',\n props.isDisabled && 'opacity-50 cursor-not-allowed',\n isSelected ? (isFocused ? 'bg-primary-100' : 'bg-primary-50') : '',\n isCustom && 'italic',\n props.className\n )\n }\n >\n {({ isSelected }) => (\n <>\n {props.color && !isCustom ? (\n <div className=\"absolute left-0 top-0 w-[3px] h-full\" style={{ backgroundColor: props.color }} />\n ) : null}\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex-1 min-w-0\">\n <div className={tw(showCheck && isSelected ? 'font-medium' : 'font-normal', 'truncate')}>\n {isCustom ? `${props.customValuePrefix} \"${props.label}\"` : props.label}\n </div>\n {props.secondary && !isCustom && (\n <div className=\"text-xs mt-px truncate text-neutral-500\">{props.secondary}</div>\n )}\n </div>\n {showCheck && isSelected && (\n <div className=\"ml-2 w-fit shrink-0 flex items-center text-primary-500\">\n <Icon name=\"check\" className=\"h-4 w-4\" />\n </div>\n )}\n </div>\n </>\n )}\n </ListBoxItem>\n )\n}\n\nfunction DropdownSection<T extends object>(props: {\n title?: string\n items?: Iterable<T>\n children: React.ReactNode | ((item: T) => React.ReactNode)\n}) {\n return (\n <ListBoxSection>\n {props.title && (\n <Header className=\"sticky top-0 z-10 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-500 border-b border-neutral-100\">\n {props.title}\n </Header>\n )}\n <Collection items={props.items}>{props.children}</Collection>\n </ListBoxSection>\n )\n}\n\nfunction ChevronButton(props?: { className?: string }) {\n return (\n <Button className={tw('flex items-center px-2 py-2', props?.className)}>\n <Icon name=\"chevron-up-down\" className=\"h-4 w-4 text-neutral-400 transition-colors\" />\n </Button>\n )\n}\n\nexport const FormFieldComponents = {\n Label,\n FormFieldHelpText,\n LabelRow,\n FieldGroup,\n FieldInput,\n DropdownListBox,\n DropdownSection,\n DropdownItem,\n ChevronButton\n}\n"},{"name":"utils/compose-refs.ts","content":"import React from 'react'\n\ntype PossibleRef<T> = React.Ref<T> | undefined\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n if (typeof ref === 'function') {\n ref(value)\n } else if (ref !== null && ref !== undefined) {\n ;(ref as React.MutableRefObject<T>).current = value\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nexport function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n return (node: T) => refs.forEach((ref) => setRef(ref, node))\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nexport function useComposedRefs<T>(...refs: PossibleRef<T>[]) {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return React.useCallback(composeRefs(...refs), refs)\n}\n"},{"name":"utils/file-input.ts","content":"/**\n * Shared utilities for file-input components.\n */\n\nexport function formatFileSize(bytes: number) {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\nexport function checkFileType(file: File, accept: string): string | null {\n const acceptedTypes = accept.split(',').map((type) => type.trim())\n const fileType = file.type\n const fileExtension = `.${file.name.split('.').pop()?.toLowerCase() || ''}`\n\n const isValid = acceptedTypes.some((acceptType) => {\n if (acceptType.includes('/*')) {\n const prefix = acceptType.split('/')[0]\n return fileType.startsWith(`${prefix}/`)\n }\n if (acceptType.startsWith('.')) {\n return acceptType.toLowerCase() === fileExtension\n }\n return fileType === acceptType\n })\n\n return isValid ? null : `Unsupported file type. Supported types: ${acceptedTypes.join(', ')}`\n}\n\nexport function checkFileMaxSize(file: File, maxSize: number): string | null {\n if (file.size > maxSize) {\n return `File exceeded max size of ${formatFileSize(maxSize)}`\n }\n return null\n}\n\nexport function validateFileMaxSize(value: string | File | null, maxSize?: number): string | null {\n if (value instanceof File && maxSize) {\n return checkFileMaxSize(value, maxSize)\n }\n return null\n}\n\nexport function validateFileType(value: string | File | null, accept?: string): string | null {\n if (value instanceof File && accept) {\n return checkFileType(value, accept)\n }\n return null\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"},{"name":"utils/use-pagination.ts","content":"import * as React from 'react'\n\nexport interface PaginationPage {\n label: string\n value: number | null\n readonly: boolean\n selected: boolean\n}\n\nexport function usePagination({ currentPage, pageCount }: { currentPage: number; pageCount: number }): PaginationPage[] {\n return React.useMemo(() => {\n function getPage(i: number): PaginationPage {\n return { label: `${i + 1}`, value: i, readonly: false, selected: currentPage === i }\n }\n\n const first: PaginationPage = { label: '1', value: 0, readonly: false, selected: currentPage === 0 }\n const ellipsis: PaginationPage = { label: '...', value: null, readonly: true, selected: false }\n const last: PaginationPage = {\n label: `${pageCount}`,\n value: pageCount - 1,\n readonly: false,\n selected: currentPage === pageCount - 1\n }\n\n if (pageCount < 8) {\n return Array.from({ length: pageCount }, (_, i) => getPage(i))\n } else if (currentPage < 5) {\n const leading = Array.from({ length: 5 }, (_, i) => getPage(i))\n return [...leading, ellipsis, last]\n } else if (currentPage > pageCount - 5) {\n const trailing = Array.from({ length: 5 }, (_, i) => getPage(i + pageCount - 4))\n return [first, ellipsis, ...trailing.filter((p) => p.value! <= pageCount - 1)]\n } else {\n const siblings = Array.from({ length: 3 }, (_, i) => getPage(i + currentPage - 1))\n return [first, ellipsis, ...siblings, ellipsis, last]\n }\n }, [pageCount, currentPage])\n}\n"},{"name":"components/helpers/pdf-dist.client.ts","content":"// @ts-ignore\nimport pdfjs from '@bundled-es-modules/pdfjs-dist'\n\npdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`\n\nexport default pdfjs\n"}]},{"name":"dialog","files":[{"name":"components/dialog.tsx","content":"import React from 'react'\nimport { Modal, ModalOverlay, Dialog as AriaDialog, Heading } from 'react-aria-components'\nimport { tw } from '../utils/tw'\nimport { Button } from './button'\n\nexport type DialogProps = {\n children?: React.ReactNode\n title?: React.ReactNode\n isOpen?: boolean\n onOpenChange?: (isOpen: boolean) => void\n size?: 'sm' | 'lg'\n className?: string\n isDismissable?: boolean\n actions?: React.ReactNode\n}\n\ninterface DialogContextValue {\n close(): void\n}\n\nconst DialogContext = React.createContext<DialogContextValue>({ close: () => undefined })\n\nfunction Dialog_({\n children,\n title,\n isOpen = false,\n onOpenChange,\n size = 'sm',\n className,\n isDismissable = false,\n actions\n}: DialogProps) {\n return (\n <ModalOverlay\n isOpen={isOpen}\n onOpenChange={onOpenChange}\n isDismissable={isDismissable}\n isKeyboardDismissDisabled={!isDismissable}\n className={tw(\n 'fixed inset-0 z-[2000] flex items-center justify-center bg-black/70 backdrop-blur-lg',\n 'transition-opacity duration-300 ease-in-out',\n 'data-[entering]:opacity-0',\n 'data-[exiting]:opacity-0'\n )}\n >\n <Modal\n className={tw(\n 'w-[calc(100%-4rem)] m-auto',\n size === 'lg' ? 'max-w-[1080px]' : 'max-w-[640px]',\n 'transition-all duration-300 ease-in-out',\n 'data-[entering]:scale-95 data-[entering]:opacity-0',\n 'data-[exiting]:scale-95 data-[exiting]:opacity-0'\n )}\n >\n <AriaDialog\n className={tw(\n 'w-full max-h-[calc(100vh-6rem)] bg-white shadow-xl outline-none rounded-lg',\n 'flex flex-col overflow-x-hidden overflow-y-auto',\n className\n )}\n >\n {({ close }) => (\n <DialogContext.Provider value={{ close }}>\n <div className=\"flex items-start justify-between gap-4 pt-6 px-6\">\n {title ? (\n <Heading slot=\"title\" className=\"flex-1 text-xl font-semibold text-neutral-900\">\n {title}\n </Heading>\n ) : (\n <div />\n )}\n </div>\n\n <div className=\"flex flex-col gap-3 px-6 pb-6\">\n <div className=\"\">{children}</div>\n <div className=\"flex gap-2 items-start\">{actions}</div>\n </div>\n </DialogContext.Provider>\n )}\n </AriaDialog>\n </Modal>\n </ModalOverlay>\n )\n}\n\nfunction CancelButton({ children = 'Cancel' }: { children?: React.ReactNode }) {\n const context = React.useContext(DialogContext)\n return (\n <Button color=\"neutral\" onClick={context.close} variant=\"soft\" shape=\"oval\">\n {children}\n </Button>\n )\n}\n\nexport const Dialog = Object.assign(Dialog_, {\n CancelButton\n})\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/headless-button.tsx","content":"import { composeRefs } from '../../utils/compose-refs'\nimport { type WithRenderProps, useRenderProps } from '../../utils/use-render-props'\n\nimport React from 'react'\nimport { usePress } from '@react-aria/interactions'\nimport { mergeProps } from '@react-aria/utils'\n\nexport const HeadlessButton = React.forwardRef<\n HTMLButtonElement,\n WithRenderProps<React.ComponentProps<'button'>, { isPressed: boolean }>\n>((rawProps, externalRef) => {\n const myRef = React.useRef<HTMLButtonElement>(null)\n const { isPressed, pressProps } = usePress({ ref: myRef, isDisabled: rawProps.disabled })\n const renderProps = React.useMemo(\n () => ({\n isPressed\n }),\n [isPressed]\n )\n\n const props = useRenderProps(mergeProps(pressProps, rawProps), renderProps, ['children', 'style', 'className'])\n const ref = React.useMemo(() => composeRefs(myRef, externalRef), [myRef, externalRef])\n\n return (\n <button\n type=\"button\"\n {...props}\n {...(props.disabled || props['data-disabled'] ? { ['data-disabled']: true } : {})}\n {...((isPressed && !props.disabled) || props['data-pressed'] ? { ['data-pressed']: true } : {})}\n aria-pressed={isPressed && !props.disabled}\n ref={ref}\n />\n )\n})\n\nHeadlessButton.displayName = 'HeadlessButton'\n"},{"name":"utils/compose-refs.ts","content":"import React from 'react'\n\ntype PossibleRef<T> = React.Ref<T> | undefined\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n if (typeof ref === 'function') {\n ref(value)\n } else if (ref !== null && ref !== undefined) {\n ;(ref as React.MutableRefObject<T>).current = value\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nexport function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n return (node: T) => refs.forEach((ref) => setRef(ref, node))\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nexport function useComposedRefs<T>(...refs: PossibleRef<T>[]) {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return React.useCallback(composeRefs(...refs), refs)\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"},{"name":"utils/use-render-props.ts","content":"import React from 'react'\n\ntype Prettify<T> = { [K in keyof T]: T[K] } & {}\n\ntype DefaultRenderPropKeys<Props> = Extract<'children' | 'className' | 'style', keyof Props>\n\ntype MaybeRenderProp<Value, RenderProps> = Value | ((renderProps: RenderProps) => Value)\n\nexport type WithRenderProps<\n Props extends Record<string, any>,\n RenderProps,\n Keys extends keyof Props = DefaultRenderPropKeys<Props>\n> = Prettify<{\n [K in keyof Props]: K extends Keys ? MaybeRenderProp<Props[K], RenderProps> : Props[K]\n}>\n\nexport function useRenderProps<\n Props extends Record<string, any>,\n RenderProps,\n Keys extends keyof Props = DefaultRenderPropKeys<Props>\n>(props: Props, renderProps: RenderProps, keys: Keys[]) {\n const withRenderProps = React.useMemo(\n () =>\n Object.fromEntries(\n keys\n .map((key) => {\n if (key in props && typeof props[key] === 'function') {\n return [key, props[key](renderProps)]\n } else {\n return []\n }\n })\n .filter((k) => k.length)\n ),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [...keys, renderProps, props, ...keys.map((key) => props[key])]\n )\n return { ...props, ...withRenderProps }\n}\n"}]},{"name":"drawer","files":[{"name":"components/drawer.tsx","content":"import React from 'react'\nimport { Drawer as VaulDrawer } from 'vaul'\nimport { tw } from '../utils/tw'\n\nexport type DrawerProps = {\n children?: React.ReactNode\n title?: React.ReactNode\n isOpen?: boolean\n onOpenChange?: (isOpen: boolean) => void\n size?: 'sm' | 'md' | 'lg'\n className?: string\n isDismissable?: boolean\n position?: 'left' | 'right' | 'top' | 'bottom'\n actions?: React.ReactNode\n contentsContainerClassName?: string\n}\n\nexport function Drawer({\n children,\n title,\n isOpen = false,\n onOpenChange,\n size = 'md',\n className,\n isDismissable = true,\n position = 'right',\n actions,\n contentsContainerClassName\n}: DrawerProps) {\n return (\n <VaulDrawer.Root open={isOpen} onOpenChange={onOpenChange} direction={position} dismissible={isDismissable}>\n <VaulDrawer.Portal>\n <VaulDrawer.Overlay className=\"fixed inset-0 z-49 bg-black/70 backdrop-blur-lg\" />\n <VaulDrawer.Content\n className={tw(\n 'flex h-auto flex-col text-sm group/drawer-content fixed z-50',\n 'data-[vaul-drawer-direction=bottom]:bottom-2 data-[vaul-drawer-direction=bottom]:left-2 data-[vaul-drawer-direction=bottom]:right-2 data-[vaul-drawer-direction=bottom]:max-h-[80vh]',\n 'data-[vaul-drawer-direction=left]:left-2 data-[vaul-drawer-direction=left]:top-2 data-[vaul-drawer-direction=left]:bottom-2 data-[vaul-drawer-direction=left]:h-[calc(100%-1rem)] data-[vaul-drawer-direction=left]:w-3/4',\n 'data-[vaul-drawer-direction=right]:right-2 data-[vaul-drawer-direction=right]:top-2 data-[vaul-drawer-direction=right]:bottom-2 data-[vaul-drawer-direction=right]:h-[calc(100%-1rem)] data-[vaul-drawer-direction=right]:w-3/4',\n 'data-[vaul-drawer-direction=top]:top-2 data-[vaul-drawer-direction=top]:left-2 data-[vaul-drawer-direction=top]:right-2 data-[vaul-drawer-direction=top]:max-h-[80vh]',\n size === 'sm'\n ? 'data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm'\n : size === 'md'\n ? 'data-[vaul-drawer-direction=left]:sm:max-w-lg data-[vaul-drawer-direction=right]:sm:max-w-lg'\n : 'data-[vaul-drawer-direction=left]:sm:max-w-3xl data-[vaul-drawer-direction=right]:sm:max-w-3xl',\n className\n )}\n style={{ '--initial-transform': 'calc(100% + 8px)' } as React.CSSProperties}\n >\n {isDismissable ? (\n <div\n className={tw(\n 'absolute bg-neutral-200 rounded-full shrink-0 group-data-[vaul-drawer-direction=bottom]/drawer-content:block',\n 'group-data-[vaul-drawer-direction=left]/drawer-content:h-25 group-data-[vaul-drawer-direction=left]/drawer-content:w-1 group-data-[vaul-drawer-direction=left]/drawer-content:right-2 group-data-[vaul-drawer-direction=left]/drawer-content:top-[calc(50%-50px)]',\n 'group-data-[vaul-drawer-direction=right]/drawer-content:h-25 group-data-[vaul-drawer-direction=right]/drawer-content:w-1 group-data-[vaul-drawer-direction=right]/drawer-content:left-2 group-data-[vaul-drawer-direction=right]/drawer-content:top-[calc(50%-50px)]',\n 'group-data-[vaul-drawer-direction=top]/drawer-content:w-25 group-data-[vaul-drawer-direction=top]/drawer-content:h-1 group-data-[vaul-drawer-direction=top]/drawer-content:bottom-2 group-data-[vaul-drawer-direction=top]/drawer-content:left-[calc(50%-50px)]',\n 'group-data-[vaul-drawer-direction=bottom]/drawer-content:w-25 group-data-[vaul-drawer-direction=bottom]/drawer-content:h-1 group-data-[vaul-drawer-direction=bottom]/drawer-content:top-2 group-data-[vaul-drawer-direction=bottom]/drawer-content:left-[calc(50%-50px)]'\n )}\n />\n ) : null}\n\n <div className=\"bg-white size-full pt-6 px-6 pb-6 rounded-md shadow-xl\">\n <div className=\"flex items-start justify-between gap-4 pb-5\">\n {title ? (\n <VaulDrawer.Title className=\"flex-1 text-xl font-semibold text-neutral-900\">{title}</VaulDrawer.Title>\n ) : (\n <div />\n )}\n <div className=\"flex gap-2 items-start\">{actions}</div>\n </div>\n\n <div className={tw(contentsContainerClassName)}>{children}</div>\n </div>\n </VaulDrawer.Content>\n </VaulDrawer.Portal>\n </VaulDrawer.Root>\n )\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/headless-button.tsx","content":"import { composeRefs } from '../../utils/compose-refs'\nimport { type WithRenderProps, useRenderProps } from '../../utils/use-render-props'\n\nimport React from 'react'\nimport { usePress } from '@react-aria/interactions'\nimport { mergeProps } from '@react-aria/utils'\n\nexport const HeadlessButton = React.forwardRef<\n HTMLButtonElement,\n WithRenderProps<React.ComponentProps<'button'>, { isPressed: boolean }>\n>((rawProps, externalRef) => {\n const myRef = React.useRef<HTMLButtonElement>(null)\n const { isPressed, pressProps } = usePress({ ref: myRef, isDisabled: rawProps.disabled })\n const renderProps = React.useMemo(\n () => ({\n isPressed\n }),\n [isPressed]\n )\n\n const props = useRenderProps(mergeProps(pressProps, rawProps), renderProps, ['children', 'style', 'className'])\n const ref = React.useMemo(() => composeRefs(myRef, externalRef), [myRef, externalRef])\n\n return (\n <button\n type=\"button\"\n {...props}\n {...(props.disabled || props['data-disabled'] ? { ['data-disabled']: true } : {})}\n {...((isPressed && !props.disabled) || props['data-pressed'] ? { ['data-pressed']: true } : {})}\n aria-pressed={isPressed && !props.disabled}\n ref={ref}\n />\n )\n})\n\nHeadlessButton.displayName = 'HeadlessButton'\n"},{"name":"utils/compose-refs.ts","content":"import React from 'react'\n\ntype PossibleRef<T> = React.Ref<T> | undefined\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n if (typeof ref === 'function') {\n ref(value)\n } else if (ref !== null && ref !== undefined) {\n ;(ref as React.MutableRefObject<T>).current = value\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nexport function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n return (node: T) => refs.forEach((ref) => setRef(ref, node))\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nexport function useComposedRefs<T>(...refs: PossibleRef<T>[]) {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return React.useCallback(composeRefs(...refs), refs)\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"},{"name":"utils/use-render-props.ts","content":"import React from 'react'\n\ntype Prettify<T> = { [K in keyof T]: T[K] } & {}\n\ntype DefaultRenderPropKeys<Props> = Extract<'children' | 'className' | 'style', keyof Props>\n\ntype MaybeRenderProp<Value, RenderProps> = Value | ((renderProps: RenderProps) => Value)\n\nexport type WithRenderProps<\n Props extends Record<string, any>,\n RenderProps,\n Keys extends keyof Props = DefaultRenderPropKeys<Props>\n> = Prettify<{\n [K in keyof Props]: K extends Keys ? MaybeRenderProp<Props[K], RenderProps> : Props[K]\n}>\n\nexport function useRenderProps<\n Props extends Record<string, any>,\n RenderProps,\n Keys extends keyof Props = DefaultRenderPropKeys<Props>\n>(props: Props, renderProps: RenderProps, keys: Keys[]) {\n const withRenderProps = React.useMemo(\n () =>\n Object.fromEntries(\n keys\n .map((key) => {\n if (key in props && typeof props[key] === 'function') {\n return [key, props[key](renderProps)]\n } else {\n return []\n }\n })\n .filter((k) => k.length)\n ),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [...keys, renderProps, props, ...keys.map((key) => props[key])]\n )\n return { ...props, ...withRenderProps }\n}\n"}]},{"name":"alert-dialog","files":[{"name":"components/alert-dialog.tsx","content":"import * as React from 'react'\nimport { Dialog, Modal, ModalOverlay, Heading } from 'react-aria-components'\nimport { tw } from '../utils/tw'\nimport { Icon } from './icon'\nimport { Button } from './button'\nimport { ButtonGroupContext } from './button-group'\nimport { ButtonContext } from './helpers/button-context'\n\nexport type AlertDialogProps = {\n title: string\n description?: string\n variant?: 'info' | 'positive' | 'neutral' | 'negative' | 'notice'\n icon?: React.ReactNode\n children?: React.ReactNode | ((props: { close(): void }) => React.ReactNode)\n isOpen?: boolean\n onOpenChange?: (isOpen: boolean) => void\n isDismissable?: boolean\n className?: string\n}\n\nconst variantStyles = {\n info: {\n container: 'border-info-500/20',\n icon: 'text-info-500',\n iconBg: 'bg-info-500/10',\n header: 'text-info-500'\n },\n positive: {\n container: 'border-positive-200',\n icon: 'text-positive-600',\n iconBg: 'bg-positive-100',\n header: 'text-positive-800'\n },\n notice: {\n container: 'border-notice-200',\n icon: 'text-notice-600',\n iconBg: 'bg-notice-100',\n header: 'text-notice-800'\n },\n negative: {\n container: 'border-negative-200',\n icon: 'text-negative-600',\n iconBg: 'bg-negative-100',\n header: 'text-negative-800'\n },\n neutral: {\n container: 'border-neutral-200',\n icon: 'text-neutral-600',\n iconBg: 'bg-neutral-100',\n header: 'text-neutral-700'\n }\n}\n\nconst defaultIconNames = {\n info: 'information-circle',\n positive: 'check',\n neutral: 'information-circle',\n negative: 'x-mark',\n notice: 'exclamation-triangle'\n} as const\n\ninterface AlertDialogContextValue {\n close(): void\n}\n\nconst AlertDialogContext = React.createContext<AlertDialogContextValue>({ close: () => undefined })\n\nfunction AlertDialog_({\n description,\n title,\n variant = 'info',\n icon,\n isOpen,\n onOpenChange,\n isDismissable = true,\n className,\n children\n}: AlertDialogProps) {\n const displayIcon = icon !== undefined ? icon : <Icon name={defaultIconNames[variant]} className=\"h-8 w-8\" />\n const styles = variantStyles[variant]\n\n return (\n <ModalOverlay\n isOpen={isOpen}\n onOpenChange={onOpenChange}\n isDismissable={isDismissable}\n isKeyboardDismissDisabled={!isDismissable}\n className={tw(\n 'fixed inset-0 z-[4000] flex items-center justify-center bg-black/70 backdrop-blur-lg',\n 'transition-opacity duration-200 ease-out',\n 'data-[entering]:opacity-0',\n 'data-[exiting]:opacity-0'\n )}\n >\n <Modal\n className={tw(\n 'w-[calc(100%-2rem)] max-w-sm',\n 'transition-all duration-200 ease-out',\n 'data-[entering]:scale-95 data-[entering]:opacity-0 data-[entering]:translate-y-2',\n 'data-[exiting]:scale-95 data-[exiting]:opacity-0 data-[exiting]:translate-y-2'\n )}\n >\n <Dialog className={tw('outline-none bg-white rounded-lg shadow-2xl w-full', className)} role=\"alertdialog\">\n {({ close }) => (\n <AlertDialogContext.Provider value={{ close }}>\n <ButtonGroupContext.Provider\n value={{ orientation: 'horizontal', alignment: 'center', fullWidth: true, className: 'flex-nowrap' }}\n >\n <ButtonContext.Provider value={{ shape: 'rectangle', variant: 'soft', fullWidth: true }}>\n <div className={tw('border rounded-lg p-6', styles.container)}>\n <div className=\"flex flex-col items-center text-center\">\n {displayIcon && (\n <div className={tw('w-12 h-12 rounded-xl flex items-center justify-center mb-4', styles.iconBg)}>\n <div className={tw(styles.icon)}>{displayIcon}</div>\n </div>\n )}\n <Heading\n slot=\"title\"\n className={tw('text-xl font-semibold', description ? 'mb-2' : 'mb-5', styles.header)}\n >\n {title}\n </Heading>\n {description && <div className=\"text-sm text-neutral-600 mb-5\">{description}</div>}\n {typeof children === 'function' ? children({ close }) : children}\n </div>\n </div>\n </ButtonContext.Provider>\n </ButtonGroupContext.Provider>\n </AlertDialogContext.Provider>\n )}\n </Dialog>\n </Modal>\n </ModalOverlay>\n )\n}\n\nAlertDialog_.displayName = 'AlertDialog'\n\nfunction CancelButton({ children = 'Cancel' }: { children?: React.ReactNode }) {\n const context = React.useContext(AlertDialogContext)\n return (\n <Button color=\"neutral\" onClick={context.close} variant=\"soft\" shape=\"rectangle\">\n {children}\n </Button>\n )\n}\n\nexport const AlertDialog = Object.assign(AlertDialog_, {\n CancelButton\n})\n"},{"name":"components/button-group.tsx","content":"import * as React from 'react'\nimport { mergeProps } from '@react-aria/utils'\nimport { tw } from '../utils/tw'\n\nexport type ButtonGroupOrientation = 'horizontal' | 'vertical'\nexport type ButtonGroupAlignment = 'start' | 'center' | 'end'\nexport type ButtonGroupSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\nexport type ButtonGroupProps = {\n orientation?: ButtonGroupOrientation\n alignment?: ButtonGroupAlignment\n size?: ButtonGroupSize\n className?: string\n children?: React.ReactNode\n 'aria-label'?: string\n 'aria-labelledby'?: string\n 'aria-describedby'?: string\n fullWidth?: boolean\n}\n\nexport const ButtonGroupContext = React.createContext<{\n orientation?: ButtonGroupOrientation\n alignment?: ButtonGroupAlignment\n size?: ButtonGroupSize\n fullWidth?: boolean\n className?: string\n}>({})\n\nconst sizeGapClasses: Record<ButtonGroupSize, string> = {\n xl: 'gap-4',\n lg: 'gap-3',\n md: 'gap-2',\n sm: 'gap-1',\n xs: 'gap-0.5'\n}\n\nconst alignmentClasses: Record<ButtonGroupAlignment, string> = {\n start: 'justify-start',\n center: 'justify-center',\n end: 'justify-end'\n}\n\nexport function ButtonGroup(inputProps: ButtonGroupProps) {\n const context = React.useContext(ButtonGroupContext)\n const {\n orientation = 'horizontal',\n alignment = 'end',\n size = 'md',\n className,\n children,\n fullWidth,\n ...ariaProps\n } = mergeProps(React.useContext(ButtonGroupContext), inputProps)\n\n return (\n <div\n role=\"group\"\n aria-label={ariaProps['aria-label']}\n aria-labelledby={ariaProps['aria-labelledby']}\n aria-describedby={ariaProps['aria-describedby']}\n className={tw(\n 'inline-flex w-fit',\n sizeGapClasses[size],\n orientation === 'horizontal' ? 'flex-row items-center flex-wrap' : 'flex-col',\n alignmentClasses[alignment],\n fullWidth && 'w-full',\n className\n )}\n >\n {children}\n </div>\n )\n}\n\nButtonGroup.displayName = 'ButtonGroup'\n"},{"name":"components/button.tsx","content":"import * as React from 'react'\nimport { Spinner } from './spinner'\nimport { flushSync } from 'react-dom'\nimport { composeRenderProps } from 'react-aria-components'\nimport {\n getButtonClasses,\n type ButtonColor,\n type ButtonShape,\n type ButtonSize,\n type ButtonVariant\n} from './helpers/get-button-classes'\nimport { HeadlessButton } from './helpers/headless-button'\nimport { useSpinDelay } from '../utils/use-spin-delay'\nimport { usePreventDefault } from '../utils/use-prevent-default'\nimport { composeTailwindRenderProps, tw } from '../utils/tw'\nimport { useStableAccessor } from '../utils/use-stable-accessor'\nimport { mergeProps } from '@react-aria/utils'\nimport { ButtonContext } from './helpers/button-context'\n\n// --- Public types ---\n\nexport type ButtonProps = {\n variant?: ButtonVariant\n color?: ButtonColor\n size?: ButtonSize\n shape?: ButtonShape\n isToggled?: boolean\n fullWidth?: boolean\n onActionSucceeded?: () => void\n onActionCompleted?: () => void\n disabled?: boolean\n onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void\n className?: string\n children?: React.ReactNode\n value?: string | undefined\n isPending?: boolean\n} & Partial<Pick<React.ComponentProps<'button'>, 'type' | 'role' | 'name' | 'form' | 'id' | 'title' | 'aria-label'>>\n\nexport const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ isToggled, onActionSucceeded, onActionCompleted, isPending: isPendingProp, ...propsRaw }, ref) => {\n const {\n variant = 'contained',\n color = 'primary',\n size = 'md',\n shape = 'oval',\n fullWidth,\n ...props\n } = mergeProps(propsRaw, React.useContext(ButtonContext))\n\n const { isClicking, onClick: isClickingHandler } = useIsClicking(props.onClick)\n\n const isPending = isPendingProp || isClicking\n const isPendingSmoothed = useSpinDelay(isPending, { delay: 0, minDuration: 50 })\n const showSpinner = useSpinDelay(isPending, { delay: 500, minDuration: 200 })\n\n const getOnActionCompleted = useStableAccessor(onActionCompleted)\n const getOnActionSucceeded = useStableAccessor(onActionSucceeded)\n const wasPending = React.useRef(false)\n React.useEffect(() => {\n if (isPendingSmoothed !== wasPending.current) {\n wasPending.current = isPendingSmoothed\n if (!isPendingSmoothed) {\n getOnActionCompleted()?.()\n getOnActionSucceeded()?.()\n }\n }\n }, [isPendingSmoothed, getOnActionCompleted, getOnActionSucceeded])\n\n const onClick = usePreventDefault(isClickingHandler, showSpinner || isClicking)\n\n return (\n <HeadlessButton\n {...props}\n ref={ref}\n data-pending={isPending}\n onClick={onClick}\n className={composeTailwindRenderProps(\n props.className,\n tw(getButtonClasses({ variant, color, size, shape, isToggled }), fullWidth && 'w-full')\n )}\n >\n {composeRenderProps(props.children, (children) => (\n <>\n <span className={tw('inline-flex items-center gap-2', showSpinner && 'opacity-0')}>{children}</span>\n <span\n aria-hidden={!showSpinner ? true : undefined}\n className={tw('absolute inset-0 flex items-center justify-center', !showSpinner && 'opacity-0')}\n >\n <Spinner className=\"size-4\" />\n </span>\n </>\n ))}\n </HeadlessButton>\n )\n }\n)\n\nButton.displayName = 'Button'\n\nfunction useIsClicking<C extends ((event: React.MouseEvent<any, MouseEvent>) => unknown) | undefined>(\n onClickRaw: C\n): { onClick: C; isClicking: boolean } {\n type Event = C extends (event: infer E) => unknown ? E : never\n\n const [isClicking, setIsClicking] = React.useState<boolean>(false)\n\n const onClickRawStabilized = useStableAccessor(onClickRaw)\n\n const onClickDebounced = React.useCallback(\n (event: Event) => {\n try {\n const onClickInner = onClickRawStabilized()\n flushSync(() => setIsClicking(true))\n const result = onClickInner ? onClickInner(event) : null\n Promise.resolve(result).finally(() => {\n setIsClicking(false)\n })\n return result\n } catch (e) {\n setIsClicking(false)\n throw e\n }\n },\n [onClickRawStabilized, setIsClicking]\n )\n\n if (!onClickRaw) {\n return { onClick: undefined as C, isClicking }\n } else {\n return { onClick: onClickDebounced as C, isClicking }\n }\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/spinner.tsx","content":"import { ProgressBar } from 'react-aria-components'\nimport { Icon } from './icon'\nimport { tw } from '../utils/tw'\n\ninterface SpinnerProps {\n className?: string\n 'aria-label'?: string\n}\n\nexport function Spinner({ className, 'aria-label': ariaLabel = 'Loading…' }: SpinnerProps) {\n return (\n <ProgressBar isIndeterminate aria-label={ariaLabel}>\n <Icon name=\"arrow-path\" className={tw('animate-spin', className)} />\n </ProgressBar>\n )\n}\n"},{"name":"components/helpers/button-context.ts","content":"import React from 'react'\nimport type { ButtonVariant, ButtonColor, ButtonSize, ButtonShape } from './get-button-classes'\n\nexport const ButtonContext = React.createContext<{\n variant?: ButtonVariant\n color?: ButtonColor\n size?: ButtonSize\n shape?: ButtonShape\n fullWidth?: boolean\n}>({})\n"},{"name":"components/helpers/get-button-classes.ts","content":"import { tw } from '../../utils/tw'\n\nexport type ButtonVariant = 'contained' | 'outlined' | 'soft' | 'plain'\nexport type ButtonColor = 'primary' | 'negative' | 'positive' | 'notice' | 'neutral'\nexport type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\nexport type ButtonShape = 'rectangle' | 'circle' | 'oval'\n\nconst base =\n 'relative inline-flex items-center justify-center gap-2 font-medium whitespace-nowrap transition-all cursor-default select-none [&_svg]:pointer-events-none [&_svg:not([class*=size-])]:size-4 [&_svg]:shrink-0 data-[disabled]:cursor-not-allowed'\n\nconst sizeClasses: Record<ButtonSize, string> = {\n xs: 'h-7 min-w-14 px-2.5 text-xs',\n sm: 'h-8 min-w-16 px-3 text-sm',\n md: 'h-9 min-w-20 px-4 text-sm',\n lg: 'h-10 min-w-24 px-5 text-base',\n xl: 'h-12 min-w-28 px-6 text-base'\n}\n\nconst shapeClasses: Record<ButtonShape, string> = {\n rectangle: 'rounded',\n circle: 'rounded-full aspect-square px-0',\n oval: 'rounded-full'\n}\n\nconst variantColorClasses: Record<ButtonVariant, Record<ButtonColor, string>> = {\n contained: {\n primary:\n 'bg-primary-600 text-white ring-1 ring-inset ring-primary-700 data-[hovered]:bg-primary-700 data-[hovered]:ring-primary-800 data-[pressed]:bg-primary-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',\n negative:\n 'bg-negative-600 text-white ring-1 ring-inset ring-negative-700 data-[hovered]:bg-negative-700 data-[hovered]:ring-negative-800 data-[pressed]:bg-negative-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',\n positive:\n 'bg-positive-600 text-white ring-1 ring-inset ring-positive-700 data-[hovered]:bg-positive-700 data-[hovered]:ring-positive-800 data-[pressed]:bg-positive-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',\n notice:\n 'bg-notice-600 text-white ring-1 ring-inset ring-notice-700 data-[hovered]:bg-notice-700 data-[hovered]:ring-notice-800 data-[pressed]:bg-notice-800 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none',\n neutral:\n 'bg-neutral-800 text-white ring-1 ring-inset ring-neutral-900 data-[hovered]:bg-neutral-900 data-[pressed]:bg-neutral-950 data-[disabled]:bg-neutral-200 data-[disabled]:text-neutral-400 data-[disabled]:ring-transparent data-[disabled]:shadow-none'\n },\n outlined: {\n primary:\n 'bg-white ring-1 ring-inset ring-primary-300 text-primary-700 data-[hovered]:bg-primary-50 data-[hovered]:ring-primary-400 data-[pressed]:bg-primary-100 data-[pressed]:ring-primary-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',\n negative:\n 'bg-white ring-1 ring-inset ring-negative-300 text-negative-700 data-[hovered]:bg-negative-50 data-[hovered]:ring-negative-400 data-[pressed]:bg-negative-100 data-[pressed]:ring-negative-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',\n positive:\n 'bg-white ring-1 ring-inset ring-positive-300 text-positive-700 data-[hovered]:bg-positive-50 data-[hovered]:ring-positive-400 data-[pressed]:bg-positive-100 data-[pressed]:ring-positive-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',\n notice:\n 'bg-white ring-1 ring-inset ring-notice-300 text-notice-700 data-[hovered]:bg-notice-50 data-[hovered]:ring-notice-400 data-[pressed]:bg-notice-100 data-[pressed]:ring-notice-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none',\n neutral:\n 'bg-white ring-1 ring-inset ring-neutral-300 text-neutral-700 data-[hovered]:bg-neutral-50 data-[hovered]:ring-neutral-400 data-[pressed]:bg-neutral-100 data-[pressed]:ring-neutral-500 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400 data-[disabled]:ring-neutral-200 data-[disabled]:shadow-none'\n },\n soft: {\n primary:\n 'bg-primary-100 text-primary-800 data-[hovered]:bg-primary-200 data-[pressed]:bg-primary-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',\n negative:\n 'bg-negative-100 text-negative-800 data-[hovered]:bg-negative-200 data-[pressed]:bg-negative-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',\n positive:\n 'bg-positive-100 text-positive-800 data-[hovered]:bg-positive-200 data-[pressed]:bg-positive-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',\n notice:\n 'bg-notice-100 text-notice-800 data-[hovered]:bg-notice-200 data-[pressed]:bg-notice-300 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400',\n neutral:\n 'bg-neutral-200 text-neutral-800 data-[hovered]:bg-neutral-300 data-[pressed]:bg-neutral-400 data-[disabled]:bg-neutral-50 data-[disabled]:text-neutral-400'\n },\n plain: {\n primary:\n 'bg-transparent text-primary-700 data-[hovered]:bg-primary-50 data-[pressed]:bg-primary-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',\n negative:\n 'bg-transparent text-negative-700 data-[hovered]:bg-negative-50 data-[pressed]:bg-negative-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',\n positive:\n 'bg-transparent text-positive-700 data-[hovered]:bg-positive-50 data-[pressed]:bg-positive-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',\n notice:\n 'bg-transparent text-notice-700 data-[hovered]:bg-notice-50 data-[pressed]:bg-notice-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400',\n neutral:\n 'bg-transparent text-neutral-700 data-[hovered]:bg-neutral-50 data-[pressed]:bg-neutral-100 data-[disabled]:bg-transparent data-[disabled]:text-neutral-400'\n }\n}\n\nconst toggledVariantColorClasses: Record<ButtonVariant, Record<ButtonColor, string>> = {\n contained: {\n primary: 'bg-primary-800 ring-primary-900',\n negative: 'bg-negative-800 ring-negative-900',\n positive: 'bg-positive-800 ring-positive-900',\n notice: 'bg-notice-800 ring-notice-900',\n neutral: 'bg-neutral-950'\n },\n outlined: {\n primary: 'bg-primary-100 ring-primary-500',\n negative: 'bg-negative-100 ring-negative-500',\n positive: 'bg-positive-100 ring-positive-500',\n notice: 'bg-notice-100 ring-notice-500',\n neutral: 'bg-neutral-100 ring-neutral-500'\n },\n soft: {\n primary: 'bg-primary-200',\n negative: 'bg-negative-200',\n positive: 'bg-positive-200',\n notice: 'bg-notice-200',\n neutral: 'bg-neutral-300'\n },\n plain: {\n primary: 'bg-primary-100',\n negative: 'bg-negative-100',\n positive: 'bg-positive-100',\n notice: 'bg-notice-100',\n neutral: 'bg-neutral-100'\n }\n}\n\nconst focusClasses =\n 'data-[focus-visible]:outline-2 data-[focus-visible]:outline-offset-2 data-[focus-visible]:outline-primary-600'\n\nexport function getButtonClasses({\n variant = 'contained',\n color = 'primary',\n size = 'md',\n shape = 'oval',\n showSpinner,\n isToggled,\n className\n}: {\n variant?: ButtonVariant\n color?: ButtonColor\n size?: ButtonSize\n shape?: ButtonShape\n showSpinner?: boolean\n isToggled?: boolean\n className?: string\n} = {}) {\n return tw(\n base,\n 'data-[pressed]:scale-[.98]',\n showSpinner ? '[&>:not([data-loader])]:opacity-0 !text-transparent' : '',\n sizeClasses[size],\n shapeClasses[shape],\n variantColorClasses[variant][color],\n isToggled && toggledVariantColorClasses[variant][color],\n focusClasses,\n className\n )\n}\n"},{"name":"components/helpers/headless-button.tsx","content":"import { composeRefs } from '../../utils/compose-refs'\nimport { type WithRenderProps, useRenderProps } from '../../utils/use-render-props'\n\nimport React from 'react'\nimport { usePress } from '@react-aria/interactions'\nimport { mergeProps } from '@react-aria/utils'\n\nexport const HeadlessButton = React.forwardRef<\n HTMLButtonElement,\n WithRenderProps<React.ComponentProps<'button'>, { isPressed: boolean }>\n>((rawProps, externalRef) => {\n const myRef = React.useRef<HTMLButtonElement>(null)\n const { isPressed, pressProps } = usePress({ ref: myRef, isDisabled: rawProps.disabled })\n const renderProps = React.useMemo(\n () => ({\n isPressed\n }),\n [isPressed]\n )\n\n const props = useRenderProps(mergeProps(pressProps, rawProps), renderProps, ['children', 'style', 'className'])\n const ref = React.useMemo(() => composeRefs(myRef, externalRef), [myRef, externalRef])\n\n return (\n <button\n type=\"button\"\n {...props}\n {...(props.disabled || props['data-disabled'] ? { ['data-disabled']: true } : {})}\n {...((isPressed && !props.disabled) || props['data-pressed'] ? { ['data-pressed']: true } : {})}\n aria-pressed={isPressed && !props.disabled}\n ref={ref}\n />\n )\n})\n\nHeadlessButton.displayName = 'HeadlessButton'\n"},{"name":"utils/compose-refs.ts","content":"import React from 'react'\n\ntype PossibleRef<T> = React.Ref<T> | undefined\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n if (typeof ref === 'function') {\n ref(value)\n } else if (ref !== null && ref !== undefined) {\n ;(ref as React.MutableRefObject<T>).current = value\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nexport function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n return (node: T) => refs.forEach((ref) => setRef(ref, node))\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nexport function useComposedRefs<T>(...refs: PossibleRef<T>[]) {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return React.useCallback(composeRefs(...refs), refs)\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"},{"name":"utils/use-prevent-default.ts","content":"import React from 'react'\nimport { useStableAccessor } from './use-stable-accessor'\n\nexport function usePreventDefault<E extends React.SyntheticEvent<any>>(\n eventHandler: React.EventHandler<E> | undefined,\n preventDefault: boolean\n): React.EventHandler<E> {\n const getShouldPreventDefault = useStableAccessor(preventDefault)\n const getEventHandler = useStableAccessor(eventHandler)\n\n const myEventHandler = React.useCallback(\n (event: E) => {\n if (getShouldPreventDefault()) {\n event.preventDefault()\n event.stopPropagation()\n return\n } else {\n const eventHandler = getEventHandler()\n if (eventHandler) {\n return eventHandler(event)\n }\n }\n },\n [getEventHandler, getShouldPreventDefault]\n )\n return myEventHandler\n}\n"},{"name":"utils/use-render-props.ts","content":"import React from 'react'\n\ntype Prettify<T> = { [K in keyof T]: T[K] } & {}\n\ntype DefaultRenderPropKeys<Props> = Extract<'children' | 'className' | 'style', keyof Props>\n\ntype MaybeRenderProp<Value, RenderProps> = Value | ((renderProps: RenderProps) => Value)\n\nexport type WithRenderProps<\n Props extends Record<string, any>,\n RenderProps,\n Keys extends keyof Props = DefaultRenderPropKeys<Props>\n> = Prettify<{\n [K in keyof Props]: K extends Keys ? MaybeRenderProp<Props[K], RenderProps> : Props[K]\n}>\n\nexport function useRenderProps<\n Props extends Record<string, any>,\n RenderProps,\n Keys extends keyof Props = DefaultRenderPropKeys<Props>\n>(props: Props, renderProps: RenderProps, keys: Keys[]) {\n const withRenderProps = React.useMemo(\n () =>\n Object.fromEntries(\n keys\n .map((key) => {\n if (key in props && typeof props[key] === 'function') {\n return [key, props[key](renderProps)]\n } else {\n return []\n }\n })\n .filter((k) => k.length)\n ),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [...keys, renderProps, props, ...keys.map((key) => props[key])]\n )\n return { ...props, ...withRenderProps }\n}\n"},{"name":"utils/use-spin-delay.ts","content":"import React from 'react'\n\nexport function useSpinDelay(loading: boolean, { delay = 500, minDuration = 200 } = {}) {\n const [show, setShow] = React.useState(false)\n const delayTimer = React.useRef<ReturnType<typeof setTimeout>>(undefined)\n const minTimer = React.useRef<ReturnType<typeof setTimeout>>(undefined)\n\n React.useEffect(() => {\n if (loading) {\n delayTimer.current = setTimeout(() => setShow(true), delay)\n } else {\n clearTimeout(delayTimer.current)\n if (show) {\n minTimer.current = setTimeout(() => setShow(false), minDuration)\n }\n }\n return () => {\n clearTimeout(delayTimer.current)\n clearTimeout(minTimer.current)\n }\n }, [loading, delay, minDuration, show])\n\n return show\n}\n"},{"name":"utils/use-stable-accessor.ts","content":"import React from 'react'\n\nexport function useStableAccessor<T>(value: T) {\n const initialPersisterKey = React.useRef<{ value: T }>({ value })\n const getValue = React.useCallback(() => {\n return initialPersisterKey.current.value\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [initialPersisterKey.current])\n initialPersisterKey.current.value = value\n return getValue\n}\n"}]},{"name":"menu","files":[{"name":"components/menu.tsx","content":"import * as React from 'react'\nimport {\n Menu as AriaMenu,\n MenuItem as AriaMenuItem,\n MenuSection as AriaMenuSection,\n MenuTrigger as AriaMenuTrigger,\n Pressable,\n Separator as AriaSeparator\n} from 'react-aria-components'\nimport { AnimatedPopover } from './helpers/animated-popover'\nimport { tw } from '../utils/tw'\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport type MenuProps = {\n /** Trigger element. Can be a ReactNode or render function receiving `{ isOpen }`. */\n Button: React.ReactNode | ((props: { isOpen: boolean }) => React.ReactElement)\n children?: React.ReactNode\n className?: string\n containerClassName?: string\n placement?: 'bottom' | 'bottom start' | 'bottom end' | 'top' | 'top start' | 'top end'\n}\n\nexport type MenuItemProps = {\n children?: React.ReactNode\n className?: string\n isDisabled?: boolean\n onAction?: () => void\n id?: string\n textValue?: string\n}\n\nexport type MenuLinkItemProps = {\n href: string\n children?: React.ReactNode\n className?: string\n isDisabled?: boolean\n target?: React.HTMLAttributeAnchorTarget\n rel?: string\n id?: string\n textValue?: string\n}\n\nexport type MenuGroupProps = {\n children?: React.ReactNode\n}\n\n// ── MenuMain ──────────────────────────────────────────────────────────────────\n\nfunction MenuMain({ Button: ButtonProp, children, className, containerClassName, placement = 'bottom end' }: MenuProps) {\n const [isOpen, setIsOpen] = React.useState(false)\n\n const triggerContent = typeof ButtonProp === 'function' ? ButtonProp({ isOpen }) : ButtonProp\n\n return (\n <div className={tw('relative inline-block text-left', containerClassName)}>\n <AriaMenuTrigger isOpen={isOpen} onOpenChange={setIsOpen}>\n <Pressable>\n {React.isValidElement(triggerContent) ? (\n triggerContent\n ) : (\n <button type=\"button\" className=\"inline-flex items-center focus:outline-none\">\n {triggerContent}\n </button>\n )}\n </Pressable>\n <AnimatedPopover placement={placement} offset={8}>\n <AriaMenu\n className={tw(\n 'overflow-hidden min-w-56 w-max rounded bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none',\n className\n )}\n autoFocus=\"first\"\n >\n {children}\n </AriaMenu>\n </AnimatedPopover>\n </AriaMenuTrigger>\n </div>\n )\n}\n\nMenuMain.displayName = 'Menu'\n\n// ── MenuItem ──────────────────────────────────────────────────────────────────\n\nfunction Item({ children, className, isDisabled, onAction, id, textValue }: MenuItemProps) {\n return (\n <AriaMenuItem\n id={id}\n textValue={textValue ?? (typeof children === 'string' ? children : undefined)}\n onAction={onAction}\n isDisabled={isDisabled}\n className={({ isFocused, isDisabled }) =>\n tw(\n isFocused ? 'bg-neutral-100 text-neutral-900' : 'text-neutral-700',\n 'group flex w-full items-center px-4 py-2 text-sm outline-none',\n isDisabled ? 'cursor-not-allowed opacity-50 pointer-events-none' : 'cursor-pointer',\n className\n )\n }\n >\n {children}\n </AriaMenuItem>\n )\n}\n\n// ── LinkItem ──────────────────────────────────────────────────────────────────\n\nfunction LinkItem({ href, children, className, isDisabled, target, rel, id, textValue }: MenuLinkItemProps) {\n return (\n <AriaMenuItem\n id={id}\n textValue={textValue ?? (typeof children === 'string' ? children : undefined)}\n href={href}\n target={target}\n rel={rel}\n isDisabled={isDisabled}\n className={({ isFocused, isDisabled }) =>\n tw(\n isFocused ? 'bg-neutral-100 text-neutral-900' : 'text-neutral-700',\n 'group flex items-center px-4 py-2 text-sm gap-2 outline-none',\n isDisabled ? 'cursor-not-allowed opacity-50 pointer-events-none' : 'cursor-pointer',\n className\n )\n }\n >\n {children}\n </AriaMenuItem>\n )\n}\n\n// ── Group ─────────────────────────────────────────────────────────────────────\n\nfunction Group({ children }: MenuGroupProps) {\n return <AriaMenuSection className=\"py-1\">{children}</AriaMenuSection>\n}\n\n// ── Separator ─────────────────────────────────────────────────────────────────\n\nfunction MenuSeparator() {\n return <AriaSeparator className=\"border-t border-neutral-100\" />\n}\n\n// ── Export ─────────────────────────────────────────────────────────────────────\n\nexport const Menu = Object.assign(MenuMain, {\n Item,\n LinkItem,\n Group,\n Separator: MenuSeparator\n})\n"},{"name":"components/helpers/animated-popover.tsx","content":"import { Popover, type PopoverProps } from 'react-aria-components'\nimport { tw } from '../../utils/tw'\n\nconst animationClasses = tw(\n 'transition-all duration-150 ease-out',\n // resting state: keep transform properties defined so transitions have endpoints\n 'translate-x-0 translate-y-0',\n // entering: fade + slide from trigger direction\n 'data-[entering]:opacity-0',\n 'data-[entering]:data-[placement=bottom]:-translate-y-1',\n 'data-[entering]:data-[placement=top]:translate-y-1',\n 'data-[entering]:data-[placement=left]:translate-x-1',\n 'data-[entering]:data-[placement=right]:-translate-x-1',\n // exiting: fade + slide toward trigger direction\n 'data-[exiting]:opacity-0',\n 'data-[exiting]:data-[placement=bottom]:-translate-y-1',\n 'data-[exiting]:data-[placement=top]:translate-y-1',\n 'data-[exiting]:data-[placement=left]:translate-x-1',\n 'data-[exiting]:data-[placement=right]:-translate-x-1'\n)\n\nexport function AnimatedPopover({ className, ...props }: PopoverProps) {\n return <Popover {...props} className={tw(animationClasses, className)} />\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"tabs","files":[{"name":"components/tabs.tsx","content":"import * as React from 'react'\nimport { Tabs as AriaTabs, TabList as AriaTabList, Tab as AriaTab, TabPanel as AriaTabPanel } from 'react-aria-components'\nimport type { Key } from 'react-aria-components'\nimport { tw } from '../utils/tw'\nimport {\n TAB_BASE_CLASSES,\n TAB_DISABLED_CLASSES,\n TAB_INACTIVE_CLASSES,\n TAB_OVAL_BASE_CLASSES,\n TAB_OVAL_SELECTED_CLASSES,\n tabColorMap,\n tabListContainerClasses,\n tabListInnerClasses,\n getIndicatorClasses,\n useAnimatedIndicator\n} from '../utils/use-tab-indicator'\nimport type { TabColor, TabVariant } from '../utils/use-tab-indicator'\n\n// --- Types ---\n\nexport type TabGroupProps = {\n children: React.ReactNode\n className?: string\n defaultSelectedKey?: Key\n selectedKey?: Key\n onSelectionChange?: (key: Key) => void\n keyboardActivation?: 'automatic' | 'manual'\n orientation?: 'horizontal' | 'vertical'\n isDisabled?: boolean\n}\n\nexport type TabListProps = {\n children: React.ReactNode\n className?: string\n color?: TabColor\n variant?: TabVariant\n}\n\nexport type TabProps = {\n id: Key\n children: React.ReactNode\n className?: string\n isDisabled?: boolean\n}\n\nexport type TabPanelsProps = {\n children: React.ReactNode\n className?: string\n}\n\nexport type TabPanelProps = {\n id: Key\n children: React.ReactNode\n className?: string\n shouldForceMount?: boolean\n}\n\n// --- TabGroup ---\n\nfunction TabGroup({ children, className, ...props }: TabGroupProps) {\n return (\n <AriaTabs {...props} className={tw('w-full', className)}>\n {children}\n </AriaTabs>\n )\n}\n\nTabGroup.displayName = 'TabGroup'\n\n// --- TabList ---\n\nconst TabListContext = React.createContext<{ color: TabColor; variant: TabVariant }>({ color: 'primary', variant: 'underline' })\n\nfunction TabList({ children, className, color = 'primary', variant = 'underline' }: TabListProps) {\n const containerRef = React.useRef<HTMLDivElement>(null)\n const indicatorRef = useAnimatedIndicator(containerRef, '[data-selected]', 'data-selected')\n\n return (\n <TabListContext.Provider value={{ color, variant }}>\n <div ref={containerRef} className={tabListContainerClasses[variant]}>\n <AriaTabList className={tw(tabListInnerClasses[variant], className)}>{children}</AriaTabList>\n <div\n ref={indicatorRef}\n className={getIndicatorClasses(variant, color)}\n style={{ opacity: 0 }}\n />\n </div>\n </TabListContext.Provider>\n )\n}\n\nTabList.displayName = 'TabList'\n\n// --- Tab ---\n\nfunction TabItem({ children, className, ...props }: TabProps) {\n const { color, variant } = React.useContext(TabListContext)\n const colors = tabColorMap[color]\n const focusClasses = `outline-none data-[focus-visible]:outline-2 data-[focus-visible]:outline-offset-2 data-[focus-visible]:${colors.outline}`\n\n return (\n <AriaTab\n {...props}\n className={({ isSelected, isDisabled }) =>\n tw(\n variant === 'oval' ? TAB_OVAL_BASE_CLASSES : TAB_BASE_CLASSES,\n focusClasses,\n isSelected\n ? (variant === 'oval' ? TAB_OVAL_SELECTED_CLASSES : `${colors.text} font-bold`)\n : `${TAB_INACTIVE_CLASSES} data-[hovered]:text-neutral-700`,\n isDisabled && TAB_DISABLED_CLASSES,\n className\n )\n }\n >\n {children}\n </AriaTab>\n )\n}\n\nTabItem.displayName = 'Tab'\n\n// --- TabPanels ---\n\nfunction TabPanels({ children, className }: TabPanelsProps) {\n return <div className={tw('mt-2', className)}>{children}</div>\n}\n\nTabPanels.displayName = 'TabPanels'\n\n// --- TabPanel ---\n\nfunction TabPanelItem({ children, className, ...props }: TabPanelProps) {\n return (\n <AriaTabPanel {...props} className={className}>\n {children}\n </AriaTabPanel>\n )\n}\n\nTabPanelItem.displayName = 'TabPanel'\n\n// --- Export ---\n\nexport const Tabs = {\n Group: TabGroup,\n List: TabList,\n Tab: TabItem,\n Panels: TabPanels,\n Panel: TabPanelItem\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"},{"name":"utils/use-tab-indicator.ts","content":"import * as React from 'react'\n\n// --- Shared Tab Styles ---\n\nexport const TAB_BASE_CLASSES = 'relative px-4 py-2 text-sm cursor-default transition-colors duration-200'\nexport const TAB_INACTIVE_CLASSES = 'text-neutral-500 font-medium'\nexport const TAB_DISABLED_CLASSES = 'opacity-50 cursor-not-allowed'\n\n// --- Tab Variant ---\n\nexport type TabVariant = 'underline' | 'oval'\n\n// --- Tab Color Map ---\n\nexport const tabColorMap = {\n primary: {\n text: 'text-primary-500',\n bg: 'bg-primary-500',\n outline: 'outline-primary-500'\n },\n neutral: {\n text: 'text-neutral-800',\n bg: 'bg-neutral-800',\n outline: 'outline-neutral-800'\n }\n} as const\n\nexport type TabColor = keyof typeof tabColorMap\n\n// --- Variant-Specific Styles ---\n\nexport const tabListContainerClasses: Record<TabVariant, string> = {\n underline: 'relative',\n oval: 'relative inline-flex bg-neutral-100 rounded-full p-1'\n}\n\nexport const tabListInnerClasses: Record<TabVariant, string> = {\n underline: 'flex space-x-1 border-b border-neutral-200',\n oval: 'flex space-x-1'\n}\n\nexport function getIndicatorClasses(variant: TabVariant, color: TabColor): string {\n if (variant === 'underline') {\n return `absolute bottom-0 h-[3px] rounded-full ${tabColorMap[color].bg}`\n }\n return `absolute inset-y-1 rounded-full ${tabColorMap[color].bg}`\n}\n\nexport const TAB_OVAL_BASE_CLASSES = 'relative z-10 px-4 py-1.5 text-sm cursor-default transition-colors duration-200 rounded-full'\nexport const TAB_OVAL_SELECTED_CLASSES = 'text-white font-semibold'\n\n// --- Animated Indicator Hook ---\n\nexport function useAnimatedIndicator(\n containerRef: React.RefObject<HTMLDivElement | null>,\n activeSelector: string,\n observedAttribute: string\n) {\n const indicatorRef = React.useRef<HTMLDivElement>(null)\n const hasAnimated = React.useRef(false)\n\n const updateIndicator = React.useCallback(() => {\n const container = containerRef.current\n const indicator = indicatorRef.current\n if (!container || !indicator) return\n\n const activeTab = container.querySelector(activeSelector) as HTMLElement | null\n if (activeTab) {\n const containerRect = container.getBoundingClientRect()\n const tabRect = activeTab.getBoundingClientRect()\n\n if (!hasAnimated.current) {\n indicator.style.transition = 'none'\n hasAnimated.current = true\n } else {\n indicator.style.transition = 'left 200ms ease, width 200ms ease'\n }\n\n indicator.style.left = `${tabRect.left - containerRect.left}px`\n indicator.style.width = `${tabRect.width}px`\n indicator.style.opacity = '1'\n } else {\n indicator.style.opacity = '0'\n }\n }, [containerRef, activeSelector])\n\n React.useEffect(() => {\n updateIndicator()\n\n const container = containerRef.current\n if (!container) return\n\n const observer = new MutationObserver(updateIndicator)\n observer.observe(container, { attributes: true, subtree: true, attributeFilter: [observedAttribute] })\n\n const resizeObserver = new ResizeObserver(updateIndicator)\n resizeObserver.observe(container)\n\n return () => {\n observer.disconnect()\n resizeObserver.disconnect()\n }\n }, [containerRef, updateIndicator, observedAttribute])\n\n return indicatorRef\n}\n"}]},{"name":"link-tabs","files":[{"name":"components/link-tabs.tsx","content":"import * as React from 'react'\nimport { NavLink } from 'react-router'\nimport type { To } from 'react-router'\nimport { tw } from '../utils/tw'\nimport {\n TAB_BASE_CLASSES,\n TAB_DISABLED_CLASSES,\n TAB_INACTIVE_CLASSES,\n TAB_OVAL_BASE_CLASSES,\n TAB_OVAL_SELECTED_CLASSES,\n tabColorMap,\n tabListContainerClasses,\n tabListInnerClasses,\n getIndicatorClasses,\n useAnimatedIndicator\n} from '../utils/use-tab-indicator'\nimport type { TabColor, TabVariant } from '../utils/use-tab-indicator'\n\n// --- Types ---\n\nexport type LinkTabListProps = {\n children: React.ReactNode\n className?: string\n color?: TabColor\n variant?: TabVariant\n}\n\nexport type LinkTabProps = {\n to: To\n children: React.ReactNode\n className?: string\n disabled?: boolean\n replace?: boolean\n end?: boolean\n}\n\n// --- LinkTabList ---\n\nconst LinkTabListContext = React.createContext<{ color: TabColor; variant: TabVariant }>({ color: 'primary', variant: 'underline' })\n\nfunction LinkTabList({ children, className, color = 'primary', variant = 'underline' }: LinkTabListProps) {\n const containerRef = React.useRef<HTMLDivElement>(null)\n const indicatorRef = useAnimatedIndicator(containerRef, '[aria-current=\"page\"]', 'aria-current')\n\n return (\n <LinkTabListContext.Provider value={{ color, variant }}>\n <div ref={containerRef} className={tabListContainerClasses[variant]}>\n <div role=\"tablist\" className={tw(tabListInnerClasses[variant], className)}>\n {children}\n </div>\n <div\n ref={indicatorRef}\n className={getIndicatorClasses(variant, color)}\n style={{ opacity: 0 }}\n />\n </div>\n </LinkTabListContext.Provider>\n )\n}\n\nLinkTabList.displayName = 'LinkTabList'\n\n// --- LinkTab ---\n\nfunction LinkTab({ to, children, className, disabled, replace, end }: LinkTabProps) {\n const { color, variant } = React.useContext(LinkTabListContext)\n const colors = tabColorMap[color]\n const baseClasses = variant === 'oval' ? TAB_OVAL_BASE_CLASSES : TAB_BASE_CLASSES\n const focusClasses = `outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:${colors.outline}`\n\n if (disabled) {\n return (\n <span\n role=\"tab\"\n aria-disabled=\"true\"\n className={tw(\n baseClasses,\n 'outline-none',\n TAB_INACTIVE_CLASSES,\n TAB_DISABLED_CLASSES,\n className\n )}\n >\n {children}\n </span>\n )\n }\n\n return (\n <NavLink\n to={to}\n replace={replace}\n end={end}\n role=\"tab\"\n className={({ isActive }) =>\n tw(\n baseClasses,\n focusClasses,\n isActive\n ? (variant === 'oval' ? TAB_OVAL_SELECTED_CLASSES : `${colors.text} font-bold`)\n : `${TAB_INACTIVE_CLASSES} hover:text-neutral-700`,\n className\n )\n }\n >\n {children}\n </NavLink>\n )\n}\n\nLinkTab.displayName = 'LinkTab'\n\n// --- Export ---\n\nexport const LinkTabs = {\n List: LinkTabList,\n Tab: LinkTab\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"},{"name":"utils/use-tab-indicator.ts","content":"import * as React from 'react'\n\n// --- Shared Tab Styles ---\n\nexport const TAB_BASE_CLASSES = 'relative px-4 py-2 text-sm cursor-default transition-colors duration-200'\nexport const TAB_INACTIVE_CLASSES = 'text-neutral-500 font-medium'\nexport const TAB_DISABLED_CLASSES = 'opacity-50 cursor-not-allowed'\n\n// --- Tab Variant ---\n\nexport type TabVariant = 'underline' | 'oval'\n\n// --- Tab Color Map ---\n\nexport const tabColorMap = {\n primary: {\n text: 'text-primary-500',\n bg: 'bg-primary-500',\n outline: 'outline-primary-500'\n },\n neutral: {\n text: 'text-neutral-800',\n bg: 'bg-neutral-800',\n outline: 'outline-neutral-800'\n }\n} as const\n\nexport type TabColor = keyof typeof tabColorMap\n\n// --- Variant-Specific Styles ---\n\nexport const tabListContainerClasses: Record<TabVariant, string> = {\n underline: 'relative',\n oval: 'relative inline-flex bg-neutral-100 rounded-full p-1'\n}\n\nexport const tabListInnerClasses: Record<TabVariant, string> = {\n underline: 'flex space-x-1 border-b border-neutral-200',\n oval: 'flex space-x-1'\n}\n\nexport function getIndicatorClasses(variant: TabVariant, color: TabColor): string {\n if (variant === 'underline') {\n return `absolute bottom-0 h-[3px] rounded-full ${tabColorMap[color].bg}`\n }\n return `absolute inset-y-1 rounded-full ${tabColorMap[color].bg}`\n}\n\nexport const TAB_OVAL_BASE_CLASSES = 'relative z-10 px-4 py-1.5 text-sm cursor-default transition-colors duration-200 rounded-full'\nexport const TAB_OVAL_SELECTED_CLASSES = 'text-white font-semibold'\n\n// --- Animated Indicator Hook ---\n\nexport function useAnimatedIndicator(\n containerRef: React.RefObject<HTMLDivElement | null>,\n activeSelector: string,\n observedAttribute: string\n) {\n const indicatorRef = React.useRef<HTMLDivElement>(null)\n const hasAnimated = React.useRef(false)\n\n const updateIndicator = React.useCallback(() => {\n const container = containerRef.current\n const indicator = indicatorRef.current\n if (!container || !indicator) return\n\n const activeTab = container.querySelector(activeSelector) as HTMLElement | null\n if (activeTab) {\n const containerRect = container.getBoundingClientRect()\n const tabRect = activeTab.getBoundingClientRect()\n\n if (!hasAnimated.current) {\n indicator.style.transition = 'none'\n hasAnimated.current = true\n } else {\n indicator.style.transition = 'left 200ms ease, width 200ms ease'\n }\n\n indicator.style.left = `${tabRect.left - containerRect.left}px`\n indicator.style.width = `${tabRect.width}px`\n indicator.style.opacity = '1'\n } else {\n indicator.style.opacity = '0'\n }\n }, [containerRef, activeSelector])\n\n React.useEffect(() => {\n updateIndicator()\n\n const container = containerRef.current\n if (!container) return\n\n const observer = new MutationObserver(updateIndicator)\n observer.observe(container, { attributes: true, subtree: true, attributeFilter: [observedAttribute] })\n\n const resizeObserver = new ResizeObserver(updateIndicator)\n resizeObserver.observe(container)\n\n return () => {\n observer.disconnect()\n resizeObserver.disconnect()\n }\n }, [containerRef, updateIndicator, observedAttribute])\n\n return indicatorRef\n}\n"}]},{"name":"stepper","files":[{"name":"components/stepper.tsx","content":"import * as React from 'react'\nimport { Icon } from './icon'\nimport { tw } from '../utils/tw'\n\n// ── Types ────────────────────────────────────────────────────────────────────────\n\nexport type StepperGroupProps = {\n children: React.ReactNode\n className?: string\n activeStep?: number\n orientation?: 'horizontal' | 'vertical'\n}\n\nexport type StepProps = {\n children: React.ReactNode\n className?: string\n completed?: boolean\n error?: boolean\n}\n\nexport type StepLabelProps = {\n children: React.ReactNode\n className?: string\n}\n\n// ── Context ─────────────────────────────────────────────────────────────────────\n\nconst StepperContext = React.createContext<{\n activeStep: number\n orientation: 'horizontal' | 'vertical'\n totalSteps: number\n}>({\n activeStep: 0,\n orientation: 'horizontal',\n totalSteps: 0\n})\n\nconst StepContext = React.createContext<{\n completed: boolean\n active: boolean\n error: boolean\n index: number\n}>({\n completed: false,\n active: false,\n error: false,\n index: 0\n})\n\n// ── StepperGroup ────────────────────────────────────────────────────────────────\n\nfunction StepperGroup({ children, className, activeStep = 0, orientation = 'horizontal' }: StepperGroupProps) {\n const steps = React.Children.toArray(children).filter((child) => React.isValidElement(child))\n const totalSteps = steps.length\n\n return (\n <StepperContext.Provider value={{ activeStep, orientation, totalSteps }}>\n <div\n className={tw('flex', orientation === 'horizontal' ? 'flex-row' : 'flex-col', className)}\n role=\"group\"\n aria-label=\"Progress\"\n >\n {steps.map((step, idx) => {\n if (React.isValidElement<StepProps & { index?: number; isLast?: boolean }>(step)) {\n return React.cloneElement(step, {\n ...step.props,\n index: idx,\n isLast: idx === totalSteps - 1,\n key: idx\n })\n }\n return step\n })}\n </div>\n </StepperContext.Provider>\n )\n}\n\nStepperGroup.displayName = 'StepperGroup'\n\n// ── Step ────────────────────────────────────────────────────────────────────────\n\nfunction Step({\n children,\n className,\n completed = false,\n error = false,\n index = 0,\n isLast\n}: StepProps & { index?: number; isLast?: boolean }) {\n const { activeStep, orientation } = React.useContext(StepperContext)\n const active = activeStep === index\n\n const firstConnectorColor = error ? 'bg-negative-500' : completed || active ? 'bg-primary-500' : 'bg-neutral-300'\n const lastConnectorColor = error ? 'bg-negative-500' : completed ? 'bg-primary-500' : 'bg-neutral-300'\n\n if (orientation === 'horizontal') {\n return (\n <StepContext.Provider value={{ completed, active, error, index }}>\n <div className={tw('flex flex-row items-start flex-1 relative', className)}>\n {index !== 0 ? (\n <div className=\"flex flex-1 items-center pt-4 w-1/2 absolute left-0\">\n <div className={tw('h-[2px] w-full transition-colors', firstConnectorColor)} />\n </div>\n ) : null}\n {!isLast ? (\n <div className=\"flex flex-1 items-center pt-4 w-1/2 absolute right-0\">\n <div className={tw('h-[2px] w-full transition-colors', lastConnectorColor)} />\n </div>\n ) : null}\n <div className=\"flex flex-col items-center w-full relative\">{children}</div>\n </div>\n </StepContext.Provider>\n )\n }\n\n return (\n <StepContext.Provider value={{ completed, active, error, index }}>\n <div className={tw('relative flex flex-col min-h-10', className)}>\n {index !== 0 ? (\n <div className=\"flex flex-1 justify-center pl-4 h-1/2 absolute top-0\">\n <div className={tw('w-[2px] h-full transition-colors', firstConnectorColor)} />\n </div>\n ) : null}\n {!isLast ? (\n <div className=\"flex flex-1 items-center pl-4 h-1/2 absolute bottom-0\">\n <div className={tw('w-[2px] h-full transition-colors', lastConnectorColor)} />\n </div>\n ) : null}\n <div className=\"flex flex-row w-full relative\">{children}</div>\n </div>\n </StepContext.Provider>\n )\n}\n\nStep.displayName = 'Step'\n\n// ── StepLabel ───────────────────────────────────────────────────────────────────\n\nfunction StepLabel({ children, className }: StepLabelProps) {\n const { completed, active, error, index } = React.useContext(StepContext)\n const { orientation } = React.useContext(StepperContext)\n\n const iconBg = error\n ? 'bg-negative-500 border-negative-500 text-white'\n : completed\n ? 'bg-primary-500 border-primary-500 text-white'\n : active\n ? 'bg-primary-500 border-primary-500 text-white'\n : 'bg-white border-neutral-300 text-neutral-600'\n\n const textColor = error ? 'text-negative-600' : active ? 'text-neutral-900' : 'text-neutral-500'\n\n return (\n <div className={tw('flex items-center', orientation === 'horizontal' ? 'flex-col text-center' : 'gap-3', className)}>\n <div\n className={tw('flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2 transition-colors', iconBg)}\n aria-current={active ? 'step' : undefined}\n >\n {completed ? (\n <Icon name=\"check\" className=\"h-4 w-4\" aria-hidden=\"true\" />\n ) : (\n <span className=\"text-sm font-semibold\">{index + 1}</span>\n )}\n </div>\n <div className={tw('flex flex-col', orientation === 'horizontal' ? 'mt-2' : '')}>\n <span className={tw('text-sm font-medium', textColor)}>{children}</span>\n </div>\n </div>\n )\n}\n\nStepLabel.displayName = 'StepLabel'\n\n// ── Export ───────────────────────────────────────────────────────────────────────\n\nexport const Stepper = {\n Group: StepperGroup,\n Step,\n Label: StepLabel\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"avatar","files":[{"name":"components/avatar.tsx","content":"import * as React from 'react'\nimport { tw } from '../utils/tw'\n\nexport type AvatarShape = 'circle' | 'rounded' | 'square'\n\nexport type AvatarProps = {\n alt?: string\n children?: React.ReactNode\n className?: string\n src?: string\n shape?: AvatarShape\n}\n\nexport const Avatar = React.forwardRef<HTMLDivElement, AvatarProps>(\n ({ alt, children, className, src, shape = 'circle' }, ref) => {\n const [imgError, setImgError] = React.useState(false)\n\n const showImage = src && !imgError\n const childrenToRender = children || (alt && stringAvatar(alt))\n\n return (\n <div\n ref={ref}\n className={tw(\n 'size-10 relative inline-flex items-center justify-center overflow-hidden font-medium text-white select-none',\n !showImage && 'bg-neutral-300',\n shape === 'circle' && 'rounded-full',\n shape === 'rounded' && 'rounded',\n shape === 'square' && 'rounded-none',\n className\n )}\n >\n {showImage ? (\n <img className=\"size-full absolute inset-0 object-cover\" alt={alt} src={src} onError={() => setImgError(true)} />\n ) : null}\n {!showImage && childrenToRender ? <span>{childrenToRender}</span> : null}\n </div>\n )\n }\n)\n\nAvatar.displayName = 'Avatar'\n\nfunction stringAvatar(name: string) {\n const names = name.split(' ')\n const first = names[0]\n const last = names[names.length - 1]\n\n if (!first) return ''\n\n const initials = names.length === 1 ? first[0] ?? '' : `${first[0] ?? ''}${last?.[0] ?? ''}`\n\n return initials.toUpperCase()\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"chip","files":[{"name":"components/chip.tsx","content":"import * as React from 'react'\nimport { tw } from '../utils/tw'\nimport { Icon } from './icon'\n\n// ── Props ─────────────────────────────────────────────────────────────────────\n\nexport type ChipProps = {\n children: React.ReactNode\n onRemove?: () => void\n isDisabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n color?: 'stone' | 'info' | 'green' | 'yellow' | 'orange' | 'red'\n colorMode?: 'light' | 'dark'\n className?: string\n style?: React.CSSProperties\n}\n\nexport type ChipButtonProps = {\n children: React.ReactNode\n disabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n color?: 'stone' | 'info' | 'green' | 'yellow' | 'orange' | 'red'\n colorMode?: 'light' | 'dark'\n className?: string\n} & Omit<React.ComponentProps<'button'>, 'disabled' | 'size' | 'color'>\n\n// ── Chip ──────────────────────────────────────────────────────────────────────\n\nexport function Chip(props: ChipProps) {\n const {\n children,\n onRemove,\n isDisabled = false,\n size = 'md',\n color = 'stone',\n colorMode = 'light',\n className,\n style\n } = props\n\n const colorClasses = colorMode === 'dark' ? darkColorClasses : lightColorClasses\n\n return (\n <div\n className={tw(\n 'inline-flex items-center rounded-full font-normal transition-colors max-w-full',\n sizeClasses[size],\n !isDisabled && !style && colorClasses[color],\n isDisabled && 'opacity-50 cursor-not-allowed',\n className\n )}\n style={style}\n >\n <span className=\"truncate\">{children}</span>\n {onRemove && (\n <button\n type=\"button\"\n onClick={onRemove}\n disabled={isDisabled}\n className={tw(\n 'inline-flex items-center justify-center rounded-full transition-colors translate-x-1',\n removeBtnSizeClasses[size],\n !isDisabled && 'hover:bg-black/10',\n isDisabled && 'cursor-not-allowed'\n )}\n aria-label=\"Remove\"\n tabIndex={-1}\n >\n <Icon name=\"x-mark\" className={removeIconSizeClasses[size]} aria-hidden=\"true\" />\n </button>\n )}\n </div>\n )\n}\n\n// ── ChipButton ────────────────────────────────────────────────────────────────\n\nexport const ChipButton = React.forwardRef<HTMLButtonElement, ChipButtonProps>((props, ref) => {\n const { children, size = 'md', color = 'stone', colorMode = 'light', className, disabled, ...rest } = props\n\n const colorClasses = colorMode === 'dark' ? darkHoverColorClasses : lightHoverColorClasses\n\n return (\n <button\n ref={ref}\n type=\"button\"\n disabled={disabled}\n className={tw(\n 'inline-flex items-center rounded-full font-normal transition-colors',\n sizeClasses[size],\n !disabled && colorClasses[color],\n disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',\n className\n )}\n {...rest}\n >\n {children}\n </button>\n )\n})\n\nChipButton.displayName = 'ChipButton'\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nconst sizeClasses = {\n sm: 'px-2 py-0.5 text-xs',\n md: 'px-2.5 py-1 text-sm',\n lg: 'px-3 py-1.5 text-base'\n}\n\nconst lightColorClasses = {\n stone: 'bg-stone-100 text-stone-700',\n info: 'bg-info-100 text-info-700',\n green: 'bg-green-100 text-green-700',\n yellow: 'bg-yellow-100 text-yellow-700',\n orange: 'bg-orange-100 text-orange-700',\n red: 'bg-red-100 text-red-700'\n}\n\nconst darkColorClasses = {\n stone: 'bg-stone-600 text-white',\n info: 'bg-info-600 text-white',\n green: 'bg-green-600 text-white',\n yellow: 'bg-yellow-600 text-white',\n orange: 'bg-orange-600 text-white',\n red: 'bg-red-600 text-white'\n}\n\nconst lightHoverColorClasses = {\n stone: 'bg-stone-100 text-stone-700 hover:bg-stone-200',\n info: 'bg-info-100 text-info-700 hover:bg-info-200',\n green: 'bg-green-100 text-green-700 hover:bg-green-200',\n yellow: 'bg-yellow-100 text-yellow-700 hover:bg-yellow-200',\n orange: 'bg-orange-100 text-orange-700 hover:bg-orange-200',\n red: 'bg-red-100 text-red-700 hover:bg-red-200'\n}\n\nconst darkHoverColorClasses = {\n stone: 'bg-stone-600 text-white hover:bg-stone-700',\n info: 'bg-info-600 text-white hover:bg-info-700',\n green: 'bg-green-600 text-white hover:bg-green-700',\n yellow: 'bg-yellow-600 text-white hover:bg-yellow-700',\n orange: 'bg-orange-600 text-white hover:bg-orange-700',\n red: 'bg-red-600 text-white hover:bg-red-700'\n}\n\nconst removeBtnSizeClasses = {\n sm: 'h-4 w-4 -m-0.5 p-0.5',\n md: 'h-5 w-5 -m-1 p-1',\n lg: 'h-6 w-6 -m-1.5 p-1.5'\n}\n\nconst removeIconSizeClasses = {\n sm: 'h-2 w-2',\n md: 'h-3 w-3',\n lg: 'h-4 w-4'\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"enum-chip","files":[{"name":"components/enum-chip.tsx","content":"import * as React from 'react'\nimport { Chip, type ChipProps } from './chip'\nimport { getColor, stringToNumber } from '../utils/enum-colors'\n\nexport type EnumChipProps = {\n options: string[] | Readonly<string[]>\n value: string\n enumLabels?: { [key: string]: string }\n} & Omit<ChipProps, 'children'>\n\nexport function EnumChip({ options, enumLabels, value, style, ...props }: EnumChipProps) {\n const colorIndex = Object.values(options).indexOf(value)\n const colorSeed = colorIndex >= 0 ? colorIndex : stringToNumber(value)\n const color = getColor(colorSeed)\n\n const label = value ? (enumLabels ? enumLabels[value] : value) : ''\n\n return (\n <Chip\n {...props}\n style={{\n backgroundColor: `${color}19`,\n color: color,\n ...style\n }}\n >\n {label}\n </Chip>\n )\n}\n"},{"name":"components/chip.tsx","content":"import * as React from 'react'\nimport { tw } from '../utils/tw'\nimport { Icon } from './icon'\n\n// ── Props ─────────────────────────────────────────────────────────────────────\n\nexport type ChipProps = {\n children: React.ReactNode\n onRemove?: () => void\n isDisabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n color?: 'stone' | 'info' | 'green' | 'yellow' | 'orange' | 'red'\n colorMode?: 'light' | 'dark'\n className?: string\n style?: React.CSSProperties\n}\n\nexport type ChipButtonProps = {\n children: React.ReactNode\n disabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n color?: 'stone' | 'info' | 'green' | 'yellow' | 'orange' | 'red'\n colorMode?: 'light' | 'dark'\n className?: string\n} & Omit<React.ComponentProps<'button'>, 'disabled' | 'size' | 'color'>\n\n// ── Chip ──────────────────────────────────────────────────────────────────────\n\nexport function Chip(props: ChipProps) {\n const {\n children,\n onRemove,\n isDisabled = false,\n size = 'md',\n color = 'stone',\n colorMode = 'light',\n className,\n style\n } = props\n\n const colorClasses = colorMode === 'dark' ? darkColorClasses : lightColorClasses\n\n return (\n <div\n className={tw(\n 'inline-flex items-center rounded-full font-normal transition-colors max-w-full',\n sizeClasses[size],\n !isDisabled && !style && colorClasses[color],\n isDisabled && 'opacity-50 cursor-not-allowed',\n className\n )}\n style={style}\n >\n <span className=\"truncate\">{children}</span>\n {onRemove && (\n <button\n type=\"button\"\n onClick={onRemove}\n disabled={isDisabled}\n className={tw(\n 'inline-flex items-center justify-center rounded-full transition-colors translate-x-1',\n removeBtnSizeClasses[size],\n !isDisabled && 'hover:bg-black/10',\n isDisabled && 'cursor-not-allowed'\n )}\n aria-label=\"Remove\"\n tabIndex={-1}\n >\n <Icon name=\"x-mark\" className={removeIconSizeClasses[size]} aria-hidden=\"true\" />\n </button>\n )}\n </div>\n )\n}\n\n// ── ChipButton ────────────────────────────────────────────────────────────────\n\nexport const ChipButton = React.forwardRef<HTMLButtonElement, ChipButtonProps>((props, ref) => {\n const { children, size = 'md', color = 'stone', colorMode = 'light', className, disabled, ...rest } = props\n\n const colorClasses = colorMode === 'dark' ? darkHoverColorClasses : lightHoverColorClasses\n\n return (\n <button\n ref={ref}\n type=\"button\"\n disabled={disabled}\n className={tw(\n 'inline-flex items-center rounded-full font-normal transition-colors',\n sizeClasses[size],\n !disabled && colorClasses[color],\n disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',\n className\n )}\n {...rest}\n >\n {children}\n </button>\n )\n})\n\nChipButton.displayName = 'ChipButton'\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nconst sizeClasses = {\n sm: 'px-2 py-0.5 text-xs',\n md: 'px-2.5 py-1 text-sm',\n lg: 'px-3 py-1.5 text-base'\n}\n\nconst lightColorClasses = {\n stone: 'bg-stone-100 text-stone-700',\n info: 'bg-info-100 text-info-700',\n green: 'bg-green-100 text-green-700',\n yellow: 'bg-yellow-100 text-yellow-700',\n orange: 'bg-orange-100 text-orange-700',\n red: 'bg-red-100 text-red-700'\n}\n\nconst darkColorClasses = {\n stone: 'bg-stone-600 text-white',\n info: 'bg-info-600 text-white',\n green: 'bg-green-600 text-white',\n yellow: 'bg-yellow-600 text-white',\n orange: 'bg-orange-600 text-white',\n red: 'bg-red-600 text-white'\n}\n\nconst lightHoverColorClasses = {\n stone: 'bg-stone-100 text-stone-700 hover:bg-stone-200',\n info: 'bg-info-100 text-info-700 hover:bg-info-200',\n green: 'bg-green-100 text-green-700 hover:bg-green-200',\n yellow: 'bg-yellow-100 text-yellow-700 hover:bg-yellow-200',\n orange: 'bg-orange-100 text-orange-700 hover:bg-orange-200',\n red: 'bg-red-100 text-red-700 hover:bg-red-200'\n}\n\nconst darkHoverColorClasses = {\n stone: 'bg-stone-600 text-white hover:bg-stone-700',\n info: 'bg-info-600 text-white hover:bg-info-700',\n green: 'bg-green-600 text-white hover:bg-green-700',\n yellow: 'bg-yellow-600 text-white hover:bg-yellow-700',\n orange: 'bg-orange-600 text-white hover:bg-orange-700',\n red: 'bg-red-600 text-white hover:bg-red-700'\n}\n\nconst removeBtnSizeClasses = {\n sm: 'h-4 w-4 -m-0.5 p-0.5',\n md: 'h-5 w-5 -m-1 p-1',\n lg: 'h-6 w-6 -m-1.5 p-1.5'\n}\n\nconst removeIconSizeClasses = {\n sm: 'h-2 w-2',\n md: 'h-3 w-3',\n lg: 'h-4 w-4'\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"utils/colors.ts","content":"export const COLORS = [\n '#f43f5e',\n '#3b82f6',\n '#22c55e',\n '#EAB308',\n '#8b5cf6',\n '#191970',\n '#f97316',\n '#6b7280',\n '#ec4899',\n '#06b6d4',\n '#84cc16',\n '#64748b',\n '#ffff00',\n '#6366f1',\n '#ff6347',\n '#737373',\n '#14b8a6',\n '#f59e0b',\n '#0ea5e9',\n '#ef4444',\n '#78716c',\n '#00ffff',\n '#d946ef',\n '#10b981',\n '#b22222',\n '#40e0d0',\n '#71717a',\n '#a855f7',\n '#7fff00'\n] as const\n\nexport function stringToNumber(str: string) {\n let h = 0x811c9dc5 // FNV offset basis\n for (const b of new TextEncoder().encode(str)) {\n h ^= b\n h = Math.imul(h, 0x01000193) // 16777619\n }\n return h >>> 0 // unsigned 32-bit\n}\n\nexport function getColor(seed: number) {\n return COLORS[seed % COLORS.length]\n}\n"},{"name":"utils/enum-colors.ts","content":"export { COLORS, getColor, stringToNumber } from './colors'\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"img","files":[{"name":"components/img.tsx","content":"import * as React from 'react'\nimport { tw } from '../utils/tw'\n\nexport type ImageFit = 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'\nexport type ImageShape = 'rounded' | 'rectangle' | 'circle'\n\nexport type ImgProps = {\n src: string\n alt?: string\n className?: string\n fit?: ImageFit\n shape?: ImageShape\n style?: React.CSSProperties\n onError?: React.ReactEventHandler<HTMLImageElement>\n}\n\nconst fitClasses: Record<ImageFit, string> = {\n cover: 'object-cover object-center',\n contain: 'object-contain',\n fill: 'object-fill',\n none: 'object-none',\n 'scale-down': 'object-scale-down'\n}\n\nconst shapeClasses: Record<ImageShape, string> = {\n rounded: 'rounded',\n rectangle: 'rounded-none',\n circle: 'rounded-full'\n}\n\nexport const Img = React.forwardRef<HTMLImageElement, ImgProps>(\n ({ src, alt, className, fit = 'cover', shape = 'rounded', style, onError }, ref) => {\n return (\n <img\n ref={ref}\n src={src}\n alt={alt}\n className={tw('w-full h-full', fitClasses[fit], shapeClasses[shape], className)}\n style={style}\n onError={onError}\n />\n )\n }\n)\n\nImg.displayName = 'Img'\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"labeled-value","files":[{"name":"components/labeled-value.tsx","content":"import * as React from 'react'\nimport { tw } from '../utils/tw'\nimport {\n type Iso,\n dateFns,\n timeFns,\n dateTimeFns,\n instantFns,\n zonedDateTimeFns,\n yearMonthFns,\n monthDayFns,\n durationFns\n} from 'iso-fns'\n\ntype LabeledValueBaseProps = {\n label?: React.ReactNode\n align?: 'start' | 'end'\n labelAlign?: 'start' | 'end'\n orientation?: 'horizontal' | 'vertical'\n emptyText?: React.ReactNode\n className?: string\n labelClassName?: string\n valueClassName?: string\n id?: string\n 'aria-labelledby'?: string\n 'aria-describedby'?: string\n}\n\ntype RangeValue<T> = { start: T; end: T }\n\ntype StringProps = {\n value: string | null | undefined\n formatOptions?: never\n}\n\ntype NumberProps = {\n value: number | RangeValue<number> | null | undefined\n formatOptions?: Intl.NumberFormatOptions\n}\n\ntype BooleanProps = {\n value: boolean | null | undefined\n formatOptions?: {\n trueLabel?: string\n falseLabel?: string\n }\n}\n\ntype ReactNodeProps = {\n value: React.ReactNode\n formatOptions?: never\n}\n\ntype StringListProps = {\n value: string[] | null | undefined\n formatOptions?: Intl.ListFormatOptions\n}\n\ntype DateProps = {\n value: Iso.Date | RangeValue<Iso.Date> | null | undefined\n formatOptions?: {\n dateFormat?: string\n }\n}\n\ntype TimeProps = {\n value: Iso.Time | RangeValue<Iso.Time> | null | undefined\n formatOptions?: {\n timeFormat?: string\n }\n}\n\ntype DateTimeProps = {\n value: Iso.DateTime | RangeValue<Iso.DateTime> | null | undefined\n formatOptions?: {\n dateTimeFormat?: string\n }\n}\n\ntype InstantProps = {\n value: Iso.Instant | RangeValue<Iso.Instant> | null | undefined\n formatOptions?: {\n dateTimeFormat?: string\n timeZone?: string\n }\n}\n\ntype ZonedDateTimeProps = {\n value: Iso.ZonedDateTime | RangeValue<Iso.ZonedDateTime> | null | undefined\n formatOptions?: {\n dateTimeFormat?: string\n }\n}\n\ntype YearMonthProps = {\n value: Iso.YearMonth | RangeValue<Iso.YearMonth> | null | undefined\n formatOptions?: {\n dateFormat?: string\n }\n}\n\ntype MonthDayProps = {\n value: Iso.MonthDay | RangeValue<Iso.MonthDay> | null | undefined\n formatOptions?: {\n dateFormat?: string\n }\n}\n\ntype DurationProps = {\n value: Iso.Duration | RangeValue<Iso.Duration> | null | undefined\n formatOptions?: {\n style?: 'short' | 'long'\n }\n}\n\ntype LabeledValueFormatProps =\n | StringProps\n | NumberProps\n | BooleanProps\n | ReactNodeProps\n | StringListProps\n | DateProps\n | TimeProps\n | DateTimeProps\n | InstantProps\n | ZonedDateTimeProps\n | YearMonthProps\n | MonthDayProps\n | DurationProps\n\nexport type LabeledValueProps = LabeledValueBaseProps & LabeledValueFormatProps\n\nexport const LabeledValue = React.forwardRef<HTMLDivElement, LabeledValueProps>(function LabeledValue(props, ref) {\n const {\n label,\n align = 'start',\n labelAlign = 'start',\n orientation = 'vertical',\n className,\n labelClassName,\n valueClassName,\n id,\n 'aria-labelledby': ariaLabelledBy,\n 'aria-describedby': ariaDescribedBy\n } = props\n\n const labelId = React.useId()\n const actualLabelId = id ?? labelId\n\n const formattedValue = FormattedValue(props)\n\n return (\n <div\n ref={ref}\n className={tw('relative', orientation === 'horizontal' ? 'flex items-baseline gap-4' : 'block', className)}\n >\n {label && (\n <LabeledValueLabel\n id={actualLabelId}\n className={tw(\n orientation === 'horizontal' ? 'flex-shrink-0' : 'mb-1',\n labelAlign === 'end' && 'text-right',\n labelClassName\n )}\n >\n {label}\n </LabeledValueLabel>\n )}\n\n <div\n className={tw(\n 'text-sm text-neutral-900 min-h-4 max-w-full break-words overflow-wrap-anywhere',\n align === 'end' && 'text-right',\n !formattedValue && 'text-neutral-400',\n valueClassName\n )}\n aria-labelledby={ariaLabelledBy ?? (label ? actualLabelId : undefined)}\n aria-describedby={ariaDescribedBy}\n >\n {formattedValue}\n </div>\n </div>\n )\n})\n\nLabeledValue.displayName = 'LabeledValue'\n\nfunction FormattedValue(props: LabeledValueFormatProps & { emptyText?: React.ReactNode }) {\n if (props.value == null || props.value === '') {\n return <>{props.emptyText}</>\n }\n\n if (React.isValidElement(props.value)) {\n return <>{props.value}</>\n }\n\n if (typeof props.value === 'boolean') {\n const boolProps = props as BooleanProps\n return <>{props.value ? (boolProps.formatOptions?.trueLabel ?? 'Yes') : (boolProps.formatOptions?.falseLabel ?? 'No')}</>\n }\n\n if (Array.isArray(props.value)) {\n return <ListValue {...(props as StringListProps)} />\n }\n\n if (typeof props.value === 'object' && props.value && 'start' in props.value && 'end' in props.value) {\n if (typeof props.value.start === 'number') {\n return <NumberValue {...(props as NumberProps)} />\n }\n if (typeof props.value.start === 'string' && dateFns.isValid(props.value.start)) {\n return <DateValue {...(props as DateProps)} />\n }\n if (typeof props.value.start === 'string' && timeFns.isValid(props.value.start)) {\n return <TimeValue {...(props as TimeProps)} />\n }\n if (typeof props.value.start === 'string' && dateTimeFns.isValid(props.value.start)) {\n return <DateTimeValue {...(props as DateTimeProps)} />\n }\n if (typeof props.value.start === 'string' && instantFns.isValid(props.value.start)) {\n return <InstantValue {...(props as InstantProps)} />\n }\n if (typeof props.value.start === 'string' && zonedDateTimeFns.isValid(props.value.start)) {\n return <ZonedDateTimeValue {...(props as ZonedDateTimeProps)} />\n }\n if (typeof props.value.start === 'string' && yearMonthFns.isValid(props.value.start)) {\n return <YearMonthValue {...(props as YearMonthProps)} />\n }\n if (typeof props.value.start === 'string' && monthDayFns.isValid(props.value.start)) {\n return <MonthDayValue {...(props as MonthDayProps)} />\n }\n if (typeof props.value.start === 'string' && durationFns.isValid(props.value.start)) {\n return <DurationValue {...(props as DurationProps)} />\n }\n }\n\n if (typeof props.value === 'string') {\n if (dateFns.isValid(props.value)) {\n return <DateValue {...(props as DateProps)} />\n }\n if (timeFns.isValid(props.value)) {\n return <TimeValue {...(props as TimeProps)} />\n }\n if (dateTimeFns.isValid(props.value)) {\n return <DateTimeValue {...(props as DateTimeProps)} />\n }\n if (instantFns.isValid(props.value)) {\n return <InstantValue {...(props as InstantProps)} />\n }\n if (zonedDateTimeFns.isValid(props.value)) {\n return <ZonedDateTimeValue {...(props as ZonedDateTimeProps)} />\n }\n if (yearMonthFns.isValid(props.value)) {\n return <YearMonthValue {...(props as YearMonthProps)} />\n }\n if (monthDayFns.isValid(props.value)) {\n return <MonthDayValue {...(props as MonthDayProps)} />\n }\n if (durationFns.isValid(props.value)) {\n return <DurationValue {...(props as DurationProps)} />\n }\n\n return <>{props.value}</>\n }\n\n if (typeof props.value === 'number') {\n return <NumberValue {...(props as NumberProps)} />\n }\n\n return <>{String(props.value)}</>\n}\n\nfunction NumberValue(props: NumberProps) {\n const formatter = React.useMemo(() => new Intl.NumberFormat('en-us', props.formatOptions ?? {}), [props.formatOptions])\n const formatted = React.useMemo(() => {\n if (props.value && typeof props.value === 'object') {\n return `${formatter.format(props.value.start)} – ${formatter.format(props.value.end)}`\n } else if (typeof props.value === 'number') {\n return formatter.format(props.value)\n } else {\n return props.value ?? null\n }\n }, [props.value, formatter])\n return <>{formatted}</>\n}\n\nfunction ListValue(props: StringListProps) {\n const formatter = React.useMemo(\n () => new Intl.ListFormat('en-us', props.formatOptions ?? { style: 'short' }),\n [props.formatOptions]\n )\n const formatted = React.useMemo(() => {\n if (props.value && props.value.length) {\n return formatter.format(props.value)\n } else {\n return null\n }\n }, [props.value, formatter])\n return <>{formatted}</>\n}\n\nfunction DateValue(props: DateProps) {\n const format = props.formatOptions?.dateFormat ?? 'M/d/yyyy'\n const formatted = React.useMemo(() => {\n if (props.value && typeof props.value === 'object') {\n return `${dateFns.format(props.value.start, format)} – ${dateFns.format(props.value.end, format)}`\n } else if (typeof props.value === 'string') {\n return dateFns.format(props.value, format)\n } else {\n return null\n }\n }, [props.value, format])\n return <>{formatted}</>\n}\n\nfunction TimeValue(props: TimeProps) {\n const format = props.formatOptions?.timeFormat ?? 'h:mm a'\n const formatted = React.useMemo(() => {\n if (props.value && typeof props.value === 'object') {\n return `${timeFns.format(props.value.start, format)} – ${timeFns.format(props.value.end, format)}`\n } else if (typeof props.value === 'string') {\n return timeFns.format(props.value, format)\n } else {\n return null\n }\n }, [props.value, format])\n return <>{formatted}</>\n}\n\nfunction DateTimeValue(props: DateTimeProps) {\n const format = props.formatOptions?.dateTimeFormat ?? 'M/d/yyyy h:mm a'\n const formatted = React.useMemo(() => {\n if (props.value && typeof props.value === 'object') {\n return `${dateTimeFns.format(props.value.start, format)} – ${dateTimeFns.format(props.value.end, format)}`\n } else if (typeof props.value === 'string') {\n return dateTimeFns.format(props.value, format)\n } else {\n return null\n }\n }, [props.value, format])\n return <>{formatted}</>\n}\n\nfunction InstantValue(props: InstantProps) {\n const timeZone = props.formatOptions?.timeZone\n const format = props.formatOptions?.dateTimeFormat ?? (timeZone ? 'M/d/yyyy h:mm a z' : 'M/d/yyyy h:mm a')\n\n const formatted = React.useMemo(() => {\n const formatInstant = (instant: Iso.Instant) => {\n if (timeZone) {\n const zonedDateTime = instantFns.toZonedDateTime(instant, timeZone)\n return zonedDateTimeFns.format(zonedDateTime, format)\n }\n return instantFns.chain(instant).toZonedDateTime('UTC').format(format).value()\n }\n\n if (props.value && typeof props.value === 'object') {\n return `${formatInstant(props.value.start)} – ${formatInstant(props.value.end)}`\n } else if (typeof props.value === 'string') {\n return formatInstant(props.value)\n } else {\n return null\n }\n }, [props.value, format, timeZone])\n return <>{formatted}</>\n}\n\nfunction ZonedDateTimeValue(props: ZonedDateTimeProps) {\n const format = props.formatOptions?.dateTimeFormat ?? 'M/d/yyyy h:mm a z'\n const formatted = React.useMemo(() => {\n if (props.value && typeof props.value === 'object') {\n return `${zonedDateTimeFns.format(props.value.start, format)} – ${zonedDateTimeFns.format(props.value.end, format)}`\n } else if (typeof props.value === 'string') {\n return zonedDateTimeFns.format(props.value, format)\n } else {\n return null\n }\n }, [props.value, format])\n return <>{formatted}</>\n}\n\nfunction YearMonthValue(props: YearMonthProps) {\n const format = props.formatOptions?.dateFormat ?? 'MMMM yyyy'\n const formatted = React.useMemo(() => {\n if (props.value && typeof props.value === 'object') {\n return `${yearMonthFns.format(props.value.start, format)} – ${yearMonthFns.format(props.value.end, format)}`\n } else if (typeof props.value === 'string') {\n return yearMonthFns.format(props.value, format)\n } else {\n return null\n }\n }, [props.value, format])\n return <>{formatted}</>\n}\n\nfunction MonthDayValue(props: MonthDayProps) {\n const format = props.formatOptions?.dateFormat ?? 'MMMM d'\n const formatted = React.useMemo(() => {\n if (props.value && typeof props.value === 'object') {\n return `${monthDayFns.format(props.value.start, format)} – ${monthDayFns.format(props.value.end, format)}`\n } else if (typeof props.value === 'string') {\n return monthDayFns.format(props.value, format)\n } else {\n return null\n }\n }, [props.value, format])\n return <>{formatted}</>\n}\n\nfunction DurationValue(props: DurationProps) {\n const durationFormat = props.formatOptions?.style ?? 'short'\n\n const formatted = React.useMemo(() => {\n const formatDuration = (duration: Iso.Duration) => {\n const fields = durationFns.getFields(duration)\n\n const partFormatters: Record<string, Record<string, string>> = {\n years: {\n long: Math.abs(fields.years) > 1 ? `${fields.years} years` : `${fields.years} year`,\n short: `${fields.years}y`\n },\n months: {\n long: Math.abs(fields.months) > 1 ? `${fields.months} months` : `${fields.months} month`,\n short: `${fields.months}M`\n },\n weeks: {\n long: Math.abs(fields.weeks) > 1 ? `${fields.weeks} weeks` : `${fields.weeks} week`,\n short: `${fields.weeks}w`\n },\n days: {\n long: Math.abs(fields.days) > 1 ? `${fields.days} days` : `${fields.days} day`,\n short: `${fields.days}d`\n },\n hours: {\n long: Math.abs(fields.hours) > 1 ? `${fields.hours} hours` : `${fields.hours} hour`,\n short: `${fields.hours}h`\n },\n minutes: {\n long: Math.abs(fields.minutes) > 1 ? `${fields.minutes} minutes` : `${fields.minutes} minute`,\n short: `${fields.minutes}m`\n },\n seconds: {\n long: Math.abs(fields.seconds) > 1 ? `${fields.seconds} seconds` : `${fields.seconds} second`,\n short: `${fields.seconds}s`\n },\n milliseconds: {\n long:\n Math.abs(fields.milliseconds) > 1 ? `${fields.milliseconds} milliseconds` : `${fields.milliseconds} millisecond`,\n short: `${fields.milliseconds}ms`\n }\n }\n\n const parts = Object.entries(partFormatters)\n .map(([key, value]) => (fields[key as keyof typeof fields] !== 0 ? value[durationFormat] : null))\n .filter(Boolean) as string[]\n if (!parts.length) {\n return durationFormat === 'long' ? '0 seconds' : '0s'\n } else {\n return parts.join(' ')\n }\n }\n\n if (props.value && typeof props.value === 'object') {\n return `${formatDuration(props.value.start)} – ${formatDuration(props.value.end)}`\n } else if (typeof props.value === 'string') {\n return formatDuration(props.value)\n } else {\n return null\n }\n }, [props.value, durationFormat])\n return <>{formatted}</>\n}\n\nexport function LabeledValueLabel(props: React.ComponentProps<'label'>) {\n return (\n <label\n {...props}\n className={tw('block uppercase text-xs tracking-widest font-normal text-neutral-400', props.className)}\n />\n )\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"inline-alert","files":[{"name":"components/inline-alert.tsx","content":"import * as React from 'react'\nimport { tw } from '../utils/tw'\nimport { Icon } from './icon'\n\nexport type InlineAlertProps = {\n children?: React.ReactNode\n variant: 'info' | 'positive' | 'notice' | 'negative' | 'neutral'\n icon?: React.ReactNode\n className?: string\n title?: React.ReactNode\n description?: React.ReactNode\n actions?: React.ReactNode\n}\n\nconst variantClasses = {\n info: 'bg-info-500/10 text-info-500 border-info-500/20',\n positive: 'bg-positive-50 text-positive-800 border-positive-200',\n notice: 'bg-notice-50 text-notice-800 border-notice-200',\n negative: 'bg-negative-50 text-negative-800 border-negative-200',\n neutral: 'bg-neutral-100 text-neutral-700 border-neutral-200'\n}\n\nconst defaultIconNames = {\n info: 'information-circle',\n positive: 'check',\n notice: 'exclamation-triangle',\n negative: 'x-mark',\n neutral: 'information-circle'\n} as const\n\nexport function InlineAlert({ children, actions, variant, icon, className, title, description }: InlineAlertProps) {\n const displayIcon = icon !== undefined ? icon : <Icon name={defaultIconNames[variant]} className=\"h-5 w-5\" />\n\n return (\n <div\n className={tw(\n 'rounded-md border px-4 py-3 flex gap-3 items-start justify-between text-left',\n variantClasses[variant],\n className\n )}\n role=\"alert\"\n >\n <div className=\"flex gap-3 items-start\">\n {displayIcon && <div className=\"flex-shrink-0\">{displayIcon}</div>}\n <div className=\"flex-1 gap-1\">\n {title && <div className={tw('font-semibold text-sm')}>{title}</div>}\n {description && <div className=\"text-sm\">{description}</div>}\n {children && <div className=\"text-sm\">{children}</div>}\n </div>\n </div>\n {actions}\n </div>\n )\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"pdf","files":[{"name":"components/pdf.tsx","content":"import React from 'react'\nimport { usePdf } from 'react-pdf-hook'\nimport { tw } from '../utils/tw'\nimport { usePagination } from '../utils/use-pagination'\nimport { Icon } from './icon'\nimport pdfjs from './helpers/pdf-dist.client'\n\nexport interface PdfProps extends React.ComponentProps<'div'> {\n src: string\n onDownload?: () => void\n}\n\nexport function Pdf({ src, onDownload, className, ...props }: PdfProps) {\n const containerRef = React.useRef<HTMLDivElement>(null)\n const [pageNumber, setPageNumber] = React.useState(0)\n\n const { pdf, canvasRef } = usePdf(pdfjs, src, pageNumber + 1, { width: containerRef.current?.clientWidth })\n\n const numPages = pdf?.numPages\n\n return (\n <div ref={containerRef} className={tw('relative overflow-hidden border border-neutral-300', className)} {...props}>\n <canvas\n ref={canvasRef}\n style={{ display: !pdf ? 'none' : undefined, maxWidth: '100%' }}\n className={tw('pdf-page', onDownload && 'cursor-pointer')}\n onClick={onDownload}\n />\n {!pdf || !containerRef.current?.clientWidth ? <div className=\"w-full h-full m-auto rounded\" /> : null}\n {numPages && numPages > 1 ? (\n <div className=\"flex absolute bottom-5 w-full justify-center\">\n <Pagination page={pageNumber} setPageNumber={setPageNumber} count={numPages} />\n </div>\n ) : null}\n </div>\n )\n}\n\nPdf.displayName = 'Pdf'\n\nfunction Pagination({ page, count, setPageNumber }: { page: number; count: number; setPageNumber: (n: number) => void }) {\n const pages = usePagination({ currentPage: page, pageCount: count })\n\n return (\n <div className=\"inline-flex items-center gap-1 pointer-events-auto\">\n <button\n type=\"button\"\n onClick={() => setPageNumber(page > 0 ? page - 1 : 0)}\n disabled={page === 0}\n className=\"p-1 rounded bg-white border border-neutral-300 hover:bg-neutral-50 disabled:opacity-50 disabled:cursor-not-allowed\"\n aria-label=\"Previous page\"\n >\n <Icon name=\"chevron-left\" className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n {pages.map((p, i) => (\n <button\n type=\"button\"\n key={p.readonly ? `ellipsis-${i}` : p.label}\n onClick={() => setPageNumber(p.readonly ? page : (p.value as number))}\n disabled={p.readonly}\n className={tw(\n 'min-w-[28px] bg-white h-7 px-2 text-xs font-medium rounded border transition-colors',\n p.selected\n ? 'bg-primary-500 text-white border-primary-500'\n : p.readonly\n ? 'border-transparent cursor-default'\n : 'border-neutral-300 hover:bg-neutral-50'\n )}\n >\n {p.label}\n </button>\n ))}\n\n <button\n type=\"button\"\n onClick={() => setPageNumber(page < count - 1 ? page + 1 : page)}\n disabled={page === count - 1}\n className=\"p-1 rounded bg-white border border-neutral-300 hover:bg-neutral-50 disabled:opacity-50 disabled:cursor-not-allowed\"\n aria-label=\"Next page\"\n >\n <Icon name=\"chevron-right\" className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n </div>\n )\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"},{"name":"utils/use-pagination.ts","content":"import * as React from 'react'\n\nexport interface PaginationPage {\n label: string\n value: number | null\n readonly: boolean\n selected: boolean\n}\n\nexport function usePagination({ currentPage, pageCount }: { currentPage: number; pageCount: number }): PaginationPage[] {\n return React.useMemo(() => {\n function getPage(i: number): PaginationPage {\n return { label: `${i + 1}`, value: i, readonly: false, selected: currentPage === i }\n }\n\n const first: PaginationPage = { label: '1', value: 0, readonly: false, selected: currentPage === 0 }\n const ellipsis: PaginationPage = { label: '...', value: null, readonly: true, selected: false }\n const last: PaginationPage = {\n label: `${pageCount}`,\n value: pageCount - 1,\n readonly: false,\n selected: currentPage === pageCount - 1\n }\n\n if (pageCount < 8) {\n return Array.from({ length: pageCount }, (_, i) => getPage(i))\n } else if (currentPage < 5) {\n const leading = Array.from({ length: 5 }, (_, i) => getPage(i))\n return [...leading, ellipsis, last]\n } else if (currentPage > pageCount - 5) {\n const trailing = Array.from({ length: 5 }, (_, i) => getPage(i + pageCount - 4))\n return [first, ellipsis, ...trailing.filter((p) => p.value! <= pageCount - 1)]\n } else {\n const siblings = Array.from({ length: 3 }, (_, i) => getPage(i + currentPage - 1))\n return [first, ellipsis, ...siblings, ellipsis, last]\n }\n }, [pageCount, currentPage])\n}\n"},{"name":"components/helpers/pdf-dist.client.ts","content":"// @ts-ignore\nimport pdfjs from '@bundled-es-modules/pdfjs-dist'\n\npdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`\n\nexport default pdfjs\n"}]},{"name":"container","files":[{"name":"components/container.tsx","content":"import * as React from 'react'\nimport { tw } from '../utils/tw'\n\n// ── Props ────────────────────────────────────────────────────────────────────\n\nexport type ContainerSize = 'lg' | 'md' | 'sm'\nexport type ContainerElevation = 0 | -1 | -2 | 'inherit'\n\nexport type ContainerProps = React.ComponentProps<'div'> & {\n size?: ContainerSize\n elevation?: ContainerElevation\n}\n\nexport type ContainerSectionProps = ContainerProps & {\n label?: React.ReactNode\n actions?: React.ReactNode\n}\n\nexport type SectionProps = {\n label?: React.ReactNode\n actions?: React.ReactNode\n children?: React.ReactNode\n}\n\n// ── Container ────────────────────────────────────────────────────────────────\n\nexport function Container({ size = 'lg', elevation = 0, className, ...props }: ContainerProps) {\n return <div {...props} className={tw(elevationClasses[String(elevation)], sizeClasses[size], className)} />\n}\n\nContainer.displayName = 'Container'\n\n// ── ContainerSection ─────────────────────────────────────────────────────────\n\nexport function ContainerSection({ label, actions, ...props }: ContainerSectionProps) {\n return (\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex items-start justify-between\">\n <h3 className=\"text-lg font-semibold\">{label}</h3>\n {actions}\n </div>\n <Container {...props} />\n </div>\n )\n}\n\nContainerSection.displayName = 'ContainerSection'\n\n// ── Section ──────────────────────────────────────────────────────────────────\n\nexport function Section({ label, actions, children }: SectionProps) {\n return (\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex items-start justify-between\">\n <h3 className=\"text-lg font-semibold\">{label}</h3>\n {actions}\n </div>\n <div>{children}</div>\n </div>\n )\n}\n\nSection.displayName = 'Section'\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\nconst sizeClasses: Record<ContainerSize, string> = {\n lg: 'p-6 rounded-lg',\n md: 'p-3 rounded',\n sm: 'p-2 rounded-sm'\n}\n\nconst elevationClasses: Record<string, string> = {\n '0': 'bg-white border border-neutral-200',\n '-1': 'bg-neutral-50 border border-neutral-200',\n '-2': 'bg-neutral-100 border border-neutral-300',\n inherit: ''\n}\n\nexport function getSizeClassesForContainer(size: ContainerSize) {\n return sizeClasses[size]\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"disclosure","files":[{"name":"components/disclosure.tsx","content":"import * as React from 'react'\nimport {\n Disclosure as AriaDisclosure,\n DisclosurePanel as AriaDisclosurePanel,\n DisclosureGroup,\n DisclosureStateContext,\n Button,\n Heading\n} from 'react-aria-components'\nimport { tw } from '../utils/tw'\nimport { type ContainerSize } from './container'\nimport { Icon } from './icon'\n\nexport type DisclosureProps = {\n id?: string | number\n title: React.ReactNode\n children: React.ReactNode\n className?: string\n size?: ContainerSize\n isDisabled?: boolean\n defaultOpen?: boolean\n isOpen?: boolean\n onOpenChange?: (isOpen: boolean) => void\n}\n\nfunction DisclosureRaw({\n id,\n title,\n className,\n defaultOpen,\n isOpen,\n onOpenChange,\n children,\n size = 'md',\n isDisabled = false\n}: DisclosureProps) {\n const paddingX = size === 'lg' ? 'px-6' : size === 'md' ? 'px-3' : 'px-2'\n const paddingBottom = size === 'lg' ? 'pb-6' : size === 'md' ? 'pb-3' : 'pb-2'\n\n return (\n <AriaDisclosure\n id={id}\n defaultExpanded={defaultOpen}\n isExpanded={isOpen}\n onExpandedChange={onOpenChange}\n isDisabled={isDisabled}\n >\n <DisclosureHeader size={size} isDisabled={isDisabled} className={className}>\n {title}\n </DisclosureHeader>\n <AriaDisclosurePanel className=\"h-(--disclosure-panel-height) motion-safe:transition-[height] duration-200 ease-in-out overflow-clip mt-1\">\n <div className={tw(paddingX, paddingBottom)}>{children}</div>\n </AriaDisclosurePanel>\n </AriaDisclosure>\n )\n}\n\nfunction DisclosureHeader({\n size,\n isDisabled,\n className,\n children\n}: {\n size: ContainerSize\n isDisabled: boolean\n className?: string\n children: React.ReactNode\n}) {\n const { isExpanded } = React.useContext(DisclosureStateContext)!\n\n return (\n <Heading>\n <Button\n slot=\"trigger\"\n className={tw(\n 'flex w-full items-center justify-start text-left transition-colors gap-2 font-medium',\n 'hover:bg-neutral-50',\n size === 'lg' ? 'px-4 py-2.5' : size === 'md' ? 'px-2.5 py-1.5' : 'px-2 py-1',\n 'rounded-lg',\n isDisabled && 'opacity-50 cursor-not-allowed hover:bg-transparent',\n className\n )}\n >\n <Icon\n name=\"chevron-right\"\n className={tw(\n 'shrink-0 transition-transform duration-200 ease-in-out',\n size === 'md' || size === 'lg' ? 'w-4 h-4' : 'w-3 h-3',\n isExpanded && 'rotate-90',\n isDisabled ? 'text-neutral-300' : 'text-neutral-500'\n )}\n />\n <span\n className={tw(\n size === 'lg'\n ? 'text-lg font-semibold'\n : size === 'md'\n ? 'text-base font-medium'\n : 'text-sm font-medium',\n isDisabled ? 'text-neutral-400' : 'text-neutral-700'\n )}\n >\n {children}\n </span>\n </Button>\n </Heading>\n )\n}\n\nexport const Disclosure = Object.assign(DisclosureRaw, {\n Group: DisclosureGroup\n})\n\nObject.defineProperty(DisclosureRaw, 'name', { value: 'Disclosure' })\n"},{"name":"components/container.tsx","content":"import * as React from 'react'\nimport { tw } from '../utils/tw'\n\n// ── Props ────────────────────────────────────────────────────────────────────\n\nexport type ContainerSize = 'lg' | 'md' | 'sm'\nexport type ContainerElevation = 0 | -1 | -2 | 'inherit'\n\nexport type ContainerProps = React.ComponentProps<'div'> & {\n size?: ContainerSize\n elevation?: ContainerElevation\n}\n\nexport type ContainerSectionProps = ContainerProps & {\n label?: React.ReactNode\n actions?: React.ReactNode\n}\n\nexport type SectionProps = {\n label?: React.ReactNode\n actions?: React.ReactNode\n children?: React.ReactNode\n}\n\n// ── Container ────────────────────────────────────────────────────────────────\n\nexport function Container({ size = 'lg', elevation = 0, className, ...props }: ContainerProps) {\n return <div {...props} className={tw(elevationClasses[String(elevation)], sizeClasses[size], className)} />\n}\n\nContainer.displayName = 'Container'\n\n// ── ContainerSection ─────────────────────────────────────────────────────────\n\nexport function ContainerSection({ label, actions, ...props }: ContainerSectionProps) {\n return (\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex items-start justify-between\">\n <h3 className=\"text-lg font-semibold\">{label}</h3>\n {actions}\n </div>\n <Container {...props} />\n </div>\n )\n}\n\nContainerSection.displayName = 'ContainerSection'\n\n// ── Section ──────────────────────────────────────────────────────────────────\n\nexport function Section({ label, actions, children }: SectionProps) {\n return (\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex items-start justify-between\">\n <h3 className=\"text-lg font-semibold\">{label}</h3>\n {actions}\n </div>\n <div>{children}</div>\n </div>\n )\n}\n\nSection.displayName = 'Section'\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\nconst sizeClasses: Record<ContainerSize, string> = {\n lg: 'p-6 rounded-lg',\n md: 'p-3 rounded',\n sm: 'p-2 rounded-sm'\n}\n\nconst elevationClasses: Record<string, string> = {\n '0': 'bg-white border border-neutral-200',\n '-1': 'bg-neutral-50 border border-neutral-200',\n '-2': 'bg-neutral-100 border border-neutral-300',\n inherit: ''\n}\n\nexport function getSizeClassesForContainer(size: ContainerSize) {\n return sizeClasses[size]\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"}]},{"name":"use-pagination","files":[{"name":"utils/use-pagination.ts","content":"import * as React from 'react'\n\nexport interface PaginationPage {\n label: string\n value: number | null\n readonly: boolean\n selected: boolean\n}\n\nexport function usePagination({ currentPage, pageCount }: { currentPage: number; pageCount: number }): PaginationPage[] {\n return React.useMemo(() => {\n function getPage(i: number): PaginationPage {\n return { label: `${i + 1}`, value: i, readonly: false, selected: currentPage === i }\n }\n\n const first: PaginationPage = { label: '1', value: 0, readonly: false, selected: currentPage === 0 }\n const ellipsis: PaginationPage = { label: '...', value: null, readonly: true, selected: false }\n const last: PaginationPage = {\n label: `${pageCount}`,\n value: pageCount - 1,\n readonly: false,\n selected: currentPage === pageCount - 1\n }\n\n if (pageCount < 8) {\n return Array.from({ length: pageCount }, (_, i) => getPage(i))\n } else if (currentPage < 5) {\n const leading = Array.from({ length: 5 }, (_, i) => getPage(i))\n return [...leading, ellipsis, last]\n } else if (currentPage > pageCount - 5) {\n const trailing = Array.from({ length: 5 }, (_, i) => getPage(i + pageCount - 4))\n return [first, ellipsis, ...trailing.filter((p) => p.value! <= pageCount - 1)]\n } else {\n const siblings = Array.from({ length: 3 }, (_, i) => getPage(i + currentPage - 1))\n return [first, ellipsis, ...siblings, ellipsis, last]\n }\n }, [pageCount, currentPage])\n}\n"}]},{"name":"form","files":[{"name":"components/form.tsx","content":"import {\n type FormProps as ReactRouterFormProps,\n Form as ReactRouterForm,\n data,\n type ClientActionFunctionArgs\n} from 'react-router'\nimport { HeadlessForm } from '@maestro-js/form'\nimport { serverOnly$ } from 'vite-env-only/macros'\n\nfunction _Form({ children, ...props }: ReactRouterFormProps) {\n return (\n <HeadlessForm as={ReactRouterForm} {...props}>\n {children}\n </HeadlessForm>\n )\n}\n\nexport const Form = Object.assign(_Form, {\n HiddenInput: HeadlessForm.HiddenInput,\n HiddenFileInput: HeadlessForm.HiddenFileInput,\n HiddenSelect: HeadlessForm.HiddenSelect,\n useControlledState: HeadlessForm.useControlledState,\n useTopLevelFormValidation: HeadlessForm.useTopLevelFormValidation,\n useReset: HeadlessForm.useReset,\n clientSafeParse: HeadlessForm.parseFormDataClientSide,\n serverSafeParse: HeadlessForm.parseFormData,\n validationError: serverOnly$(validationError) as typeof validationError,\n customValidationError: serverOnly$(customValidationError) as typeof customValidationError,\n composeValidators: HeadlessForm.composeValidators,\n FormError\n})\n\nasync function validationError(\n {\n issues,\n formData,\n formId\n }: {\n issues: { path?: ((string | number | symbol) | { key: string | number | symbol })[] | undefined; message: string }[]\n formData: FormData\n formId?: string | null\n },\n init: RequestInit = {}\n) {\n console.error(issues)\n return data(\n {\n success: false as const,\n formSubmissionError: {\n issues: issues.map((i) => ({\n path: i.path?.map((p) => (p && typeof p === 'object' && 'key' in p ? p.key : p))?.join('.') ?? '',\n message: i.message\n })),\n formId: formId ?? formData.get('__formId')\n }\n },\n { status: 422, ...init }\n )\n}\n\nasync function customValidationError(\n { message, formData }: { message: string; formData: FormData },\n init?: number | ResponseInit\n) {\n console.error(message)\n return data(\n {\n success: false as const,\n formSubmissionError: {\n issues: [{ path: '', message: message }],\n formId: formData.get('__formId')\n }\n },\n { status: typeof init === 'number' ? init : 422, ...(typeof init === 'object' ? init : {}) }\n )\n}\n\nfunction FormError({ formId, children }: { formId?: string; children(message: string): React.ReactNode }) {\n const serverErrors = HeadlessForm.useServerErrors(formId)\n const formErrors = serverErrors.filter((s) => !s.path)\n return formErrors.length ? <>{children(formErrors[0]!.message)}</> : null\n}\n"}]},{"name":"toast","files":[{"name":"components/toast.tsx","content":"import * as React from 'react'\nimport { flushSync } from 'react-dom'\nimport {\n Text,\n UNSTABLE_Toast as AriaToast,\n UNSTABLE_ToastContent as ToastContent,\n UNSTABLE_ToastQueue as ToastQueue,\n UNSTABLE_ToastRegion as AriaToastRegion\n} from 'react-aria-components'\nimport { tw } from '../utils/tw'\nimport { Icon } from './icon'\nimport { HeadlessButton } from './helpers/headless-button'\n\nexport interface Toast {\n title: string\n description?: string\n variant?: 'info' | 'positive' | 'warning' | 'negative' | 'notice'\n}\n\nconst queue = new ToastQueue<Toast>({\n wrapUpdate(fn) {\n if ('startViewTransition' in document) {\n document.startViewTransition(() => {\n flushSync(fn)\n })\n } else {\n fn()\n }\n }\n})\n\nexport function showToast(\n toast: Toast,\n options?: {\n /** Handler that is called when the toast is closed, either by the user or after a timeout. */\n onClose?: () => void\n /** A timeout to automatically close the toast after, in milliseconds. */\n timeout?: number | null\n }\n) {\n return queue.add(toast, {\n ...options,\n timeout: typeof options?.timeout === 'undefined' ? 5_000 : (options?.timeout ?? undefined)\n })\n}\n\nexport function ToastRegion({ toast }: { toast?: Toast | null }) {\n return (\n <>\n <style>\n {`\n::view-transition-new(.toast):only-child {\n animation: maestro-toast-slide-in 400ms;\n}\n\n::view-transition-old(.toast):only-child {\n animation: maestro-toast-slide-out 400ms;\n animation-fill-mode: forwards;\n}\n\n@keyframes maestro-toast-slide-out {\n to {\n translate: 0 100%;\n opacity: 0;\n visibility: hidden;\n }\n}\n\n@keyframes maestro-toast-slide-in {\n from {\n translate: 0 100%;\n opacity: 0;\n }\n}\n `}\n </style>\n <AriaToastRegion\n className=\"fixed right-4 bottom-4 sm:right-6 sm:bottom-6 z-[6000] flex flex-col gap-2 pointer-events-none outline-none\"\n queue={queue}\n >\n {({ toast }) => <ToastItem toast={toast} />}\n </AriaToastRegion>\n {toast ? <DeclaredToast toast={toast} /> : null}\n </>\n )\n}\n\nToastRegion.displayName = 'ToastRegion'\n\n// --- Internal Components ---\n\nfunction ToastItem({ toast }: { toast: { content: Toast; key: string } }) {\n const variant = toast.content.variant || 'info'\n const hasDescription = !!toast.content.description\n\n return (\n <AriaToast\n toast={toast}\n style={{ viewTransitionName: toast.key } as React.CSSProperties}\n className={tw(\n 'pointer-events-auto',\n 'flex gap-3 rounded-md border px-4 py-3 shadow-lg',\n 'min-w-[300px] max-w-md',\n '[view-transition-class:toast]',\n hasDescription ? 'items-start' : 'items-center',\n variantClasses[variant]\n )}\n >\n <Icon name={variantIconNames[variant]} className={tw('h-5 w-5 shrink-0', hasDescription && 'mt-0.5')} />\n <ToastContent className=\"flex flex-1 flex-col min-w-0\">\n <Text slot=\"title\" className=\"font-semibold text-sm\">\n {toast.content.title}\n </Text>\n {toast.content.description && (\n <Text slot=\"description\" className=\"mt-1 text-sm\">\n {toast.content.description}\n </Text>\n )}\n </ToastContent>\n <HeadlessButton\n onClick={() => queue.close(toast.key)}\n className={tw(\n 'flex-shrink-0 rounded p-1 transition-colors',\n 'hover:bg-black/5 focus:outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',\n variantCloseOutlineClasses[variant]\n )}\n >\n <Icon name=\"x-mark\" className=\"h-5 w-5\" />\n </HeadlessButton>\n </AriaToast>\n )\n}\n\nfunction DeclaredToast({\n toast,\n options\n}: {\n toast: Toast\n options?: {\n onClose?: () => void\n timeout?: number | null\n }\n}) {\n React.useEffect(() => {\n setTimeout(() => {\n showToast(toast, options)\n }, 0)\n }, [toast, options])\n return null\n}\n\n// --- Variant Maps ---\n\nconst variantClasses = {\n info: 'bg-info-500/10 text-info-500 border-info-500/20',\n positive: 'bg-positive-50 text-positive-800 border-positive-200',\n warning: 'bg-notice-50 text-notice-800 border-notice-200',\n negative: 'bg-negative-50 text-negative-800 border-negative-200',\n notice: 'bg-neutral-100 text-neutral-700 border-neutral-200'\n}\n\nconst variantIconNames = {\n info: 'information-circle',\n positive: 'check',\n warning: 'exclamation-triangle',\n negative: 'x-mark',\n notice: 'information-circle'\n} as const\n\nconst variantCloseOutlineClasses = {\n info: 'focus-visible:outline-info-500',\n positive: 'focus-visible:outline-positive-600',\n warning: 'focus-visible:outline-notice-600',\n negative: 'focus-visible:outline-negative-600',\n notice: 'focus-visible:outline-neutral-400'\n}\n"},{"name":"components/icon.tsx","content":"import * as React from 'react'\n// @ts-ignore\nimport href from '../icons/sprite.svg'\nimport type { IconName } from '~/icons/types'\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface IconProps extends React.ComponentProps<'svg'> {\n name: IconName\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(({ className, name, ...props }, ref) => {\n return (\n <svg ref={ref} viewBox=\"0 0 24 24\" className={className} {...props}>\n <use href={`${href}#${name}`} />\n </svg>\n )\n})\n\nIcon.displayName = 'Icon'\n"},{"name":"components/helpers/headless-button.tsx","content":"import { composeRefs } from '../../utils/compose-refs'\nimport { type WithRenderProps, useRenderProps } from '../../utils/use-render-props'\n\nimport React from 'react'\nimport { usePress } from '@react-aria/interactions'\nimport { mergeProps } from '@react-aria/utils'\n\nexport const HeadlessButton = React.forwardRef<\n HTMLButtonElement,\n WithRenderProps<React.ComponentProps<'button'>, { isPressed: boolean }>\n>((rawProps, externalRef) => {\n const myRef = React.useRef<HTMLButtonElement>(null)\n const { isPressed, pressProps } = usePress({ ref: myRef, isDisabled: rawProps.disabled })\n const renderProps = React.useMemo(\n () => ({\n isPressed\n }),\n [isPressed]\n )\n\n const props = useRenderProps(mergeProps(pressProps, rawProps), renderProps, ['children', 'style', 'className'])\n const ref = React.useMemo(() => composeRefs(myRef, externalRef), [myRef, externalRef])\n\n return (\n <button\n type=\"button\"\n {...props}\n {...(props.disabled || props['data-disabled'] ? { ['data-disabled']: true } : {})}\n {...((isPressed && !props.disabled) || props['data-pressed'] ? { ['data-pressed']: true } : {})}\n aria-pressed={isPressed && !props.disabled}\n ref={ref}\n />\n )\n})\n\nHeadlessButton.displayName = 'HeadlessButton'\n"},{"name":"utils/compose-refs.ts","content":"import React from 'react'\n\ntype PossibleRef<T> = React.Ref<T> | undefined\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n if (typeof ref === 'function') {\n ref(value)\n } else if (ref !== null && ref !== undefined) {\n ;(ref as React.MutableRefObject<T>).current = value\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nexport function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n return (node: T) => refs.forEach((ref) => setRef(ref, node))\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nexport function useComposedRefs<T>(...refs: PossibleRef<T>[]) {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return React.useCallback(composeRefs(...refs), refs)\n}\n"},{"name":"utils/tw.ts","content":"import { twMerge } from 'tailwind-merge'\nimport { composeRenderProps } from 'react-aria-components'\n\nexport function tw(...inputs: unknown[]) {\n return twMerge(inputs.flat(Infinity).filter(Boolean).join(' '))\n}\n\nexport function composeTailwindRenderProps<T>(\n className: string | ((renderProps: T) => string) | undefined,\n tailwind: string\n): string | ((renderProps: T) => string) {\n return composeRenderProps(className, (prev) => tw(tailwind, prev))\n}\n"},{"name":"utils/use-render-props.ts","content":"import React from 'react'\n\ntype Prettify<T> = { [K in keyof T]: T[K] } & {}\n\ntype DefaultRenderPropKeys<Props> = Extract<'children' | 'className' | 'style', keyof Props>\n\ntype MaybeRenderProp<Value, RenderProps> = Value | ((renderProps: RenderProps) => Value)\n\nexport type WithRenderProps<\n Props extends Record<string, any>,\n RenderProps,\n Keys extends keyof Props = DefaultRenderPropKeys<Props>\n> = Prettify<{\n [K in keyof Props]: K extends Keys ? MaybeRenderProp<Props[K], RenderProps> : Props[K]\n}>\n\nexport function useRenderProps<\n Props extends Record<string, any>,\n RenderProps,\n Keys extends keyof Props = DefaultRenderPropKeys<Props>\n>(props: Props, renderProps: RenderProps, keys: Keys[]) {\n const withRenderProps = React.useMemo(\n () =>\n Object.fromEntries(\n keys\n .map((key) => {\n if (key in props && typeof props[key] === 'function') {\n return [key, props[key](renderProps)]\n } else {\n return []\n }\n })\n .filter((k) => k.length)\n ),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [...keys, renderProps, props, ...keys.map((key) => props[key])]\n )\n return { ...props, ...withRenderProps }\n}\n"}]}]
|