@photon-ai/pho-ui 1.0.1 → 1.2.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/dist/chunks/IconCheck-D2WuculQ.js +11 -0
- package/dist/chunks/IconCheck-D2WuculQ.js.map +1 -0
- package/dist/chunks/card-9cGnv-VD.js +56 -0
- package/dist/chunks/card-9cGnv-VD.js.map +1 -0
- package/dist/chunks/{close-button-8f-oRJXt.js → close-button-Bq9f-H15.js} +256 -181
- package/dist/chunks/close-button-Bq9f-H15.js.map +1 -0
- package/dist/chunks/{dialog-Bgb2Z1_v.js → dialog-DolPKFGS.js} +2 -2
- package/dist/chunks/{dialog-Bgb2Z1_v.js.map → dialog-DolPKFGS.js.map} +1 -1
- package/dist/chunks/{input-BSJGBUbf.js → input-CxS7ndpb.js} +7 -7
- package/dist/chunks/input-CxS7ndpb.js.map +1 -0
- package/dist/chunks/input-group-D6dlma8k.js +85 -0
- package/dist/chunks/input-group-D6dlma8k.js.map +1 -0
- package/dist/chunks/{menu-Dan0oRvL.js → menu-BSXds3Rt.js} +2 -2
- package/dist/chunks/{menu-Dan0oRvL.js.map → menu-BSXds3Rt.js.map} +1 -1
- package/dist/chunks/settings-B0vhLoMi.js +134 -0
- package/dist/chunks/settings-B0vhLoMi.js.map +1 -0
- package/dist/chunks/sidebar-BCqq58hV.js +177 -0
- package/dist/chunks/sidebar-BCqq58hV.js.map +1 -0
- package/dist/chunks/use-press-pulse-CMwPTOR2.js +44 -0
- package/dist/chunks/use-press-pulse-CMwPTOR2.js.map +1 -0
- package/dist/components/button.js +1 -1
- package/dist/components/card.js +6 -0
- package/dist/components/checkbox.js +33 -28
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/context-menu.js +2 -2
- package/dist/components/dialog.js +1 -1
- package/dist/components/field.js +17 -17
- package/dist/components/field.js.map +1 -1
- package/dist/components/input-group.js +5 -0
- package/dist/components/input.js +1 -1
- package/dist/components/menu.js +1 -1
- package/dist/components/select.js +83 -39
- package/dist/components/select.js.map +1 -1
- package/dist/components/skeleton.js +18 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/index.js +44 -27
- package/dist/primitives.js +1 -1
- package/dist/sections/settings.js +8 -0
- package/dist/sections/sidebar.js +8 -5
- package/dist/src/components/card/card.d.ts +75 -0
- package/dist/src/components/card/index.d.ts +1 -0
- package/dist/src/components/checkbox/checkbox.d.ts +5 -1
- package/dist/src/components/input-group/index.d.ts +1 -0
- package/dist/src/components/input-group/input-group.d.ts +57 -0
- package/dist/src/components/select/index.d.ts +1 -1
- package/dist/src/components/select/select.d.ts +29 -10
- package/dist/src/components/skeleton/index.d.ts +1 -0
- package/dist/src/components/skeleton/skeleton.d.ts +11 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/sections/settings/index.d.ts +1 -0
- package/dist/src/sections/settings/settings.d.ts +170 -0
- package/dist/src/sections/sidebar/index.d.ts +1 -1
- package/dist/src/sections/sidebar/sidebar.d.ts +25 -6
- package/dist/src/utils/index.d.ts +1 -0
- package/dist/src/utils/use-press-pulse.d.ts +37 -0
- package/dist/utils.js +12 -8
- package/package.json +17 -1
- package/src/styles/theme.css +18 -4
- package/dist/chunks/close-button-8f-oRJXt.js.map +0 -1
- package/dist/chunks/input-BSJGBUbf.js.map +0 -1
- package/dist/chunks/sidebar-Buhva5xa.js +0 -164
- package/dist/chunks/sidebar-Buhva5xa.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"close-button-Bq9f-H15.js","names":[],"sources":["../../src/components/button/button.tsx","../../src/components/button/close-button.tsx"],"sourcesContent":["import {\n isValidElement,\n useEffect,\n useMemo,\n useRef,\n useState,\n type AnchorHTMLAttributes,\n type ButtonHTMLAttributes,\n type ComponentType,\n type FC,\n type MouseEvent as ReactMouseEvent,\n type ReactNode,\n type Ref,\n} from \"react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { cn } from \"../../utils/cn\";\nimport { resolveVariant, type VariantMap } from \"../../utils/resolve-variant\";\nimport { isReactComponent } from \"../../utils/is-react-component\";\nimport { useLinkComponent } from \"../../utils/link-provider\";\nimport { Spinner } from \"../spinner/spinner\";\n\n/**\n * Design notes:\n * - TWO visual axes: `variant` is the surface (filled / outlined / ghost /\n * effect) and `color` is the semantic ink (default / destructive). Dialog\n * confirms use the default `effect`; danger-zone triggers are `ghost` +\n * `destructive`. `effect` is the marketing pill: same-hue 1.5px outer rim\n * plus inset sheen that springs on hover.\n * - `iconReveal` is a separate boolean: on hover, prefix/suffix icons spring\n * in from collapsed — works with any variant, independent of `effect`.\n * - Press scale (Motion `whileTap` → 0.97) is shared by every variant; only\n * `effect` also springs its inset sheen on hover/press, and replays a\n * one-shot sheen glint when it leaves `disabled`/`loading` (wake).\n * - THREE sizes (`sm` / `md` / `lg`, default `md`). No `xs` / `xl`.\n * - `prefix` / `suffix` are generic slots (icon, spinner, count…), not\n * icon-only props. `svgOnly` makes an icon button and *requires* an\n * `aria-label`. `loading` is declarative. Effect’s rim/sheen are\n * box-shadow layers (not layout `border`).\n * - Navigation belongs to `ButtonLink`; an inline text link is an anchor.\n */\n\nconst COMMON_ROOT = cn(\n // Transition colors/shadows in CSS; transform (press scale) is owned by Motion.\n \"group relative inline-flex h-max cursor-pointer items-center justify-center whitespace-nowrap outline-pho-brand transition-[color,background-color,border-color,opacity,box-shadow,outline-color] duration-100 ease-linear focus-visible:outline-2 focus-visible:outline-offset-2\",\n // Disabled (native `disabled` on <button>, `aria-disabled` on links).\n \"disabled:cursor-not-allowed disabled:opacity-50 aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:pointer-events-none\",\n);\n\n/**\n * Size axis — padding, text size, and icon-only (svgOnly) padding.\n *\n * Icon-side tuck (LiftKit-derived optical correction): a side that carries an\n * icon gets its padding divided by the golden ratio — an icon's rounded form\n * brings its own whitespace, so the full text-edge padding reads as a hole.\n * LiftKit: `--button-padX-sideWithIcon: calc(font-size / 1.618)`. Applied to\n * Pho's paddings: sm 14px/φ ≈ 8px · md 16px/φ ≈ 10px · lg 20px/φ ≈ 12px,\n * selected structurally via `:has()` on the slot markers (`data-icon` is\n * `prefix`, `suffix`, or `loading` — the spinner is an icon too).\n */\nexport const BUTTON_SIZES = {\n sm: {\n classes: cn(\n \"gap-1 px-3.5 py-1.5 text-base font-medium data-svg-only:p-2\",\n \"has-[[data-icon=prefix]]:pl-2 has-[[data-icon=loading]]:pl-2 has-[[data-icon=suffix]]:pr-2\",\n ),\n description: \"Small — compact rows, tables, and toolbars\",\n },\n md: {\n classes: cn(\n \"gap-1 px-4 py-2 text-base font-medium data-svg-only:p-2.5\",\n \"has-[[data-icon=prefix]]:pl-2.5 has-[[data-icon=loading]]:pl-2.5 has-[[data-icon=suffix]]:pr-2.5\",\n ),\n description: \"Medium — the default\",\n },\n lg: {\n classes: cn(\n \"gap-1.5 px-5 py-2.5 text-md font-medium data-svg-only:p-3\",\n \"has-[[data-icon=prefix]]:pl-3 has-[[data-icon=loading]]:pl-3 has-[[data-icon=suffix]]:pr-3\",\n ),\n description: \"Large — prominent and marketing actions\",\n },\n} satisfies VariantMap;\n\n/** Shape axis — corner treatment. Pills are the photon.codes default. */\nexport const BUTTON_SHAPES = {\n rounded: {\n classes: \"rounded-full\",\n description: \"Pill — the default; every button on photon.codes\",\n },\n square: {\n classes: \"rounded-base\",\n description: \"Gently rounded corners for dense app chrome\",\n },\n circle: {\n classes: \"rounded-full\",\n description: \"Circular — pair with svgOnly for round icon buttons\",\n },\n} satisfies VariantMap;\n\n/**\n * At `lg`, square buttons add squircle smoothing on top of `rounded-base`\n * (12px) — the same treatment as Input `lg`, so 44px buttons and fields share\n * a corner language. 12/44 ≈ 0.272 — LiftKit's half-step decimal (√φ − 1).\n */\nconst SQUARE_LG_RADIUS = \"squircle\";\n\n/**\n * Optical pad for `effect`: the 1.5px outer rim is a box-shadow (no layout\n * cost), so without this the painted height reads ~3px taller than filled /\n * outlined / ghost. Subtract 1.5px from each side so fill+rim matches siblings.\n */\nconst EFFECT_OPTICAL_PAD: Record<ButtonSize, string> = {\n sm: \"py-[4.5px] data-svg-only:p-[6.5px]\",\n md: \"py-[6.5px] data-svg-only:p-[8.5px]\",\n lg: \"py-[8.5px] data-svg-only:p-[10.5px]\",\n};\n\n/** Surface axis — how the control is drawn. `effect` is the default. */\nexport const BUTTON_VARIANTS = {\n effect: {\n classes: \"\",\n description:\n \"Default — marketing pill with same-hue outer rim and inset sheen\",\n },\n filled: {\n classes: \"\",\n description: \"Solid fill without sheen\",\n },\n outlined: {\n classes: \"\",\n description: \"Hairline ring — supporting action\",\n },\n ghost: {\n classes: \"\",\n description: \"Borderless — quiet / inline action\",\n },\n} satisfies VariantMap;\n\n/** Color axis — semantic ink. `default` is the default. */\nexport const BUTTON_COLORS = {\n default: {\n classes: \"\",\n description: \"Neutral / brand contrast\",\n },\n destructive: {\n classes: \"\",\n description: \"Destructive intent (delete, discard)\",\n },\n} satisfies VariantMap;\n\n/**\n * Surface × color looks. Kept as a matrix so each cell can own its tokens\n * (filled destructive is solid red; ghost destructive is red text only).\n */\nconst BUTTON_LOOKS: Record<\n keyof typeof BUTTON_VARIANTS,\n Record<keyof typeof BUTTON_COLORS, string>\n> = {\n // Icons inherit the label ink (currentColor) — no separate opacity/tint.\n // The rim replicates photon.codes' Framer CTA border: 1.5px in the fill hue\n // at 62% alpha, reading as a soft same-hue edge (the site has NO outer\n // glow — verified against live computed styles). Sheen geometry is CSS\n // vars that Motion springs on hover (see Button). All edges are shadows —\n // never a layout `border` — so height stays paired with other variants.\n effect: {\n default: cn(\n \"squircle overflow-hidden bg-pho-brand-solid text-pho-on-brand transition-none\",\n \"hover:bg-pho-brand-solid data-loading:bg-pho-brand-solid\",\n \"[--pho-rim:color-mix(in_oklab,var(--background-color-pho-brand-solid)_62%,transparent)]\",\n \"[--pho-sheen:light-dark(rgba(255,255,255,0.5),rgba(0,0,0,0.12))]\",\n \"shadow-[0_0_0_1.5px_var(--pho-rim),inset_0_0_0_1px_var(--border-color-pho-on-brand),inset_0_var(--pho-sheen-y,0px)_var(--pho-sheen-blur,5px)_var(--pho-sheen-spread,2px)_var(--pho-sheen)]\",\n ),\n destructive: cn(\n \"squircle overflow-hidden bg-pho-error-solid text-white outline-pho-error transition-none\",\n \"hover:bg-pho-error-solid data-loading:bg-pho-error-solid\",\n \"[--pho-rim:color-mix(in_oklab,var(--background-color-pho-error-solid)_62%,transparent)]\",\n \"[--pho-sheen:light-dark(rgba(255,255,255,0.45),rgba(0,0,0,0.18))]\",\n \"shadow-[0_0_0_1.5px_var(--pho-rim),inset_0_0_0_1px_oklch(1_0_0/0.2),inset_0_var(--pho-sheen-y,0px)_var(--pho-sheen-blur,5px)_var(--pho-sheen-spread,2px)_var(--pho-sheen)]\",\n ),\n },\n filled: {\n default:\n \"bg-pho-brand-solid text-pho-on-brand hover:bg-pho-brand-solid-hover data-loading:bg-pho-brand-solid-hover\",\n destructive:\n \"bg-pho-error-solid text-white outline-pho-error hover:bg-pho-error-solid-hover data-loading:bg-pho-error-solid-hover\",\n },\n outlined: {\n default:\n \"bg-pho-primary text-pho-secondary ring-1 ring-pho-secondary ring-inset hover:bg-pho-primary-hover hover:text-pho-secondary-hover data-loading:bg-pho-primary-hover\",\n destructive:\n \"bg-pho-primary text-pho-error outline-pho-error ring-1 ring-pho-error ring-inset hover:bg-pho-error-primary hover:text-pho-error-hover data-loading:bg-pho-error-primary\",\n },\n ghost: {\n default:\n \"text-pho-secondary hover:bg-pho-ghost-hover hover:text-pho-secondary-hover data-loading:bg-pho-ghost-hover\",\n destructive:\n \"text-pho-error outline-pho-error hover:bg-pho-error-primary hover:text-pho-error-hover data-loading:bg-pho-error-primary\",\n },\n};\n\nexport type ButtonSize = keyof typeof BUTTON_SIZES;\nexport type ButtonShape = keyof typeof BUTTON_SHAPES;\nexport type ButtonVariant = keyof typeof BUTTON_VARIANTS;\nexport type ButtonColor = keyof typeof BUTTON_COLORS;\n\n/** Compose the full class string for a button — exported for reuse/testing. */\nexport function buttonVariants(opts?: {\n variant?: ButtonVariant;\n color?: ButtonColor;\n size?: ButtonSize;\n shape?: ButtonShape;\n className?: string;\n}): string {\n const {\n variant = \"effect\",\n color = \"default\",\n size = \"md\",\n shape: shapeOpt = \"rounded\",\n className,\n } = opts ?? {};\n // Effect pills are always fully rounded — shape is ignored.\n const shape = variant === \"effect\" ? \"rounded\" : shapeOpt;\n const look =\n BUTTON_LOOKS[variant as keyof typeof BUTTON_LOOKS]?.[\n color as keyof (typeof BUTTON_LOOKS)[\"effect\"]\n ] ?? \"\";\n return cn(\n COMMON_ROOT,\n resolveVariant(BUTTON_SHAPES, shape),\n shape === \"square\" && size === \"lg\" && SQUARE_LG_RADIUS,\n resolveVariant(BUTTON_SIZES, size),\n // Outer rim is a 1.5px box-shadow (no layout cost). Shrink padding by the\n // same amount so fill+rim paints at the same visual size as filled /\n // outlined / ghost siblings of the same `size`.\n variant === \"effect\" && EFFECT_OPTICAL_PAD[size],\n look,\n className,\n );\n}\n\n/** An icon/element slot rendered before or after the label. */\ntype Slottable = ReactNode | FC<{ className?: string }>;\n\ninterface ButtonBaseProps {\n /** Surface variant. @default \"effect\" */\n variant?: ButtonVariant;\n /** Semantic color. @default \"default\" */\n color?: ButtonColor;\n /** Size variant. @default \"md\" */\n size?: ButtonSize;\n /** Corner shape. @default \"rounded\" */\n shape?: ButtonShape;\n /** Slot before the label — a component (`Plus`) or element (`<Plus />`). */\n prefix?: Slottable;\n /** Slot after the label. */\n suffix?: Slottable;\n /**\n * Reveal prefix/suffix icons on hover — collapsed and invisible at rest,\n * spring in on hover. Independent of `variant` (including `effect`).\n * Ignored when `svgOnly`. @default false\n */\n iconReveal?: boolean;\n /** Render as an icon-only button. Requires an `aria-label`. */\n svgOnly?: boolean;\n /** Show a spinner and block interaction while staying readable. */\n loading?: boolean;\n children?: ReactNode;\n className?: string;\n}\n\n/**\n * DOM event handlers whose React signatures clash with Motion's gesture\n * props of the same name (Motion re-types these on `motion.*` elements).\n * Excluded from the public API — Button renders through Motion when the\n * sheen or icon reveal is active, so these handlers would be intercepted.\n */\ntype MotionHandlerConflicts =\n | \"onAnimationStart\"\n | \"onAnimationEnd\"\n | \"onAnimationIteration\"\n | \"onDrag\"\n | \"onDragStart\"\n | \"onDragEnd\"\n | \"onDragEnter\"\n | \"onDragExit\"\n | \"onDragLeave\"\n | \"onDragOver\"\n | \"onDrop\";\n\nexport interface ButtonProps\n extends\n ButtonBaseProps,\n Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n keyof ButtonBaseProps | MotionHandlerConflicts\n > {\n ref?: Ref<HTMLButtonElement>;\n}\n\nexport interface ButtonLinkProps\n extends\n ButtonBaseProps,\n Omit<\n AnchorHTMLAttributes<HTMLAnchorElement>,\n keyof ButtonBaseProps | MotionHandlerConflicts\n > {\n /** Navigate to this URL (rendered via the host's `LinkProvider`). */\n href: string;\n /** Visually and semantically disable the link. */\n disabled?: boolean;\n}\n\nconst SLOT_CLASS =\n // Icons scale with the label: 1.125em tracks the button's font size\n // (16px icon beside 14px text, 18px beside 16px) so small buttons never\n // carry an oversized glyph.\n \"transition-inherit-all pointer-events-none inline-flex shrink-0 items-center [&>svg]:size-[1.125em] [&>svg]:shrink-0\";\n\nfunction Slot({\n position,\n children,\n}: {\n position: \"prefix\" | \"suffix\" | \"loading\";\n children: ReactNode;\n}) {\n return (\n <span data-icon={position} className={SLOT_CLASS}>\n {children}\n </span>\n );\n}\n\nfunction renderSlot(icon: Slottable, position: \"prefix\" | \"suffix\") {\n if (icon == null || icon === false) return null;\n if (isReactComponent(icon)) {\n const Icon = icon;\n return (\n <Slot position={position}>\n <Icon />\n </Slot>\n );\n }\n if (isValidElement(icon)) return <Slot position={position}>{icon}</Slot>;\n return null;\n}\n\nfunction warnMissingLabel(svgOnly: boolean | undefined, label: unknown) {\n if (\n svgOnly &&\n !label &&\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV !== \"production\"\n ) {\n console.warn(\n \"Button: `svgOnly` buttons require an `aria-label` that names the action.\",\n );\n }\n}\n\n/* ======================== Motion: sheen + icon reveal ======================== */\n\n/** Framer source transition: `spring-physics 200 30 0.1`. */\nconst BUTTON_SPRING = {\n type: \"spring\",\n stiffness: 200,\n damping: 30,\n mass: 0.1,\n} as const;\n\nconst EFFECT_SHEEN_REST = {\n \"--pho-sheen-y\": \"0px\",\n \"--pho-sheen-blur\": \"5px\",\n \"--pho-sheen-spread\": \"2px\",\n} as const;\n\n/** Per-size sheen hover targets. */\nconst EFFECT_SHEEN_HOVER = {\n sm: {\n \"--pho-sheen-y\": \"-3px\",\n \"--pho-sheen-blur\": \"8px\",\n \"--pho-sheen-spread\": \"2px\",\n },\n md: {\n \"--pho-sheen-y\": \"-3px\",\n \"--pho-sheen-blur\": \"8px\",\n \"--pho-sheen-spread\": \"2px\",\n },\n lg: {\n \"--pho-sheen-y\": \"-6px\",\n \"--pho-sheen-blur\": \"14px\",\n \"--pho-sheen-spread\": \"1px\",\n },\n} as const;\n\n/**\n * Per-size sheen press targets — the inverse gesture of hover: the light\n * drops back below the rest line and tightens while the pill compresses\n * slightly, so hover → press reads as \"lift, then push in\".\n */\nconst EFFECT_SHEEN_PRESS = {\n sm: {\n \"--pho-sheen-y\": \"1.5px\",\n \"--pho-sheen-blur\": \"4px\",\n \"--pho-sheen-spread\": \"1px\",\n },\n md: {\n \"--pho-sheen-y\": \"1.5px\",\n \"--pho-sheen-blur\": \"4px\",\n \"--pho-sheen-spread\": \"1px\",\n },\n lg: {\n \"--pho-sheen-y\": \"3px\",\n \"--pho-sheen-blur\": \"7px\",\n \"--pho-sheen-spread\": \"1px\",\n },\n} as const;\n\n/** Pill compression while pressed — subtle, transform-only; every variant. */\nconst BUTTON_PRESS_SCALE = 0.97;\n\n/**\n * One-shot \"wake\" glint when an effect pill re-enables (disabled / loading →\n * interactive). The keyframes replay the pill's own gesture in reverse: the\n * sheen starts from its pressed-in position (low, tight — the dormant read),\n * springs up past the hover line, and settles at rest. Shadow-only, so the\n * pill never moves or resizes next to its neighbours.\n */\nconst EFFECT_SHEEN_WAKE = {\n sm: {\n \"--pho-sheen-y\": [\"1.5px\", \"-4px\", \"0px\"],\n \"--pho-sheen-blur\": [\"4px\", \"9px\", \"5px\"],\n \"--pho-sheen-spread\": [\"1px\", \"2px\", \"2px\"],\n },\n md: {\n \"--pho-sheen-y\": [\"1.5px\", \"-4px\", \"0px\"],\n \"--pho-sheen-blur\": [\"4px\", \"9px\", \"5px\"],\n \"--pho-sheen-spread\": [\"1px\", \"2px\", \"2px\"],\n },\n lg: {\n \"--pho-sheen-y\": [\"3px\", \"-8px\", \"0px\"],\n \"--pho-sheen-blur\": [\"7px\", \"16px\", \"5px\"],\n \"--pho-sheen-spread\": [\"1px\", \"1px\", \"2px\"],\n },\n} as const;\n\n/**\n * Wake timing: a short delay lets the 100ms opacity fade land first, then a\n * quick rise (feedback) and a relaxed settle. Keyframes need a tween — the\n * shared spring only animates between two values.\n */\nconst WAKE_TRANSITION = {\n duration: 0.6,\n delay: 0.05,\n times: [0, 0.4, 1],\n ease: [\"easeOut\", \"easeInOut\"],\n} as const;\n\n/**\n * Fire the wake glint on the disabled/loading → enabled edge (never on\n * mount), skipped under reduced motion. `data-waking` mirrors the window so\n * tests and styling can observe it.\n */\nfunction useWakeOnEnable(inert: boolean, sheen: boolean) {\n const [waking, setWaking] = useState(false);\n const prevInert = useRef(inert);\n const reduceMotion = useReducedMotion();\n\n useEffect(() => {\n if (prevInert.current && !inert && sheen && !reduceMotion) {\n setWaking(true);\n } else if (inert) {\n setWaking(false);\n }\n prevInert.current = inert;\n }, [inert, sheen, reduceMotion]);\n\n return {\n waking,\n onWakeComplete: (definition: unknown) => {\n if (definition === \"wake\") setWaking(false);\n },\n };\n}\n\n/**\n * Icon reveal sizes — collapsed at rest, full on hover. `lg` matches the\n * Framer 20×21 source; `sm`/`md` scale with the 14px label.\n */\nconst ICON_REVEAL_VARIANTS = {\n sm: {\n rest: { opacity: 0, width: 1, height: 9 },\n hover: { opacity: 1, width: 17, height: 18 },\n },\n md: {\n rest: { opacity: 0, width: 1, height: 9 },\n hover: { opacity: 1, width: 17, height: 18 },\n },\n lg: {\n rest: { opacity: 0, width: 1, height: 10 },\n hover: { opacity: 1, width: 20, height: 21 },\n },\n} as const;\n\n/**\n * When icons are hidden at rest, undo Button's icon-side padding tuck so the\n * pill doesn't look lopsided. Same `:has()` keys as the tuck (later wins).\n */\nconst ICON_REVEAL_PAD_RESTORE: Record<ButtonSize, string> = {\n sm: \"has-[[data-icon=prefix]]:pl-3.5 has-[[data-icon=loading]]:pl-3.5 has-[[data-icon=suffix]]:pr-3.5\",\n md: \"has-[[data-icon=prefix]]:pl-4 has-[[data-icon=loading]]:pl-4 has-[[data-icon=suffix]]:pr-4\",\n lg: \"has-[[data-icon=prefix]]:pl-5 has-[[data-icon=loading]]:pl-5 has-[[data-icon=suffix]]:pr-5\",\n};\n\nfunction RevealSlot({\n position,\n size,\n children,\n}: {\n position: \"prefix\" | \"suffix\";\n size: ButtonSize;\n children: ReactNode;\n}) {\n return (\n <motion.span\n data-icon={position}\n variants={ICON_REVEAL_VARIANTS[size]}\n transition={BUTTON_SPRING}\n style={{\n display: \"inline-flex\",\n flex: \"none\",\n alignItems: \"center\",\n overflow: \"hidden\",\n }}\n className=\"pointer-events-none [&>svg]:block [&>svg]:size-full\"\n >\n {children}\n </motion.span>\n );\n}\n\nfunction wrapRevealIcon(\n icon: Slottable,\n position: \"prefix\" | \"suffix\",\n size: ButtonSize,\n): ReactNode | undefined {\n if (icon == null || icon === false) return undefined;\n if (isReactComponent(icon)) {\n const Icon = icon;\n return (\n <RevealSlot position={position} size={size}>\n <Icon />\n </RevealSlot>\n );\n }\n if (isValidElement(icon)) {\n return (\n <RevealSlot position={position} size={size}>\n {icon}\n </RevealSlot>\n );\n }\n return (\n <RevealSlot position={position} size={size}>\n {icon}\n </RevealSlot>\n );\n}\n\nfunction buttonInner({\n svgOnly,\n loading,\n prefix,\n suffix,\n children,\n iconReveal,\n size,\n}: Pick<\n ButtonBaseProps,\n | \"svgOnly\"\n | \"loading\"\n | \"prefix\"\n | \"suffix\"\n | \"children\"\n | \"iconReveal\"\n | \"size\"\n> & { size: ButtonSize }) {\n if (svgOnly) {\n return loading ? <Spinner /> : children;\n }\n const reveal = Boolean(iconReveal);\n return (\n <>\n {loading ? (\n <Slot position=\"loading\">\n <Spinner />\n </Slot>\n ) : reveal ? (\n wrapRevealIcon(prefix, \"prefix\", size)\n ) : (\n renderSlot(prefix, \"prefix\")\n )}\n {children != null && children !== false && (\n <span data-text className=\"transition-inherit-all px-0.5\">\n {children}\n </span>\n )}\n {reveal\n ? wrapRevealIcon(suffix, \"suffix\", size)\n : renderSlot(suffix, \"suffix\")}\n </>\n );\n}\n\n/**\n * Root Motion props — press scale on every variant; sheen + icon-reveal gap\n * only when those features are on. Press composes over hover (`whileTap` >\n * `whileHover`): effect sheen inverts while the pill compresses; other\n * variants get the same scale with no sheen change. `waking` plays the\n * one-shot re-enable glint; a real hover mid-glint takes over seamlessly.\n */\nfunction buttonRootMotionProps(\n size: ButtonSize,\n inert: boolean | undefined,\n opts: { sheen: boolean; iconReveal: boolean; waking?: boolean },\n) {\n const rest: Record<string, string | number> = {\n scale: 1,\n ...(opts.sheen ? EFFECT_SHEEN_REST : {}),\n };\n const hover: Record<string, string | number> = opts.sheen\n ? { ...EFFECT_SHEEN_HOVER[size] }\n : {};\n const press: Record<string, string | number> = {\n scale: BUTTON_PRESS_SCALE,\n ...(opts.sheen ? EFFECT_SHEEN_PRESS[size] : {}),\n };\n const wake = opts.sheen\n ? { ...EFFECT_SHEEN_WAKE[size], transition: WAKE_TRANSITION }\n : {};\n if (opts.iconReveal) {\n // Collapse gap at rest so the hidden icon leaves no hole; lg eases open.\n rest.gap = 0;\n hover.gap = size === \"lg\" ? 4 : 0;\n }\n return {\n initial: false as const,\n animate: opts.waking && opts.sheen ? \"wake\" : \"rest\",\n whileHover: inert ? undefined : \"hover\",\n whileTap: inert ? undefined : \"press\",\n variants: { rest, hover, press, wake },\n transition: BUTTON_SPRING,\n };\n}\n\nfunction buttonClassName({\n variant,\n color,\n size,\n shape,\n loading,\n svgOnly,\n className,\n}: {\n variant?: ButtonVariant;\n color?: ButtonColor;\n size?: ButtonSize;\n shape?: ButtonShape;\n loading?: boolean;\n svgOnly?: boolean;\n className?: string;\n}) {\n return cn(\n buttonVariants({ variant, color, size, shape }),\n loading && \"pointer-events-none\",\n svgOnly && \"[&>svg]:size-[1.25em] [&>svg]:shrink-0\",\n className,\n );\n}\n\n/**\n * Button — triggers an action that mutates state (submit, save, delete).\n * For navigation that changes the URL, use {@link ButtonLink}.\n *\n * Default surface is `effect` (same-hue 1.5px outer rim + inset sheen). Use\n * `filled` for a solid fill without those layers. `iconReveal` springs\n * prefix/suffix icons in on hover — independent of variant.\n */\nexport function Button({\n variant = \"effect\",\n color,\n size = \"md\",\n shape,\n prefix,\n suffix,\n iconReveal = false,\n svgOnly,\n loading,\n children,\n className,\n ref,\n ...props\n}: ButtonProps) {\n warnMissingLabel(svgOnly, props[\"aria-label\"]);\n const sheen = variant === \"effect\";\n const reveal = iconReveal && !svgOnly;\n const inert = Boolean(props.disabled || loading);\n const { waking, onWakeComplete } = useWakeOnEnable(inert, sheen);\n const cls = buttonClassName({\n variant,\n color,\n size,\n shape,\n loading,\n svgOnly,\n className: cn(reveal && ICON_REVEAL_PAD_RESTORE[size], className),\n });\n const inner = buttonInner({\n svgOnly,\n loading,\n prefix,\n suffix,\n children,\n iconReveal: reveal,\n size,\n });\n\n return (\n <motion.button\n ref={ref}\n type=\"button\"\n {...buttonRootMotionProps(size, inert, {\n sheen,\n iconReveal: reveal,\n waking,\n })}\n onAnimationComplete={onWakeComplete}\n {...props}\n aria-busy={loading || undefined}\n data-loading={loading ? \"true\" : undefined}\n data-svg-only={svgOnly ? \"true\" : undefined}\n data-waking={waking || undefined}\n className={cls}\n >\n {inner}\n </motion.button>\n );\n}\n\nButton.displayName = \"Button\";\n\n/**\n * ButtonLink — a link styled as a button, for navigation that changes the URL.\n * Shares every visual prop with {@link Button}; renders the host router's link\n * via `LinkProvider`.\n */\nexport function ButtonLink({\n variant = \"effect\",\n color,\n size = \"md\",\n shape,\n prefix,\n suffix,\n iconReveal = false,\n svgOnly,\n loading,\n disabled,\n href,\n children,\n className,\n ...props\n}: ButtonLinkProps) {\n warnMissingLabel(svgOnly, props[\"aria-label\"]);\n const Link = useLinkComponent();\n const sheen = variant === \"effect\";\n const reveal = iconReveal && !svgOnly;\n const cls = buttonClassName({\n variant,\n color,\n size,\n shape,\n loading,\n svgOnly,\n className: cn(reveal && ICON_REVEAL_PAD_RESTORE[size], className),\n });\n const inner = buttonInner({\n svgOnly,\n loading,\n prefix,\n suffix,\n children,\n iconReveal: reveal,\n size,\n });\n // Hooks must run unconditionally — only used when Motion is needed.\n const MotionLink = useMemo(\n // LinkProvider requires `href`; motion.create needs an open prop record —\n // bridge the two through `unknown` (props are supplied at the call site).\n () =>\n motion.create(Link as unknown as ComponentType<Record<string, unknown>>),\n [Link],\n );\n\n const inert = Boolean(disabled || loading);\n const { waking, onWakeComplete } = useWakeOnEnable(inert, sheen);\n const shared = {\n ...props,\n ...(inert ? {} : { href }),\n \"aria-disabled\": inert || undefined,\n \"aria-busy\": loading || undefined,\n \"data-loading\": loading ? (\"true\" as const) : undefined,\n \"data-svg-only\": svgOnly ? (\"true\" as const) : undefined,\n className: cls,\n tabIndex: inert ? -1 : props.tabIndex,\n onClick: inert\n ? (event: ReactMouseEvent<HTMLAnchorElement>) => {\n event.preventDefault();\n }\n : props.onClick,\n };\n\n if (inert) {\n return <span {...shared}>{inner}</span>;\n }\n\n return (\n <MotionLink\n {...buttonRootMotionProps(size, inert, {\n sheen,\n iconReveal: reveal,\n waking,\n })}\n onAnimationComplete={onWakeComplete}\n data-waking={waking || undefined}\n {...shared}\n >\n {inner}\n </MotionLink>\n );\n}\n\nButtonLink.displayName = \"ButtonLink\";\n\n/* ======================= EffectButton (compat alias) ======================= */\n\nexport const EFFECT_BUTTON_SIZES = BUTTON_SIZES;\nexport type EffectButtonSize = ButtonSize;\n\n/** Compose classes for an effect pill — `Button` with `variant=\"effect\"`. */\nexport function effectButtonVariants(opts?: {\n size?: EffectButtonSize;\n fullWidth?: boolean;\n className?: string;\n}): string {\n const { size = \"md\", fullWidth, className } = opts ?? {};\n return buttonVariants({\n variant: \"effect\",\n size,\n className: cn(fullWidth && \"w-full\", className),\n });\n}\n\n/**\n * Legacy EffectButton also reserved `style` (sheen variables lived there) and\n * never exposed the HTML `color` attribute — Button's semantic `color` prop\n * now occupies that name.\n */\ntype MotionConflicts = MotionHandlerConflicts | \"style\" | \"color\";\n\ninterface EffectButtonBaseProps {\n /** Size variant — Button's scale (32 / 36 / 44px). @default \"md\" */\n size?: EffectButtonSize;\n /** Stretch to the container's full width (stacked login / OAuth pills). */\n fullWidth?: boolean;\n /**\n * @deprecated Use `iconReveal` on `Button` instead. Mapped to `iconReveal`\n * when set.\n */\n animated?: boolean;\n /** @see ButtonBaseProps.iconReveal */\n iconReveal?: boolean;\n prefix?: Slottable;\n suffix?: Slottable;\n svgOnly?: boolean;\n loading?: boolean;\n children?: ReactNode;\n className?: string;\n}\n\nexport interface EffectButtonProps\n extends\n EffectButtonBaseProps,\n Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n keyof EffectButtonBaseProps | MotionConflicts\n > {\n ref?: Ref<HTMLButtonElement>;\n}\n\nexport interface EffectButtonLinkProps\n extends\n EffectButtonBaseProps,\n Omit<\n AnchorHTMLAttributes<HTMLAnchorElement>,\n keyof EffectButtonBaseProps | MotionConflicts\n > {\n href: string;\n disabled?: boolean;\n}\n\n/**\n * @deprecated Prefer `<Button variant=\"effect\">`. Thin alias that forwards to\n * Button with the sheen surface (no icon reveal).\n */\nexport function EffectButton({\n size = \"md\",\n fullWidth,\n animated,\n iconReveal,\n className,\n ...props\n}: EffectButtonProps) {\n return (\n <Button\n variant=\"effect\"\n size={size}\n iconReveal={iconReveal ?? animated ?? true}\n className={cn(fullWidth && \"w-full\", className)}\n {...props}\n />\n );\n}\n\nEffectButton.displayName = \"EffectButton\";\n\n/**\n * @deprecated Prefer `<ButtonLink variant=\"effect\">`.\n */\nexport function EffectButtonLink({\n size = \"md\",\n fullWidth,\n animated,\n iconReveal,\n className,\n ...props\n}: EffectButtonLinkProps) {\n return (\n <ButtonLink\n variant=\"effect\"\n size={size}\n iconReveal={iconReveal ?? animated ?? true}\n className={cn(fullWidth && \"w-full\", className)}\n {...props}\n />\n );\n}\n\nEffectButtonLink.displayName = \"EffectButtonLink\";\n","import { type ButtonHTMLAttributes } from \"react\";\nimport { IconX } from \"@tabler/icons-react\";\nimport { cn } from \"../../utils/cn\";\nimport { resolveVariant, type VariantMap } from \"../../utils/resolve-variant\";\n\n// `rounded-base` (12px) at every size; base classes already apply squircle.\nconst CLOSE_SIZES = {\n sm: { classes: \"size-9 rounded-base *:size-5\" },\n md: { classes: \"size-10 rounded-base *:size-5\" },\n lg: { classes: \"size-11 rounded-base *:size-6\" },\n} satisfies VariantMap;\n\nexport interface CloseButtonProps extends Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n \"color\"\n> {\n /** Size variant. @default \"sm\" */\n size?: keyof typeof CLOSE_SIZES;\n /** Accessible label. @default \"Close\" */\n label?: string;\n}\n\n/**\n * Icon-only dismiss control — a squircle chip that fills on hover. Adapts to\n * light/dark automatically via semantic tokens. Used by Dialog and dismissible\n * surfaces.\n */\nexport function CloseButton({\n size = \"sm\",\n label,\n className,\n ...props\n}: CloseButtonProps) {\n return (\n <button\n type=\"button\"\n aria-label={label || \"Close\"}\n {...props}\n className={cn(\n \"squircle text-pho-secondary outline-pho-brand hover:bg-pho-ghost-hover hover:text-pho-secondary-hover inline-flex cursor-pointer items-center justify-center p-2 transition duration-100 ease-linear focus-visible:outline-2 focus-visible:outline-offset-2\",\n resolveVariant(CLOSE_SIZES, size),\n className,\n )}\n >\n <IconX aria-hidden=\"true\" className=\"transition-inherit-all shrink-0\" />\n </button>\n );\n}\n\nCloseButton.displayName = \"CloseButton\";\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { t as g } from "./cn-DTiB6ek_.js";
|
|
3
|
-
import { o as T, t as j } from "./close-button-
|
|
3
|
+
import { o as T, t as j } from "./close-button-Bq9f-H15.js";
|
|
4
4
|
import { createContext as R, useCallback as z, useContext as x, useEffect as v, useMemo as A, useRef as m, useState as C } from "react";
|
|
5
5
|
import { jsx as t, jsxs as f } from "react/jsx-runtime";
|
|
6
6
|
import { Dialog as i } from "@base-ui/react/dialog";
|
|
@@ -138,4 +138,4 @@ export {
|
|
|
138
138
|
I as t
|
|
139
139
|
};
|
|
140
140
|
|
|
141
|
-
//# sourceMappingURL=dialog-
|
|
141
|
+
//# sourceMappingURL=dialog-DolPKFGS.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog-Bgb2Z1_v.js","names":[],"sources":["../../src/components/dialog/dialog.tsx"],"sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ComponentProps,\n type ElementType,\n type MouseEvent as ReactMouseEvent,\n type ReactNode,\n} from \"react\";\nimport { Dialog as BaseDialog } from \"@base-ui/react/dialog\";\nimport { cn } from \"../../utils/cn\";\nimport { Button, type ButtonProps } from \"../button/button\";\nimport { CloseButton } from \"../button/close-button\";\n\n/** Override Base UI's `string | (state) => string` className with plain string. */\ntype StyledProps<T extends ElementType> = Omit<\n ComponentProps<T>,\n \"className\"\n> & {\n className?: string;\n};\n\n/* ========================== Async pending context ========================== */\n\n/** Imperative actions exposed by Base UI's `actionsRef`. */\ninterface RootActions {\n unmount: () => void;\n close: () => void;\n}\n\ninterface DialogPendingContextValue {\n /** True while a `Dialog.Action` promise is in flight. */\n pending: boolean;\n beginPending: () => void;\n endPending: () => void;\n /** Close the dialog imperatively (used by `Dialog.Action` on resolve). */\n close: () => void;\n}\n\nconst DialogPendingContext = createContext<DialogPendingContextValue | null>(\n null,\n);\n\n/**\n * Whether the surrounding dialog has a `Dialog.Action` promise in flight —\n * use it to disable secondary actions (Cancel etc.) while work is pending.\n * Returns `false` outside a `Dialog.Root`.\n */\nexport function useDialogPending(): boolean {\n return useContext(DialogPendingContext)?.pending ?? false;\n}\n\ntype RootProps = ComponentProps<typeof BaseDialog.Root>;\n\n/**\n * `Dialog.Root` with async awareness: while a `Dialog.Action` promise is\n * pending, every close request (outside press, Escape, `Dialog.Close`) is\n * cancelled so the dialog stays open until the work settles. Props pass\n * through to Base UI's `Dialog.Root` unchanged.\n */\nfunction Root({ onOpenChange, actionsRef, ...props }: RootProps) {\n const [pending, setPending] = useState(false);\n // Guard reads a ref so a resolve → close() in the same tick isn't blocked\n // by a not-yet-flushed state update.\n const pendingRef = useRef(false);\n const internalActionsRef = useRef<RootActions | null>(null);\n\n // Keep a caller-supplied actionsRef working alongside our internal one.\n useEffect(() => {\n if (!actionsRef) return;\n actionsRef.current = internalActionsRef.current;\n return () => {\n actionsRef.current = null;\n };\n }, [actionsRef]);\n\n const handleOpenChange = useCallback<NonNullable<RootProps[\"onOpenChange\"]>>(\n (open, eventDetails) => {\n if (!open && pendingRef.current) {\n eventDetails.cancel();\n return;\n }\n onOpenChange?.(open, eventDetails);\n },\n [onOpenChange],\n );\n\n const contextValue = useMemo<DialogPendingContextValue>(\n () => ({\n pending,\n beginPending: () => {\n pendingRef.current = true;\n setPending(true);\n },\n endPending: () => {\n pendingRef.current = false;\n setPending(false);\n },\n close: () => internalActionsRef.current?.close(),\n }),\n [pending],\n );\n\n return (\n <DialogPendingContext.Provider value={contextValue}>\n <BaseDialog.Root\n actionsRef={internalActionsRef}\n onOpenChange={handleOpenChange}\n {...props}\n />\n </DialogPendingContext.Provider>\n );\n}\n\nconst Trigger = BaseDialog.Trigger;\nconst Portal = BaseDialog.Portal;\nconst Close = BaseDialog.Close;\n\n/* ============================== Dialog.Action ============================== */\n\nexport interface DialogActionProps extends Omit<ButtonProps, \"onClick\"> {\n /**\n * Confirm handler. Return a promise to keep the dialog open while it runs:\n * the button shows `loading`, all dismissal is locked, and the dialog\n * closes when the promise resolves. On rejection the dialog stays open —\n * surface errors inside the handler. A non-promise return closes\n * immediately, like `Dialog.Close`.\n */\n onClick?: (\n event: ReactMouseEvent<HTMLButtonElement>,\n ) => void | Promise<unknown>;\n}\n\nfunction isThenable(value: unknown): value is Promise<unknown> {\n return (\n value != null && typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\n/**\n * The confirm button of an action row, with native promise support — a\n * `Button` that runs its (possibly async) `onClick` and closes the dialog\n * when the work settles successfully.\n */\nfunction Action({ onClick, loading, disabled, ...props }: DialogActionProps) {\n const pendingContext = useContext(DialogPendingContext);\n const [busy, setBusy] = useState(false);\n const mountedRef = useRef(true);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n function handleClick(event: ReactMouseEvent<HTMLButtonElement>) {\n if (busy || loading) return;\n const result = onClick?.(event);\n if (!isThenable(result)) {\n pendingContext?.close();\n return;\n }\n setBusy(true);\n pendingContext?.beginPending();\n result.then(\n () => {\n pendingContext?.endPending();\n if (mountedRef.current) setBusy(false);\n pendingContext?.close();\n },\n (error: unknown) => {\n pendingContext?.endPending();\n if (mountedRef.current) setBusy(false);\n if (\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV !== \"production\"\n ) {\n console.error(\"Dialog.Action: async onClick rejected —\", error);\n }\n },\n );\n }\n\n const inert = busy || loading;\n return (\n <Button\n {...props}\n disabled={disabled || inert}\n loading={inert}\n onClick={handleClick}\n />\n );\n}\n\nfunction Backdrop({\n className,\n ...props\n}: StyledProps<typeof BaseDialog.Backdrop>) {\n return (\n <BaseDialog.Backdrop\n className={cn(\n \"bg-pho-overlay fixed inset-0 z-50 transition-opacity duration-200 ease-out\",\n \"data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:opacity-0\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Popup({ className, ...props }: StyledProps<typeof BaseDialog.Popup>) {\n return (\n <BaseDialog.Popup\n className={cn(\n // Floating layer: bg-pho-primary + border-pho-secondary hairline, elevation kept.\n \"border-pho-secondary bg-pho-primary shadow-pho-xl fixed top-1/2 left-1/2 z-50 w-[calc(100%-2rem)] max-w-lg -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-lg border outline-none\",\n // Opacity + scale only — leave translate centering alone (no transition-all).\n \"transition-[opacity,scale] duration-200 ease-out\",\n \"data-[starting-style]:scale-95 data-[starting-style]:opacity-0\",\n \"data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:duration-150\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Title({ className, ...props }: StyledProps<typeof BaseDialog.Title>) {\n return (\n <BaseDialog.Title\n className={cn(\n \"text-pho-primary font-display text-display-sm font-medium text-balance\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Description({\n className,\n ...props\n}: StyledProps<typeof BaseDialog.Description>) {\n return (\n <BaseDialog.Description\n className={cn(\n \"text-pho-description text-display-xs font-normal\",\n className,\n )}\n {...props}\n />\n );\n}\n\ninterface ContentProps extends Omit<\n StyledProps<typeof BaseDialog.Popup>,\n \"title\"\n> {\n /** Heading rendered in the panel header. */\n title?: ReactNode;\n /** Supporting text under the title. */\n description?: ReactNode;\n /**\n * Footer action row (Cancel / Confirm, etc.). Iron rule: when `actions` is\n * set, the header close button is never shown — dismiss via the actions.\n */\n actions?: ReactNode;\n /**\n * Header close control. Only applies when `actions` is absent.\n * @default true\n */\n showClose?: boolean;\n children?: ReactNode;\n}\n\n/**\n * Convenience panel: renders the portal, backdrop, and styled popup with an\n * optional header (title/description + close) and optional action row. For\n * full control, compose the parts directly (`Dialog.Portal` /\n * `Dialog.Backdrop` / `Dialog.Popup`).\n */\nfunction Content({\n title,\n description,\n actions,\n showClose = true,\n children,\n className,\n ...props\n}: ContentProps) {\n // Iron rule: action dialogs dismiss via their actions — never a header X.\n const showCloseButton = actions == null && showClose;\n const hasHeader = Boolean(title || description || showCloseButton);\n const hasBody = children != null || actions != null;\n\n return (\n <Portal>\n <Backdrop />\n <Popup className={className} {...props}>\n {hasHeader && (\n <div className=\"flex flex-col gap-1 px-6 pt-6\">\n {(title || showCloseButton) && (\n // Title row height = title only. Close is out of flow, centered\n // on the row, and may paint outside it (no clip, no stretch).\n <div className=\"relative\">\n {title ? (\n <Title className={showCloseButton ? \"pr-12\" : undefined}>\n {title}\n </Title>\n ) : (\n <div className=\"h-[1.625rem]\" aria-hidden=\"true\" />\n )}\n {showCloseButton && (\n <Close\n render={\n <CloseButton className=\"absolute top-1/2 right-0 z-10 -translate-y-1/2\" />\n }\n />\n )}\n </div>\n )}\n {description && <Description>{description}</Description>}\n </div>\n )}\n {hasBody && (\n <div className=\"flex flex-col gap-5 px-6 py-5\">\n {children}\n {actions != null && (\n <div className=\"flex justify-end gap-2\">{actions}</div>\n )}\n </div>\n )}\n </Popup>\n </Portal>\n );\n}\n\n/**\n * Modal dialog built on Base UI `Dialog`. Controlled or uncontrolled via\n * `Dialog.Root` (`open` / `onOpenChange` / `defaultOpen`). `Dialog.Action`\n * adds native promise support: an async confirm keeps the dialog open and\n * undismissable until the work resolves.\n */\nexport const Dialog = {\n Root,\n Trigger,\n Portal,\n Backdrop,\n Popup,\n Title,\n Description,\n Close,\n Action,\n Content,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dialog-DolPKFGS.js","names":[],"sources":["../../src/components/dialog/dialog.tsx"],"sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ComponentProps,\n type ElementType,\n type MouseEvent as ReactMouseEvent,\n type ReactNode,\n} from \"react\";\nimport { Dialog as BaseDialog } from \"@base-ui/react/dialog\";\nimport { cn } from \"../../utils/cn\";\nimport { Button, type ButtonProps } from \"../button/button\";\nimport { CloseButton } from \"../button/close-button\";\n\n/** Override Base UI's `string | (state) => string` className with plain string. */\ntype StyledProps<T extends ElementType> = Omit<\n ComponentProps<T>,\n \"className\"\n> & {\n className?: string;\n};\n\n/* ========================== Async pending context ========================== */\n\n/** Imperative actions exposed by Base UI's `actionsRef`. */\ninterface RootActions {\n unmount: () => void;\n close: () => void;\n}\n\ninterface DialogPendingContextValue {\n /** True while a `Dialog.Action` promise is in flight. */\n pending: boolean;\n beginPending: () => void;\n endPending: () => void;\n /** Close the dialog imperatively (used by `Dialog.Action` on resolve). */\n close: () => void;\n}\n\nconst DialogPendingContext = createContext<DialogPendingContextValue | null>(\n null,\n);\n\n/**\n * Whether the surrounding dialog has a `Dialog.Action` promise in flight —\n * use it to disable secondary actions (Cancel etc.) while work is pending.\n * Returns `false` outside a `Dialog.Root`.\n */\nexport function useDialogPending(): boolean {\n return useContext(DialogPendingContext)?.pending ?? false;\n}\n\ntype RootProps = ComponentProps<typeof BaseDialog.Root>;\n\n/**\n * `Dialog.Root` with async awareness: while a `Dialog.Action` promise is\n * pending, every close request (outside press, Escape, `Dialog.Close`) is\n * cancelled so the dialog stays open until the work settles. Props pass\n * through to Base UI's `Dialog.Root` unchanged.\n */\nfunction Root({ onOpenChange, actionsRef, ...props }: RootProps) {\n const [pending, setPending] = useState(false);\n // Guard reads a ref so a resolve → close() in the same tick isn't blocked\n // by a not-yet-flushed state update.\n const pendingRef = useRef(false);\n const internalActionsRef = useRef<RootActions | null>(null);\n\n // Keep a caller-supplied actionsRef working alongside our internal one.\n useEffect(() => {\n if (!actionsRef) return;\n actionsRef.current = internalActionsRef.current;\n return () => {\n actionsRef.current = null;\n };\n }, [actionsRef]);\n\n const handleOpenChange = useCallback<NonNullable<RootProps[\"onOpenChange\"]>>(\n (open, eventDetails) => {\n if (!open && pendingRef.current) {\n eventDetails.cancel();\n return;\n }\n onOpenChange?.(open, eventDetails);\n },\n [onOpenChange],\n );\n\n const contextValue = useMemo<DialogPendingContextValue>(\n () => ({\n pending,\n beginPending: () => {\n pendingRef.current = true;\n setPending(true);\n },\n endPending: () => {\n pendingRef.current = false;\n setPending(false);\n },\n close: () => internalActionsRef.current?.close(),\n }),\n [pending],\n );\n\n return (\n <DialogPendingContext.Provider value={contextValue}>\n <BaseDialog.Root\n actionsRef={internalActionsRef}\n onOpenChange={handleOpenChange}\n {...props}\n />\n </DialogPendingContext.Provider>\n );\n}\n\nconst Trigger = BaseDialog.Trigger;\nconst Portal = BaseDialog.Portal;\nconst Close = BaseDialog.Close;\n\n/* ============================== Dialog.Action ============================== */\n\nexport interface DialogActionProps extends Omit<ButtonProps, \"onClick\"> {\n /**\n * Confirm handler. Return a promise to keep the dialog open while it runs:\n * the button shows `loading`, all dismissal is locked, and the dialog\n * closes when the promise resolves. On rejection the dialog stays open —\n * surface errors inside the handler. A non-promise return closes\n * immediately, like `Dialog.Close`.\n */\n onClick?: (\n event: ReactMouseEvent<HTMLButtonElement>,\n ) => void | Promise<unknown>;\n}\n\nfunction isThenable(value: unknown): value is Promise<unknown> {\n return (\n value != null && typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\n/**\n * The confirm button of an action row, with native promise support — a\n * `Button` that runs its (possibly async) `onClick` and closes the dialog\n * when the work settles successfully.\n */\nfunction Action({ onClick, loading, disabled, ...props }: DialogActionProps) {\n const pendingContext = useContext(DialogPendingContext);\n const [busy, setBusy] = useState(false);\n const mountedRef = useRef(true);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n function handleClick(event: ReactMouseEvent<HTMLButtonElement>) {\n if (busy || loading) return;\n const result = onClick?.(event);\n if (!isThenable(result)) {\n pendingContext?.close();\n return;\n }\n setBusy(true);\n pendingContext?.beginPending();\n result.then(\n () => {\n pendingContext?.endPending();\n if (mountedRef.current) setBusy(false);\n pendingContext?.close();\n },\n (error: unknown) => {\n pendingContext?.endPending();\n if (mountedRef.current) setBusy(false);\n if (\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV !== \"production\"\n ) {\n console.error(\"Dialog.Action: async onClick rejected —\", error);\n }\n },\n );\n }\n\n const inert = busy || loading;\n return (\n <Button\n {...props}\n disabled={disabled || inert}\n loading={inert}\n onClick={handleClick}\n />\n );\n}\n\nfunction Backdrop({\n className,\n ...props\n}: StyledProps<typeof BaseDialog.Backdrop>) {\n return (\n <BaseDialog.Backdrop\n className={cn(\n \"bg-pho-overlay fixed inset-0 z-50 transition-opacity duration-200 ease-out\",\n \"data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:opacity-0\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Popup({ className, ...props }: StyledProps<typeof BaseDialog.Popup>) {\n return (\n <BaseDialog.Popup\n className={cn(\n // Floating layer: bg-pho-primary + border-pho-secondary hairline, elevation kept.\n \"border-pho-secondary bg-pho-primary shadow-pho-xl fixed top-1/2 left-1/2 z-50 w-[calc(100%-2rem)] max-w-lg -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-lg border outline-none\",\n // Opacity + scale only — leave translate centering alone (no transition-all).\n \"transition-[opacity,scale] duration-200 ease-out\",\n \"data-[starting-style]:scale-95 data-[starting-style]:opacity-0\",\n \"data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:duration-150\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Title({ className, ...props }: StyledProps<typeof BaseDialog.Title>) {\n return (\n <BaseDialog.Title\n className={cn(\n \"text-pho-primary font-display text-display-sm font-medium text-balance\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Description({\n className,\n ...props\n}: StyledProps<typeof BaseDialog.Description>) {\n return (\n <BaseDialog.Description\n className={cn(\n \"text-pho-description text-display-xs font-normal\",\n className,\n )}\n {...props}\n />\n );\n}\n\ninterface ContentProps extends Omit<\n StyledProps<typeof BaseDialog.Popup>,\n \"title\"\n> {\n /** Heading rendered in the panel header. */\n title?: ReactNode;\n /** Supporting text under the title. */\n description?: ReactNode;\n /**\n * Footer action row (Cancel / Confirm, etc.). Iron rule: when `actions` is\n * set, the header close button is never shown — dismiss via the actions.\n */\n actions?: ReactNode;\n /**\n * Header close control. Only applies when `actions` is absent.\n * @default true\n */\n showClose?: boolean;\n children?: ReactNode;\n}\n\n/**\n * Convenience panel: renders the portal, backdrop, and styled popup with an\n * optional header (title/description + close) and optional action row. For\n * full control, compose the parts directly (`Dialog.Portal` /\n * `Dialog.Backdrop` / `Dialog.Popup`).\n */\nfunction Content({\n title,\n description,\n actions,\n showClose = true,\n children,\n className,\n ...props\n}: ContentProps) {\n // Iron rule: action dialogs dismiss via their actions — never a header X.\n const showCloseButton = actions == null && showClose;\n const hasHeader = Boolean(title || description || showCloseButton);\n const hasBody = children != null || actions != null;\n\n return (\n <Portal>\n <Backdrop />\n <Popup className={className} {...props}>\n {hasHeader && (\n <div className=\"flex flex-col gap-1 px-6 pt-6\">\n {(title || showCloseButton) && (\n // Title row height = title only. Close is out of flow, centered\n // on the row, and may paint outside it (no clip, no stretch).\n <div className=\"relative\">\n {title ? (\n <Title className={showCloseButton ? \"pr-12\" : undefined}>\n {title}\n </Title>\n ) : (\n <div className=\"h-[1.625rem]\" aria-hidden=\"true\" />\n )}\n {showCloseButton && (\n <Close\n render={\n <CloseButton className=\"absolute top-1/2 right-0 z-10 -translate-y-1/2\" />\n }\n />\n )}\n </div>\n )}\n {description && <Description>{description}</Description>}\n </div>\n )}\n {hasBody && (\n <div className=\"flex flex-col gap-5 px-6 py-5\">\n {children}\n {actions != null && (\n <div className=\"flex justify-end gap-2\">{actions}</div>\n )}\n </div>\n )}\n </Popup>\n </Portal>\n );\n}\n\n/**\n * Modal dialog built on Base UI `Dialog`. Controlled or uncontrolled via\n * `Dialog.Root` (`open` / `onOpenChange` / `defaultOpen`). `Dialog.Action`\n * adds native promise support: an async confirm keeps the dialog open and\n * undismissable until the work resolves.\n */\nexport const Dialog = {\n Root,\n Trigger,\n Portal,\n Backdrop,\n Popup,\n Title,\n Description,\n Close,\n Action,\n Content,\n};\n"],"mappings":"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"}
|
|
@@ -45,14 +45,14 @@ var S = [
|
|
|
45
45
|
}, P = {
|
|
46
46
|
sm: "",
|
|
47
47
|
md: "",
|
|
48
|
-
lg: r("isolate before:pointer-events-none before:absolute before:-z-10 before:content-['']", "before:bg-pho-primary before:inset-px before:rounded-[calc(var(--radius-base)-1px)]", "has-[input:focus-visible]:before:inset-0.5 has-[input:focus-visible]:before:rounded-[calc(var(--radius-base)-2px)]", "before:transition-[inset,border-radius] before:duration-100 before:ease-linear")
|
|
48
|
+
lg: r("isolate before:pointer-events-none before:absolute before:-z-10 before:content-['']", "before:bg-pho-primary before:inset-px before:rounded-[calc(var(--radius-base)-1px)]", "data-[invalid]:before:inset-[1.5px] data-[invalid]:before:rounded-[calc(var(--radius-base)-1.5px)]", "has-[input:focus-visible]:before:inset-0.5 has-[input:focus-visible]:before:rounded-[calc(var(--radius-base)-2px)]", "before:transition-[inset,border-radius] before:duration-100 before:ease-linear")
|
|
49
49
|
}, T = {
|
|
50
50
|
sm: "autofill:rounded-[calc(var(--radius-base)-2px)]",
|
|
51
51
|
md: "autofill:rounded-[calc(var(--radius-base)-2px)]",
|
|
52
52
|
lg: "autofill:rounded-[calc(var(--radius-base)-2px)]"
|
|
53
53
|
};
|
|
54
|
-
function Z({ label: d, hint: c, size: a = "md", invalid: t, icon: i, labelTrailing:
|
|
55
|
-
const [
|
|
54
|
+
function Z({ label: d, hint: c, size: a = "md", invalid: t, icon: i, labelTrailing: m, disabled: b, required: p, type: u = "text", className: v, wrapperClassName: h, id: x, ref: g, ...y }) {
|
|
55
|
+
const [f, N] = A(!1), o = u === "password", I = o && f ? "text" : u, _ = !!i, E = o || t;
|
|
56
56
|
return /* @__PURE__ */ s(l.Root, {
|
|
57
57
|
disabled: b,
|
|
58
58
|
className: "group/field flex w-full flex-col gap-1.5",
|
|
@@ -65,11 +65,11 @@ function Z({ label: d, hint: c, size: a = "md", invalid: t, icon: i, labelTraili
|
|
|
65
65
|
className: "text-pho-brand",
|
|
66
66
|
children: "*"
|
|
67
67
|
})]
|
|
68
|
-
}),
|
|
68
|
+
}), m]
|
|
69
69
|
}),
|
|
70
70
|
/* @__PURE__ */ s("div", {
|
|
71
71
|
"data-invalid": t || void 0,
|
|
72
|
-
className: r("bg-pho-primary ring-pho-primary relative flex w-full items-center p-0.5 ring-1 transition-shadow duration-100 ease-linear ring-inset", F[a], P[a], "has-[input:focus-visible]:ring-pho-brand has-[input:focus-visible]:ring-2", "data-[invalid]:ring-pho-error-subtle data-[invalid]:has-[input:focus-visible]:ring-pho-error", "group-data-[disabled]/field:cursor-not-allowed group-data-[disabled]/field:opacity-50", h),
|
|
72
|
+
className: r("bg-pho-primary ring-pho-primary relative flex w-full items-center p-0.5 ring-1 transition-shadow duration-100 ease-linear ring-inset", F[a], P[a], "has-[input:focus-visible]:ring-pho-brand has-[input:focus-visible]:ring-2", "data-[invalid]:ring-pho-error-subtle data-[invalid]:has-[input:focus-visible]:ring-pho-error data-[invalid]:ring-[1.5px]", "group-data-[disabled]/field:cursor-not-allowed group-data-[disabled]/field:opacity-50", h),
|
|
73
73
|
children: [
|
|
74
74
|
i && /* @__PURE__ */ e(i, { className: r("text-pho-secondary pointer-events-none absolute", z[a]) }),
|
|
75
75
|
/* @__PURE__ */ e(l.Control, {
|
|
@@ -87,7 +87,7 @@ function Z({ label: d, hint: c, size: a = "md", invalid: t, icon: i, labelTraili
|
|
|
87
87
|
"aria-label": "Toggle password visibility",
|
|
88
88
|
onClick: () => N((w) => !w),
|
|
89
89
|
className: "text-pho-secondary hover:text-pho-secondary-hover absolute right-3 flex cursor-pointer items-center transition duration-100 ease-linear focus:outline-none",
|
|
90
|
-
children:
|
|
90
|
+
children: f ? /* @__PURE__ */ e(k, { className: "size-4" }) : /* @__PURE__ */ e(D, { className: "size-4" })
|
|
91
91
|
})
|
|
92
92
|
]
|
|
93
93
|
}),
|
|
@@ -103,4 +103,4 @@ export {
|
|
|
103
103
|
Z as t
|
|
104
104
|
};
|
|
105
105
|
|
|
106
|
-
//# sourceMappingURL=input-
|
|
106
|
+
//# sourceMappingURL=input-CxS7ndpb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-CxS7ndpb.js","names":["__iconNode","__iconNode"],"sources":["../../../../node_modules/.pnpm/@tabler+icons-react@3.44.0_react@19.2.7/node_modules/@tabler/icons-react/dist/esm/icons/IconEyeOff.mjs","../../../../node_modules/.pnpm/@tabler+icons-react@3.44.0_react@19.2.7/node_modules/@tabler/icons-react/dist/esm/icons/IconEye.mjs","../../../../node_modules/.pnpm/@tabler+icons-react@3.44.0_react@19.2.7/node_modules/@tabler/icons-react/dist/esm/icons/IconAlertCircleFilled.mjs","../../src/components/input/input.tsx"],"sourcesContent":["/**\n * @license @tabler/icons-react v3.44.0 - MIT\n *\n * This source code is licensed under the MIT license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createReactComponent from '../createReactComponent.mjs';\n\nconst __iconNode = [[\"path\", { \"d\": \"M10.585 10.587a2 2 0 0 0 2.829 2.828\", \"key\": \"svg-0\" }], [\"path\", { \"d\": \"M16.681 16.673a8.717 8.717 0 0 1 -4.681 1.327c-3.6 0 -6.6 -2 -9 -6c1.272 -2.12 2.712 -3.678 4.32 -4.674m2.86 -1.146a9.055 9.055 0 0 1 1.82 -.18c3.6 0 6.6 2 9 6c-.666 1.11 -1.379 2.067 -2.138 2.87\", \"key\": \"svg-1\" }], [\"path\", { \"d\": \"M3 3l18 18\", \"key\": \"svg-2\" }]];\nconst IconEyeOff = createReactComponent(\"outline\", \"eye-off\", \"EyeOff\", __iconNode);\n\nexport { __iconNode, IconEyeOff as default };\n//# sourceMappingURL=IconEyeOff.mjs.map\n","/**\n * @license @tabler/icons-react v3.44.0 - MIT\n *\n * This source code is licensed under the MIT license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createReactComponent from '../createReactComponent.mjs';\n\nconst __iconNode = [[\"path\", { \"d\": \"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\", \"key\": \"svg-0\" }], [\"path\", { \"d\": \"M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6\", \"key\": \"svg-1\" }]];\nconst IconEye = createReactComponent(\"outline\", \"eye\", \"Eye\", __iconNode);\n\nexport { __iconNode, IconEye as default };\n//# sourceMappingURL=IconEye.mjs.map\n","/**\n * @license @tabler/icons-react v3.44.0 - MIT\n *\n * This source code is licensed under the MIT license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createReactComponent from '../createReactComponent.mjs';\n\nconst __iconNode = [[\"path\", { \"d\": \"M12 2c5.523 0 10 4.477 10 10a10 10 0 0 1 -19.995 .324l-.005 -.324l.004 -.28c.148 -5.393 4.566 -9.72 9.996 -9.72zm.01 13l-.127 .007a1 1 0 0 0 0 1.986l.117 .007l.127 -.007a1 1 0 0 0 0 -1.986l-.117 -.007zm-.01 -8a1 1 0 0 0 -.993 .883l-.007 .117v4l.007 .117a1 1 0 0 0 1.986 0l.007 -.117v-4l-.007 -.117a1 1 0 0 0 -.993 -.883z\", \"key\": \"svg-0\" }]];\nconst IconAlertCircleFilled = createReactComponent(\"filled\", \"alert-circle-filled\", \"AlertCircleFilled\", __iconNode);\n\nexport { __iconNode, IconAlertCircleFilled as default };\n//# sourceMappingURL=IconAlertCircleFilled.mjs.map\n","import {\n useState,\n type ComponentType,\n type InputHTMLAttributes,\n type ReactNode,\n type Ref,\n} from \"react\";\nimport {\n IconAlertCircleFilled,\n IconEye,\n IconEyeOff,\n} from \"@tabler/icons-react\";\nimport { Field } from \"@base-ui/react/field\";\nimport { cn } from \"../../utils/cn\";\n\nexport type InputSize = \"sm\" | \"md\" | \"lg\";\n\n// Control padding per size. The wrapper carries a 2px inset (`p-0.5`) so the\n// control's border box — and the browser's autofill fill it paints — sits\n// inside the ring instead of over it; these paddings are the documented\n// effective padding (sm/md 12/8px, lg 14/10px) minus that 2px inset, so the\n// text position and total field height are unchanged.\nconst SIZE_PAD: Record<InputSize, string> = {\n // 16px on small viewports — iOS zooms into any focused field under 16px.\n sm: \"px-2.5 py-1.5 text-md md:text-base\",\n md: \"px-2.5 py-1.5 text-md\",\n lg: \"px-3 py-2 text-md\",\n};\n// Effective icon clearance minus the wrapper's 2px inset.\nconst LEADING_PAD: Record<InputSize, string> = {\n sm: \"pl-8.5\",\n md: \"pl-9.5\",\n lg: \"pl-10\",\n};\n// Icons are absolutely positioned, so they're placed from the wrapper's\n// padding edge — which, with no border, is the visible field edge; the\n// wrapper's `p-0.5` does not offset them.\nconst LEADING_ICON_POS: Record<InputSize, string> = {\n sm: \"left-3 size-4\",\n md: \"left-3 size-5\",\n lg: \"left-3.5 size-5\",\n};\n// Wrapper corner radius — `rounded-base` (12px) at every size; `lg` also gets\n// `squircle` superellipse smoothing (the ring is a box-shadow on this wrapper,\n// so it follows the border-radius automatically).\nconst SIZE_RADIUS: Record<InputSize, string> = {\n sm: \"rounded-base\",\n md: \"rounded-base\",\n lg: \"squircle rounded-base\",\n};\n// Concentric inner-curve cover, `lg` only. Nested corner radii must follow\n// innerRadius = outerRadius − inset, so the hairline's inner edge at a 12px\n// outer radius is 11px at rest (ring-1) and 10px when focused (ring-2).\n// Browsers derive that automatically for plain round corners, but with\n// `corner-shape: superellipse(...)` shipping engines still scale (rather than\n// offset) the inner shadow curve, which renders the ring visibly thicker at\n// the four corners (see w3c/csswg-drafts#12961). This ::before repaints the\n// field surface over everything inside the correct concentric curve — a round\n// radius is the right parallel-offset approximation of the mild superellipse —\n// so the ring reads as a uniform hairline in every engine. `isolate` +\n// `-z-10` slot the cover above the wrapper's background/ring but below the\n// control's text, icons, and autofill fill. `sm`/`md` have no corner-shape,\n// so the native (already concentric) rendering is kept there.\n// The ::before is absolutely positioned, so its inset is measured from the\n// wrapper's border box (border 0): 1px in at rest, 2px in when focused.\nconst SIZE_RING_COVER: Record<InputSize, string> = {\n sm: \"\",\n md: \"\",\n lg: cn(\n \"isolate before:pointer-events-none before:absolute before:-z-10 before:content-['']\",\n \"before:bg-pho-primary before:inset-px before:rounded-[calc(var(--radius-base)-1px)]\",\n // Invalid sits between rest (1px) and focus (2px) — keep the cover concentric.\n \"data-[invalid]:before:inset-[1.5px] data-[invalid]:before:rounded-[calc(var(--radius-base)-1.5px)]\",\n \"has-[input:focus-visible]:before:inset-0.5 has-[input:focus-visible]:before:rounded-[calc(var(--radius-base)-2px)]\",\n \"before:transition-[inset,border-radius] before:duration-100 before:ease-linear\",\n ),\n};\n// The control's border box sits 2px inside the wrapper (its `p-0.5` inset),\n// clear of both ring widths, so the browser's autofill fill paints inside the\n// hairline instead of over it. Rounding follows inner = outer − gap, derived\n// from the token with calc. Round (not squircle) — the parallel offset of\n// the wrapper's mild superellipse is effectively circular.\nconst SIZE_AUTOFILL_RADIUS: Record<InputSize, string> = {\n sm: \"autofill:rounded-[calc(var(--radius-base)-2px)]\",\n md: \"autofill:rounded-[calc(var(--radius-base)-2px)]\",\n lg: \"autofill:rounded-[calc(var(--radius-base)-2px)]\",\n};\n\nexport interface InputProps extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"size\"\n> {\n /** Field label (associated for accessibility). */\n label?: ReactNode;\n /** Helper text below the field; rendered in the error color when `invalid`. */\n hint?: ReactNode;\n /** Size variant. @default \"md\" */\n size?: InputSize;\n /** Render the invalid state (ring + icon + error-colored hint). */\n invalid?: boolean;\n /** Leading icon component (e.g. a Tabler icon). */\n icon?: ComponentType<{ className?: string }>;\n /** Node rendered at the right edge of the label row (e.g. \"Forgot?\"). */\n labelTrailing?: ReactNode;\n /** Class applied to the input wrapper. */\n wrapperClassName?: string;\n ref?: Ref<HTMLInputElement>;\n}\n\n/**\n * Text input built on Base UI `Field` (label/description a11y wiring) with\n * Pho's bordered surface, leading icon, invalid state, and a built-in password\n * reveal toggle.\n */\nexport function Input({\n label,\n hint,\n size = \"md\",\n invalid,\n icon: Icon,\n labelTrailing,\n disabled,\n required,\n type = \"text\",\n className,\n wrapperClassName,\n id,\n ref,\n ...props\n}: InputProps) {\n const [reveal, setReveal] = useState(false);\n const isPassword = type === \"password\";\n const resolvedType = isPassword && reveal ? \"text\" : type;\n const hasLeadingIcon = Boolean(Icon);\n const hasTrailingIcon = isPassword || invalid;\n\n return (\n <Field.Root\n disabled={disabled}\n className=\"group/field flex w-full flex-col gap-1.5\"\n >\n {label && (\n <div className=\"flex items-center justify-between gap-2\">\n <Field.Label className=\"text-pho-secondary flex cursor-default items-center gap-0.5 text-base font-medium\">\n {label}\n {required && <span className=\"text-pho-brand\">*</span>}\n </Field.Label>\n {labelTrailing}\n </div>\n )}\n\n <div\n data-invalid={invalid || undefined}\n className={cn(\n // Flat field surface — hairline ring only; elevation shadows are\n // reserved for floating layers (menus/popovers/dialogs). `p-0.5`\n // insets the control 2px (the focused ring width) so its background\n // and the browser's autofill fill nest inside the ring; the size\n // maps compensate so effective padding/height are unchanged.\n \"bg-pho-primary ring-pho-primary relative flex w-full items-center p-0.5 ring-1 transition-shadow duration-100 ease-linear ring-inset\",\n SIZE_RADIUS[size],\n SIZE_RING_COVER[size],\n \"has-[input:focus-visible]:ring-pho-brand has-[input:focus-visible]:ring-2\",\n // Invalid is mid-weight: thicker than the 1px rest hairline, thinner\n // than the 2px focus ring. Rest uses the subtle error ink; focus\n // steps up to the solid error (same hue, full opacity).\n \"data-[invalid]:ring-pho-error-subtle data-[invalid]:has-[input:focus-visible]:ring-pho-error data-[invalid]:ring-[1.5px]\",\n \"group-data-[disabled]/field:cursor-not-allowed group-data-[disabled]/field:opacity-50\",\n wrapperClassName,\n )}\n >\n {Icon && (\n <Icon\n className={cn(\n \"text-pho-secondary pointer-events-none absolute\",\n LEADING_ICON_POS[size],\n )}\n />\n )}\n\n <Field.Control\n ref={ref}\n id={id}\n type={resolvedType}\n required={required}\n {...props}\n className={cn(\n \"text-pho-primary placeholder:text-pho-placeholder m-0 w-full bg-transparent outline-none disabled:cursor-not-allowed\",\n SIZE_PAD[size],\n SIZE_AUTOFILL_RADIUS[size],\n hasLeadingIcon && LEADING_PAD[size],\n // 34px + the wrapper's 2px inset = the previous 36px clearance.\n hasTrailingIcon && \"pr-8.5\",\n className,\n )}\n />\n\n {invalid && !isPassword && (\n <IconAlertCircleFilled className=\"text-pho-error pointer-events-none absolute right-3 size-4\" />\n )}\n\n {isPassword && (\n <button\n type=\"button\"\n // Reachable by click but skipped in tab order so Tab moves field→field.\n tabIndex={-1}\n aria-label=\"Toggle password visibility\"\n onClick={() => setReveal((v) => !v)}\n className=\"text-pho-secondary hover:text-pho-secondary-hover absolute right-3 flex cursor-pointer items-center transition duration-100 ease-linear focus:outline-none\"\n >\n {reveal ? (\n <IconEyeOff className=\"size-4\" />\n ) : (\n <IconEye className=\"size-4\" />\n )}\n </button>\n )}\n </div>\n\n {hint && (\n <Field.Description\n className={cn(\n \"text-pho-description text-base\",\n invalid && \"text-pho-error\",\n )}\n >\n {hint}\n </Field.Description>\n )}\n </Field.Root>\n );\n}\n\nInput.displayName = \"Input\";\n"],"x_google_ignoreList":[0,1,2],"mappings":";;;;;;AASA,IAAMA,IAAa;AAAA,EAAC,CAAC,QAAQ;AAAA,IAAE,GAAK;AAAA,IAAwC,KAAO;AAAA,EAAQ,CAAC;AAAA,EAAG,CAAC,QAAQ;AAAA,IAAE,GAAK;AAAA,IAAuM,KAAO;AAAA,EAAQ,CAAC;AAAA,EAAG,CAAC,QAAQ;AAAA,IAAE,GAAK;AAAA,IAAc,KAAO;AAAA,EAAQ,CAAC;AAAC,GAClX,IAAa,EAAqB,WAAW,WAAW,UAAUA,CAAU,GCD5EC,IAAa,CAAC,CAAC,QAAQ;AAAA,EAAE,GAAK;AAAA,EAAuC,KAAO;AAAQ,CAAC,GAAG,CAAC,QAAQ;AAAA,EAAE,GAAK;AAAA,EAAqF,KAAO;AAAQ,CAAC,CAAC,GAC9M,IAAU,EAAqB,WAAW,OAAO,OAAOA,CAAU,GCDlE,IAAa,CAAC,CAAC,QAAQ;AAAA,EAAE,GAAK;AAAA,EAAoU,KAAO;AAAQ,CAAC,CAAC,GACnX,IAAwB,EAAqB,UAAU,uBAAuB,qBAAqB,CAAU,GCY7G,IAAsC;AAAA,EAE1C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN,GAEM,IAAyC;AAAA,EAC7C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN,GAIM,IAA8C;AAAA,EAClD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN,GAIM,IAAyC;AAAA,EAC7C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN,GAgBM,IAA6C;AAAA,EACjD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI,EACF,uFACA,uFAEA,sGACA,sHACA,gFACF;AACF,GAMM,IAAkD;AAAA,EACtD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AA4BA,SAAgB,EAAM,EACpB,OAAA,GACA,MAAA,GACA,MAAA,IAAO,MACP,SAAA,GACA,MAAM,GACN,eAAA,GACA,UAAA,GACA,UAAA,GACA,MAAA,IAAO,QACP,WAAA,GACA,kBAAA,GACA,IAAA,GACA,KAAA,GACA,GAAG,EAAA,GACU;AACb,QAAM,CAAC,GAAQ,CAAA,IAAa,EAAS,EAAK,GACpC,IAAa,MAAS,YACtB,IAAe,KAAc,IAAS,SAAS,GAC/C,IAAiB,EAAQ,GACzB,IAAkB,KAAc;AAEtC,SACE,gBAAA,EAAC,EAAM,MAAP;AAAA,IACY,UAAA;AAAA,IACV,WAAU;AAAA,cAFZ;AAAA,MAIG,KACC,gBAAA,EAAC,OAAD;AAAA,QAAK,WAAU;AAAA,kBAAf,CACE,gBAAA,EAAC,EAAM,OAAP;AAAA,UAAa,WAAU;AAAA,oBAAvB,CACG,GACA,KAAY,gBAAA,EAAC,QAAD;AAAA,YAAM,WAAU;AAAA,sBAAiB;AAAA,UAAO,CAAA,CAC1C;AAAA,YACZ,CACE;AAAA;MAGP,gBAAA,EAAC,OAAD;AAAA,QACE,gBAAc,KAAW;AAAA,QACzB,WAAW,EAMT,wIACA,EAAY,CAAA,GACZ,EAAgB,CAAA,GAChB,6EAIA,4HACA,yFACA,CACF;AAAA,kBAlBF;AAAA,UAoBG,KACC,gBAAA,EAAC,GAAD,EACE,WAAW,EACT,mDACA,EAAiB,CAAA,CACnB,EACD,CAAA;AAAA,UAGH,gBAAA,EAAC,EAAM,SAAP;AAAA,YACO,KAAA;AAAA,YACD,IAAA;AAAA,YACJ,MAAM;AAAA,YACI,UAAA;AAAA,YACV,GAAI;AAAA,YACJ,WAAW,EACT,wHACA,EAAS,CAAA,GACT,EAAqB,CAAA,GACrB,KAAkB,EAAY,CAAA,GAE9B,KAAmB,UACnB,CACF;AAAA,UACD,CAAA;AAAA,UAEA,KAAW,CAAC,KACX,gBAAA,EAAC,GAAD,EAAuB,WAAU,6DAA8D,CAAA;AAAA,UAGhG,KACC,gBAAA,EAAC,UAAD;AAAA,YACE,MAAK;AAAA,YAEL,UAAU;AAAA,YACV,cAAW;AAAA,YACX,SAAA,MAAe,EAAA,CAAW,MAAM,CAAC,CAAC;AAAA,YAClC,WAAU;AAAA,sBAET,IACC,gBAAA,EAAC,GAAD,EAAY,WAAU,SAAU,CAAA,IAEhC,gBAAA,EAAC,GAAD,EAAS,WAAU,SAAU,CAAA;AAAA,UAEzB,CAAA;AAAA,QAEP;AAAA;MAEJ,KACC,gBAAA,EAAC,EAAM,aAAP;AAAA,QACE,WAAW,EACT,kCACA,KAAW,gBACb;AAAA,kBAEC;AAAA,MACgB,CAAA;AAAA,IAEX;AAAA;AAEhB;AAEA,EAAM,cAAc"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { t as i } from "./cn-DTiB6ek_.js";
|
|
3
|
+
import { createContext as b, useContext as p } from "react";
|
|
4
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
5
|
+
import { Field as f } from "@base-ui/react/field";
|
|
6
|
+
import { Separator as m } from "@base-ui/react/separator";
|
|
7
|
+
var l = b({
|
|
8
|
+
size: "md",
|
|
9
|
+
invalid: !1
|
|
10
|
+
}), v = {
|
|
11
|
+
sm: "gap-2 px-3 text-md md:text-base",
|
|
12
|
+
md: "gap-2 px-3 text-md",
|
|
13
|
+
lg: "gap-2 px-3.5 text-md"
|
|
14
|
+
}, h = {
|
|
15
|
+
sm: "my-0.5 py-1.5",
|
|
16
|
+
md: "my-0.5 py-1.5",
|
|
17
|
+
lg: "my-0.5 py-2"
|
|
18
|
+
}, g = {
|
|
19
|
+
sm: "[&_svg]:size-4",
|
|
20
|
+
md: "[&_svg]:size-5",
|
|
21
|
+
lg: "[&_svg]:size-5"
|
|
22
|
+
}, x = {
|
|
23
|
+
sm: "rounded-base",
|
|
24
|
+
md: "rounded-base",
|
|
25
|
+
lg: "squircle rounded-base"
|
|
26
|
+
}, y = {
|
|
27
|
+
sm: "",
|
|
28
|
+
md: "",
|
|
29
|
+
lg: i("isolate before:pointer-events-none before:absolute before:-z-10 before:content-['']", "before:bg-pho-primary before:inset-px before:rounded-[calc(var(--radius-base)-1px)]", "data-[invalid]:before:inset-[1.5px] data-[invalid]:before:rounded-[calc(var(--radius-base)-1.5px)]", "has-[:focus-visible]:before:inset-0.5 has-[:focus-visible]:before:rounded-[calc(var(--radius-base)-2px)]", "before:transition-[inset,border-radius] before:duration-100 before:ease-linear")
|
|
30
|
+
};
|
|
31
|
+
function I({ size: e = "md", invalid: r, className: a, onPointerDown: o, ref: s, ...u }) {
|
|
32
|
+
const c = (t) => {
|
|
33
|
+
if (o?.(t), t.defaultPrevented || t.target.closest("input, button, a, select, textarea, label")) return;
|
|
34
|
+
const d = t.currentTarget.querySelector("input");
|
|
35
|
+
!d || d.disabled || (t.preventDefault(), d.focus());
|
|
36
|
+
};
|
|
37
|
+
return /* @__PURE__ */ n(l.Provider, {
|
|
38
|
+
value: {
|
|
39
|
+
size: e,
|
|
40
|
+
invalid: !!r
|
|
41
|
+
},
|
|
42
|
+
children: /* @__PURE__ */ n("div", {
|
|
43
|
+
ref: s,
|
|
44
|
+
"data-invalid": r || void 0,
|
|
45
|
+
onPointerDown: c,
|
|
46
|
+
className: i("bg-pho-primary ring-pho-primary relative flex w-full cursor-text items-center ring-1 transition-shadow duration-100 ease-linear ring-inset", v[e], x[e], y[e], "has-[:focus-visible]:ring-pho-brand has-[:focus-visible]:ring-2", "has-[button[data-popup-open]]:ring-pho-brand has-[button[data-popup-open]]:ring-2", "data-[invalid]:ring-pho-error-subtle data-[invalid]:has-[:focus-visible]:ring-pho-error data-[invalid]:has-[button[data-popup-open]]:ring-pho-error data-[invalid]:ring-[1.5px]", "has-[input:disabled]:cursor-not-allowed has-[input:disabled]:opacity-50", a),
|
|
47
|
+
...u
|
|
48
|
+
})
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function _({ className: e, ref: r, ...a }) {
|
|
52
|
+
const { size: o, invalid: s } = p(l);
|
|
53
|
+
return /* @__PURE__ */ n(f.Control, {
|
|
54
|
+
ref: r,
|
|
55
|
+
"aria-invalid": s || void 0,
|
|
56
|
+
...a,
|
|
57
|
+
className: i("text-pho-primary placeholder:text-pho-placeholder m-0 w-full min-w-0 flex-1 bg-transparent outline-none disabled:cursor-not-allowed", "autofill:rounded-[calc(var(--radius-base)-2px)]", h[o], e)
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function S({ className: e, ref: r, ...a }) {
|
|
61
|
+
const { size: o } = p(l);
|
|
62
|
+
return /* @__PURE__ */ n("div", {
|
|
63
|
+
ref: r,
|
|
64
|
+
className: i("text-pho-secondary flex shrink-0 items-center gap-1 select-none", "[&_svg]:shrink-0", g[o], e),
|
|
65
|
+
...a
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function w({ className: e, orientation: r = "vertical", ...a }) {
|
|
69
|
+
return /* @__PURE__ */ n(m, {
|
|
70
|
+
orientation: r,
|
|
71
|
+
className: i("my-0.5 w-px shrink-0 self-stretch bg-[var(--border-color-pho-secondary)]", e),
|
|
72
|
+
...a
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
var R = {
|
|
76
|
+
Root: I,
|
|
77
|
+
Input: _,
|
|
78
|
+
Addon: S,
|
|
79
|
+
Separator: w
|
|
80
|
+
};
|
|
81
|
+
export {
|
|
82
|
+
R as t
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
//# sourceMappingURL=input-group-D6dlma8k.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-group-D6dlma8k.js","names":[],"sources":["../../src/components/input-group/input-group.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n type ComponentProps,\n type ElementType,\n type HTMLAttributes,\n type InputHTMLAttributes,\n type PointerEvent,\n type Ref,\n} from \"react\";\nimport { Field } from \"@base-ui/react/field\";\nimport { Separator as BaseSeparator } from \"@base-ui/react/separator\";\nimport { cn } from \"../../utils/cn\";\n\n/** Override Base UI's `string | (state) => string` className with plain string. */\ntype StyledProps<T extends ElementType> = Omit<\n ComponentProps<T>,\n \"className\"\n> & {\n className?: string;\n};\n\nexport type InputGroupSize = \"sm\" | \"md\" | \"lg\";\n\n/** Lets `Addon` and the control read the Root's size/invalid state. */\nconst InputGroupContext = createContext<{\n size: InputGroupSize;\n invalid: boolean;\n}>({ size: \"md\", invalid: false });\n\n// Mirrors Input's size math (see input.tsx): the group surface carries the\n// horizontal padding and text scale, the control carries the vertical\n// padding. Effective metrics — sm/md 12/8px, lg 14/10px — land the same\n// 36/40/44px field heights, so grouped and plain inputs align in a form.\nconst SIZE_ROOT: Record<InputGroupSize, string> = {\n // 16px on small viewports — iOS zooms into any focused field under 16px.\n sm: \"gap-2 px-3 text-md md:text-base\",\n md: \"gap-2 px-3 text-md\",\n lg: \"gap-2 px-3.5 text-md\",\n};\n// `my-0.5` insets the control 2px (the focused ring width) so the browser's\n// autofill fill nests inside the ring; `py` makes up the rest of the height.\nconst SIZE_CONTROL: Record<InputGroupSize, string> = {\n sm: \"my-0.5 py-1.5\",\n md: \"my-0.5 py-1.5\",\n lg: \"my-0.5 py-2\",\n};\n// In-field icon size per group size — same scale as Input's leading icon.\nconst SIZE_ADDON_ICON: Record<InputGroupSize, string> = {\n sm: \"[&_svg]:size-4\",\n md: \"[&_svg]:size-5\",\n lg: \"[&_svg]:size-5\",\n};\n// `lg` gets the squircle treatment like Input; sm/md stay plain round.\nconst SIZE_RADIUS: Record<InputGroupSize, string> = {\n sm: \"rounded-base\",\n md: \"rounded-base\",\n lg: \"squircle rounded-base\",\n};\n// Concentric inner-curve cover for the `lg` squircle — same engine workaround\n// as Input (see input.tsx `SIZE_RING_COVER` for the full story).\nconst SIZE_RING_COVER: Record<InputGroupSize, string> = {\n sm: \"\",\n md: \"\",\n lg: cn(\n \"isolate before:pointer-events-none before:absolute before:-z-10 before:content-['']\",\n \"before:bg-pho-primary before:inset-px before:rounded-[calc(var(--radius-base)-1px)]\",\n // Invalid sits between rest (1px) and focus (2px) — keep the cover concentric.\n \"data-[invalid]:before:inset-[1.5px] data-[invalid]:before:rounded-[calc(var(--radius-base)-1.5px)]\",\n \"has-[:focus-visible]:before:inset-0.5 has-[:focus-visible]:before:rounded-[calc(var(--radius-base)-2px)]\",\n \"before:transition-[inset,border-radius] before:duration-100 before:ease-linear\",\n ),\n};\n\nexport interface InputGroupRootProps extends HTMLAttributes<HTMLDivElement> {\n /** Size variant, matching Input's scale. @default \"md\" */\n size?: InputGroupSize;\n /** Render the invalid state (error ring; `aria-invalid` on the control). */\n invalid?: boolean;\n ref?: Ref<HTMLDivElement>;\n}\n\n/**\n * The shared field surface: one hairline ring around the control and its\n * addons, with the same focus / invalid / disabled treatment as `Input`.\n * Clicking anywhere on the surface (addon text, icons, blank space) focuses\n * the control, so the whole group reads as a single field.\n */\nfunction Root({\n size = \"md\",\n invalid,\n className,\n onPointerDown,\n ref,\n ...props\n}: InputGroupRootProps) {\n const handlePointerDown = (event: PointerEvent<HTMLDivElement>) => {\n onPointerDown?.(event);\n if (event.defaultPrevented) return;\n // Interactive children keep their own pointer behavior (and the control\n // itself keeps native caret placement).\n const target = event.target as HTMLElement;\n if (target.closest(\"input, button, a, select, textarea, label\")) return;\n const input = event.currentTarget.querySelector(\"input\");\n if (!input || input.disabled) return;\n // Focus the control instead of the wrapper — and don't blur it again.\n event.preventDefault();\n input.focus();\n };\n\n return (\n <InputGroupContext.Provider value={{ size, invalid: Boolean(invalid) }}>\n <div\n ref={ref}\n data-invalid={invalid || undefined}\n onPointerDown={handlePointerDown}\n className={cn(\n // Flat field surface — hairline ring only, same as Input; elevation\n // shadows are reserved for floating layers.\n \"bg-pho-primary ring-pho-primary relative flex w-full cursor-text items-center ring-1 transition-shadow duration-100 ease-linear ring-inset\",\n SIZE_ROOT[size],\n SIZE_RADIUS[size],\n SIZE_RING_COVER[size],\n // Any focusable child (input, a Select trigger, an addon button)\n // lights the shared ring — including an open Select popup.\n \"has-[:focus-visible]:ring-pho-brand has-[:focus-visible]:ring-2\",\n \"has-[button[data-popup-open]]:ring-pho-brand has-[button[data-popup-open]]:ring-2\",\n // Invalid is mid-weight: thicker than the 1px rest hairline, thinner\n // than the 2px focus / open ring. Rest uses the subtle error ink;\n // focus / open steps up to the solid error.\n \"data-[invalid]:ring-pho-error-subtle data-[invalid]:has-[:focus-visible]:ring-pho-error data-[invalid]:has-[button[data-popup-open]]:ring-pho-error data-[invalid]:ring-[1.5px]\",\n \"has-[input:disabled]:cursor-not-allowed has-[input:disabled]:opacity-50\",\n className,\n )}\n {...props}\n />\n </InputGroupContext.Provider>\n );\n}\n\nexport interface InputGroupInputProps extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"size\"\n> {\n ref?: Ref<HTMLInputElement>;\n}\n\n/**\n * The text control. Rendered as Base UI `Field.Control`, so inside a\n * `Field.Root` it picks up the label and description wiring automatically;\n * standalone it behaves as a plain `<input>` (pass `aria-label` then).\n */\nfunction GroupInput({ className, ref, ...props }: InputGroupInputProps) {\n const { size, invalid } = useContext(InputGroupContext);\n return (\n <Field.Control\n ref={ref}\n aria-invalid={invalid || undefined}\n {...props}\n className={cn(\n \"text-pho-primary placeholder:text-pho-placeholder m-0 w-full min-w-0 flex-1 bg-transparent outline-none disabled:cursor-not-allowed\",\n // Rounding for the autofill fill nested 2px inside the ring.\n \"autofill:rounded-[calc(var(--radius-base)-2px)]\",\n SIZE_CONTROL[size],\n className,\n )}\n />\n );\n}\n\n/**\n * Inline content beside the control — prefix/suffix text, an icon, a `Kbd`\n * hint, or a small button. Text and icons render in the secondary tone so the\n * value stays the loudest thing on the surface.\n */\nfunction Addon({\n className,\n ref,\n ...props\n}: HTMLAttributes<HTMLDivElement> & { ref?: Ref<HTMLDivElement> }) {\n const { size } = useContext(InputGroupContext);\n return (\n <div\n ref={ref}\n className={cn(\n \"text-pho-secondary flex shrink-0 items-center gap-1 select-none\",\n \"[&_svg]:shrink-0\",\n SIZE_ADDON_ICON[size],\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * A hairline between segments — turns an addon into a boxed section\n * (`https:// │ workspace │ .photon.dev`).\n */\nfunction GroupSeparator({\n className,\n orientation = \"vertical\",\n ...props\n}: StyledProps<typeof BaseSeparator>) {\n return (\n <BaseSeparator\n orientation={orientation}\n className={cn(\n // `my-0.5` keeps the line 2px clear of the ring (children paint over\n // the inset box-shadow, so a full-bleed line would notch it) — the\n // same inset the control itself uses, covering the focused 2px ring.\n \"my-0.5 w-px shrink-0 self-stretch bg-[var(--border-color-pho-secondary)]\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * Input Group — one field surface shared by a text control and its addons:\n * prefix/suffix text, icons, keyboard hints, or a trailing button. Same\n * ring, sizes, and focus / invalid / disabled behavior as `Input`, so grouped\n * and plain fields align in the same form. For a leading icon alone, plain\n * `Input` with `icon` is enough.\n */\nexport const InputGroup = {\n Root,\n Input: GroupInput,\n Addon,\n Separator: GroupSeparator,\n};\n"],"mappings":";;;;;;AAyBA,IAAM,IAAoB,EAGvB;AAAA,EAAE,MAAM;AAAA,EAAM,SAAS;AAAM,CAAC,GAM3B,IAA4C;AAAA,EAEhD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN,GAGM,IAA+C;AAAA,EACnD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN,GAEM,IAAkD;AAAA,EACtD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN,GAEM,IAA8C;AAAA,EAClD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN,GAGM,IAAkD;AAAA,EACtD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI,EACF,uFACA,uFAEA,sGACA,4GACA,gFACF;AACF;AAgBA,SAAS,EAAK,EACZ,MAAA,IAAO,MACP,SAAA,GACA,WAAA,GACA,eAAA,GACA,KAAA,GACA,GAAG,EAAA,GACmB;AACtB,QAAM,IAAA,CAAqB,MAAwC;AAMjE,QALA,IAAgB,CAAK,GACjB,EAAM,oBAGK,EAAM,OACV,QAAQ,2CAA2C,EAAG;AACjE,UAAM,IAAQ,EAAM,cAAc,cAAc,OAAO;AACvD,IAAI,CAAC,KAAS,EAAM,aAEpB,EAAM,eAAe,GACrB,EAAM,MAAM;AAAA,EACd;AAEA,SACE,gBAAA,EAAC,EAAkB,UAAnB;AAAA,IAA4B,OAAO;AAAA,MAAE,MAAA;AAAA,MAAM,SAAS,EAAQ;AAAA,IAAS;AAAA,cACnE,gBAAA,EAAC,OAAD;AAAA,MACO,KAAA;AAAA,MACL,gBAAc,KAAW;AAAA,MACzB,eAAe;AAAA,MACf,WAAW,EAGT,8IACA,EAAU,CAAA,GACV,EAAY,CAAA,GACZ,EAAgB,CAAA,GAGhB,mEACA,qFAIA,mLACA,2EACA,CACF;AAAA,MACA,GAAI;AAAA,IACL,CAAA;AAAA,EACyB,CAAA;AAEhC;AAcA,SAAS,EAAW,EAAE,WAAA,GAAW,KAAA,GAAK,GAAG,EAAA,GAA+B;AACtE,QAAM,EAAE,MAAA,GAAM,SAAA,EAAA,IAAY,EAAW,CAAiB;AACtD,SACE,gBAAA,EAAC,EAAM,SAAP;AAAA,IACO,KAAA;AAAA,IACL,gBAAc,KAAW;AAAA,IACzB,GAAI;AAAA,IACJ,WAAW,EACT,uIAEA,mDACA,EAAa,CAAA,GACb,CACF;AAAA,EACD,CAAA;AAEL;AAOA,SAAS,EAAM,EACb,WAAA,GACA,KAAA,GACA,GAAG,EAAA,GAC8D;AACjE,QAAM,EAAE,MAAA,EAAA,IAAS,EAAW,CAAiB;AAC7C,SACE,gBAAA,EAAC,OAAD;AAAA,IACO,KAAA;AAAA,IACL,WAAW,EACT,mEACA,oBACA,EAAgB,CAAA,GAChB,CACF;AAAA,IACA,GAAI;AAAA,EACL,CAAA;AAEL;AAMA,SAAS,EAAe,EACtB,WAAA,GACA,aAAA,IAAc,YACd,GAAG,EAAA,GACiC;AACpC,SACE,gBAAA,EAAC,GAAD;AAAA,IACe,aAAA;AAAA,IACb,WAAW,EAIT,4EACA,CACF;AAAA,IACA,GAAI;AAAA,EACL,CAAA;AAEL;AASA,IAAa,IAAa;AAAA,EACxB,MAAA;AAAA,EACA,OAAO;AAAA,EACP,OAAA;AAAA,EACA,WAAW;AACb"}
|
|
@@ -4,8 +4,8 @@ import { t as I } from "./resolve-variant-CcD8iG5l.js";
|
|
|
4
4
|
import { n as S } from "./spinner-Cy8bkyJf.js";
|
|
5
5
|
import { createContext as E, useCallback as T, useContext as P, useEffect as N, useMemo as G, useRef as b, useState as C } from "react";
|
|
6
6
|
import { Fragment as U, jsx as r, jsxs as x } from "react/jsx-runtime";
|
|
7
|
-
import { Menu as t } from "@base-ui/react/menu";
|
|
8
7
|
import { Separator as _ } from "@base-ui/react/separator";
|
|
8
|
+
import { Menu as t } from "@base-ui/react/menu";
|
|
9
9
|
var y = E(null);
|
|
10
10
|
function ce() {
|
|
11
11
|
return P(y)?.pending ?? !1;
|
|
@@ -205,4 +205,4 @@ export {
|
|
|
205
205
|
f as t
|
|
206
206
|
};
|
|
207
207
|
|
|
208
|
-
//# sourceMappingURL=menu-
|
|
208
|
+
//# sourceMappingURL=menu-BSXds3Rt.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-Dan0oRvL.js","names":[],"sources":["../../src/components/menu/menu-pending.tsx","../../src/components/menu/menu.tsx"],"sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ComponentType,\n type MouseEvent as ReactMouseEvent,\n type ReactNode,\n type RefObject,\n} from \"react\";\nimport { Spinner } from \"../spinner/spinner\";\n\n/** Imperative actions exposed by Base UI menu `actionsRef`. */\ninterface RootActions {\n unmount: () => void;\n close: () => void;\n}\n\ninterface MenuPendingContextValue {\n /** True while a menu item promise is in flight. */\n pending: boolean;\n beginPending: () => void;\n endPending: () => void;\n /** Close the menu imperatively (used by async items on resolve). */\n close: () => void;\n}\n\nconst MenuPendingContext = createContext<MenuPendingContextValue | null>(null);\n\n/**\n * Whether the surrounding menu has an async item promise in flight —\n * use it to disable sibling items while work is pending.\n * Returns `false` outside a `Menu.Root` / `ContextMenu.Root`.\n */\nexport function useMenuPending(): boolean {\n return useContext(MenuPendingContext)?.pending ?? false;\n}\n\nexport function isThenable(value: unknown): value is Promise<unknown> {\n return (\n value != null && typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\ntype PreventableMouseEvent = ReactMouseEvent<HTMLElement> & {\n preventBaseUIHandler?: () => void;\n};\n\ntype RootWithPendingProps = {\n onOpenChange?: (open: boolean, eventDetails: { cancel: () => void }) => void;\n actionsRef?: RefObject<RootActions | null>;\n children?: ReactNode;\n};\n\n/**\n * Wraps a Base UI menu root so async `Menu.Item` clicks can lock dismissal\n * (outside press, Escape) until the promise settles.\n */\nexport function withMenuPendingRoot<P extends RootWithPendingProps>(\n BaseRoot: ComponentType<P>,\n): ComponentType<P> {\n function PendingRoot({ onOpenChange, actionsRef, ...props }: P) {\n const [pending, setPending] = useState(false);\n // Guard reads a ref so a resolve → close() in the same tick isn't blocked\n // by a not-yet-flushed state update.\n const pendingRef = useRef(false);\n const internalActionsRef = useRef<RootActions | null>(null);\n\n useEffect(() => {\n if (!actionsRef) return;\n // Proxy so callers always hit the live Base UI actions, even if the\n // popup remounts after this effect runs.\n actionsRef.current = {\n unmount: () => internalActionsRef.current?.unmount(),\n close: () => internalActionsRef.current?.close(),\n };\n return () => {\n actionsRef.current = null;\n };\n }, [actionsRef]);\n\n const handleOpenChange = useCallback(\n (open: boolean, eventDetails: { cancel: () => void }) => {\n if (!open && pendingRef.current) {\n eventDetails.cancel();\n return;\n }\n onOpenChange?.(open, eventDetails);\n },\n [onOpenChange],\n );\n\n const contextValue = useMemo<MenuPendingContextValue>(\n () => ({\n pending,\n beginPending: () => {\n pendingRef.current = true;\n setPending(true);\n },\n endPending: () => {\n pendingRef.current = false;\n setPending(false);\n },\n close: () => internalActionsRef.current?.close(),\n }),\n [pending],\n );\n\n return (\n <MenuPendingContext.Provider value={contextValue}>\n <BaseRoot\n {...(props as P)}\n actionsRef={internalActionsRef}\n onOpenChange={handleOpenChange}\n />\n </MenuPendingContext.Provider>\n );\n }\n\n return PendingRoot as ComponentType<P>;\n}\n\nexport type AsyncMenuItemClick = (\n event: ReactMouseEvent<HTMLElement>,\n) => void | Promise<unknown>;\n\n/**\n * Shared click handler for `Menu.Item` / `ContextMenu.Item` promise support.\n * Returns whether the item should show its loading state.\n */\nexport function useAsyncMenuItem({\n onClick,\n closeOnClick = true,\n loading = false,\n disabled = false,\n}: {\n onClick?: AsyncMenuItemClick;\n closeOnClick?: boolean;\n loading?: boolean;\n disabled?: boolean;\n}) {\n const pendingContext = useContext(MenuPendingContext);\n const menuPending = pendingContext?.pending ?? false;\n const [busy, setBusy] = useState(false);\n const mountedRef = useRef(true);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const inert = busy || loading;\n // Disable siblings while any item promise is in flight — not the busy item itself.\n const blocked = disabled || (menuPending && !busy);\n\n function handleClick(event: ReactMouseEvent<HTMLElement>) {\n if (inert || (menuPending && !busy)) {\n (event as PreventableMouseEvent).preventBaseUIHandler?.();\n return;\n }\n const result = onClick?.(event);\n if (!isThenable(result)) {\n return;\n }\n // Stop Base UI's closeOnClick for this press; close on resolve instead.\n (event as PreventableMouseEvent).preventBaseUIHandler?.();\n setBusy(true);\n pendingContext?.beginPending();\n result.then(\n () => {\n pendingContext?.endPending();\n if (mountedRef.current) setBusy(false);\n if (closeOnClick) pendingContext?.close();\n },\n (error: unknown) => {\n pendingContext?.endPending();\n if (mountedRef.current) setBusy(false);\n if (\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV !== \"production\"\n ) {\n console.error(\"Menu.Item: async onClick rejected —\", error);\n }\n },\n );\n }\n\n return { inert, blocked, handleClick };\n}\n\n/** Menu-row spinner — keeps `data-menu-spinner` for the hide-siblings CSS. */\nexport function MenuItemSpinner() {\n return <Spinner data-menu-spinner=\"\" size=\"md\" />;\n}\n","import {\n type ComponentProps,\n type ElementType,\n type MouseEvent as ReactMouseEvent,\n} from \"react\";\nimport { Menu as BaseMenu } from \"@base-ui/react/menu\";\nimport { Separator as BaseSeparator } from \"@base-ui/react/separator\";\nimport { cn } from \"../../utils/cn\";\nimport { resolveVariant, type VariantMap } from \"../../utils/resolve-variant\";\nimport {\n MenuItemSpinner,\n useAsyncMenuItem,\n useMenuPending,\n withMenuPendingRoot,\n type AsyncMenuItemClick,\n} from \"./menu-pending\";\n\nexport { useMenuPending };\n\n/** Override Base UI's `string | (state) => string` className with plain string. */\ntype StyledProps<T extends ElementType> = Omit<\n ComponentProps<T>,\n \"className\" | \"color\"\n> & {\n className?: string;\n};\n\n/** Shared popup + item styles, reused by `ContextMenu` and `Menubar` menus. */\n// Floating layer: keeps its elevation shadow, sits on the primary surface\n// with the site's hairline (ring-pho-secondary, #eee/#202020) as its edge.\n// `base` radius + p-1.5 inset — the highlight floats 6px clear of the edge.\nexport const MENU_POPUP =\n \"bg-pho-primary text-pho-primary shadow-pho-lg ring-pho-secondary origin-[var(--transform-origin)] min-w-44 rounded-base p-1.5 ring-1 ring-inset outline-none transition-[transform,opacity] duration-150 ease-out data-[starting-style]:scale-95 data-[starting-style]:opacity-0 data-[ending-style]:scale-95 data-[ending-style]:opacity-0\";\n// Rest at secondary; highlighted lifts to primary with the quiet ghost wash.\n// Icons use `text-current` so they follow the row ink (including destructive).\n// While loading, the spinner is first; hide the rest of the leading icons.\n// Concentric correction (see /scale): row radius = popup radius − popup\n// padding (base − 6px). py-2 + 20px line-height lands the 36px row height.\nexport const MENU_ITEM =\n \"relative flex cursor-pointer items-center gap-2 rounded-[calc(var(--radius-base)-6px)] py-2 pr-2.5 pl-2.5 text-base text-pho-secondary outline-none select-none data-[highlighted]:bg-pho-ghost-hover data-[highlighted]:text-pho-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-loading:data-[disabled]:opacity-100 data-loading:[&>svg:not([data-menu-spinner])]:hidden [&>svg]:size-4 [&>svg]:text-current\";\n\n/** Semantic ink for `Menu.Item` / `LinkItem` / `ContextMenu.Item`. */\nexport const MENU_ITEM_COLORS = {\n default: { classes: \"\", description: \"Secondary → primary on highlight\" },\n destructive: {\n classes:\n \"text-pho-error data-[highlighted]:bg-pho-error-primary data-[highlighted]:text-pho-error\",\n description: \"Error ink + error wash on highlight\",\n },\n} satisfies VariantMap;\n\nexport type MenuItemColor = keyof typeof MENU_ITEM_COLORS;\n\ntype ItemColorProps = {\n /** Semantic ink. @default \"default\" */\n color?: MenuItemColor;\n};\n\nexport type MenuItemProps = Omit<StyledProps<typeof BaseMenu.Item>, \"onClick\"> &\n ItemColorProps & {\n /**\n * Action handler. Return a promise to keep the menu open while it runs:\n * the item shows a spinner, dismissal is locked, and the menu closes when\n * the promise resolves (if `closeOnClick`). On rejection the menu stays\n * open — surface errors inside the handler.\n */\n onClick?: AsyncMenuItemClick;\n /**\n * Declarative loading state (same visual as an in-flight promise).\n * @default false\n */\n loading?: boolean;\n };\n\nconst Root = withMenuPendingRoot(BaseMenu.Root);\nconst Trigger = BaseMenu.Trigger;\nconst Portal = BaseMenu.Portal;\nconst Group = BaseMenu.Group;\nconst RadioGroup = BaseMenu.RadioGroup;\n// Don't wrap SubmenuRoot — nested pending context would shadow the parent\n// Root and fail to lock dismissal for async items inside a submenu.\nconst SubmenuRoot = BaseMenu.SubmenuRoot;\n\nfunction Positioner({\n className,\n sideOffset = 6,\n ...props\n}: StyledProps<typeof BaseMenu.Positioner>) {\n return (\n <BaseMenu.Positioner\n sideOffset={sideOffset}\n className={cn(\"z-50\", className)}\n {...props}\n />\n );\n}\n\nfunction Popup({ className, ...props }: StyledProps<typeof BaseMenu.Popup>) {\n return <BaseMenu.Popup className={cn(MENU_POPUP, className)} {...props} />;\n}\n\nfunction Item({\n className,\n color = \"default\",\n onClick,\n closeOnClick = true,\n loading = false,\n disabled = false,\n children,\n ...props\n}: MenuItemProps) {\n const { inert, blocked, handleClick } = useAsyncMenuItem({\n onClick,\n closeOnClick,\n loading,\n disabled,\n });\n\n return (\n <BaseMenu.Item\n className={cn(\n MENU_ITEM,\n resolveVariant(MENU_ITEM_COLORS, color),\n className,\n )}\n closeOnClick={closeOnClick}\n disabled={blocked}\n data-loading={inert || undefined}\n aria-busy={inert || undefined}\n onClick={handleClick as (event: ReactMouseEvent<HTMLElement>) => void}\n {...props}\n >\n {inert ? (\n <>\n <MenuItemSpinner />\n <output className=\"sr-only\" aria-live=\"polite\" />\n </>\n ) : null}\n {children}\n </BaseMenu.Item>\n );\n}\n\nfunction LinkItem({\n className,\n color = \"default\",\n ...props\n}: StyledProps<typeof BaseMenu.LinkItem> & ItemColorProps) {\n return (\n <BaseMenu.LinkItem\n className={cn(\n MENU_ITEM,\n resolveVariant(MENU_ITEM_COLORS, color),\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction GroupLabel({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.GroupLabel>) {\n return (\n <BaseMenu.GroupLabel\n className={cn(\n \"text-pho-secondary px-2.5 py-1.5 text-xs font-medium\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Separator({ className, ...props }: StyledProps<typeof BaseSeparator>) {\n return (\n <BaseSeparator\n className={cn(\n \"-mx-1.5 my-1.5 h-px bg-[var(--border-color-pho-secondary)]\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SubmenuTrigger({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.SubmenuTrigger>) {\n return (\n <BaseMenu.SubmenuTrigger\n className={cn(\n MENU_ITEM,\n \"data-[popup-open]:bg-pho-ghost-hover\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction CheckboxItem({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.CheckboxItem>) {\n return (\n <BaseMenu.CheckboxItem\n className={cn(MENU_ITEM, \"pl-8\", className)}\n {...props}\n />\n );\n}\n\nfunction RadioItem({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.RadioItem>) {\n return (\n <BaseMenu.RadioItem\n className={cn(MENU_ITEM, \"pl-8\", className)}\n {...props}\n />\n );\n}\n\nconst CHECK_INDICATOR =\n \"text-pho-brand absolute left-2.5 flex items-center [&>svg]:size-4\";\n\nfunction CheckboxItemIndicator({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.CheckboxItemIndicator>) {\n return (\n <BaseMenu.CheckboxItemIndicator\n className={cn(CHECK_INDICATOR, className)}\n {...props}\n />\n );\n}\n\nfunction RadioItemIndicator({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.RadioItemIndicator>) {\n return (\n <BaseMenu.RadioItemIndicator\n className={cn(CHECK_INDICATOR, className)}\n {...props}\n />\n );\n}\n\n/**\n * Dropdown menu built on Base UI `Menu` — a list of actions opened from a\n * trigger. Supports grouped items, separators, submenus\n * (`SubmenuRoot` + `SubmenuTrigger`), and stateful `CheckboxItem` /\n * `RadioItem`s. `Menu.Item` has native promise support (spinner + locked\n * dismissal while pending). For a right-click menu use `ContextMenu`.\n */\nexport const Menu = {\n Root,\n Trigger,\n Portal,\n Positioner,\n Popup,\n Item,\n LinkItem,\n Group,\n GroupLabel,\n Separator,\n SubmenuRoot,\n SubmenuTrigger,\n CheckboxItem,\n CheckboxItemIndicator,\n RadioGroup,\n RadioItem,\n RadioItemIndicator,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"menu-BSXds3Rt.js","names":[],"sources":["../../src/components/menu/menu-pending.tsx","../../src/components/menu/menu.tsx"],"sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ComponentType,\n type MouseEvent as ReactMouseEvent,\n type ReactNode,\n type RefObject,\n} from \"react\";\nimport { Spinner } from \"../spinner/spinner\";\n\n/** Imperative actions exposed by Base UI menu `actionsRef`. */\ninterface RootActions {\n unmount: () => void;\n close: () => void;\n}\n\ninterface MenuPendingContextValue {\n /** True while a menu item promise is in flight. */\n pending: boolean;\n beginPending: () => void;\n endPending: () => void;\n /** Close the menu imperatively (used by async items on resolve). */\n close: () => void;\n}\n\nconst MenuPendingContext = createContext<MenuPendingContextValue | null>(null);\n\n/**\n * Whether the surrounding menu has an async item promise in flight —\n * use it to disable sibling items while work is pending.\n * Returns `false` outside a `Menu.Root` / `ContextMenu.Root`.\n */\nexport function useMenuPending(): boolean {\n return useContext(MenuPendingContext)?.pending ?? false;\n}\n\nexport function isThenable(value: unknown): value is Promise<unknown> {\n return (\n value != null && typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\ntype PreventableMouseEvent = ReactMouseEvent<HTMLElement> & {\n preventBaseUIHandler?: () => void;\n};\n\ntype RootWithPendingProps = {\n onOpenChange?: (open: boolean, eventDetails: { cancel: () => void }) => void;\n actionsRef?: RefObject<RootActions | null>;\n children?: ReactNode;\n};\n\n/**\n * Wraps a Base UI menu root so async `Menu.Item` clicks can lock dismissal\n * (outside press, Escape) until the promise settles.\n */\nexport function withMenuPendingRoot<P extends RootWithPendingProps>(\n BaseRoot: ComponentType<P>,\n): ComponentType<P> {\n function PendingRoot({ onOpenChange, actionsRef, ...props }: P) {\n const [pending, setPending] = useState(false);\n // Guard reads a ref so a resolve → close() in the same tick isn't blocked\n // by a not-yet-flushed state update.\n const pendingRef = useRef(false);\n const internalActionsRef = useRef<RootActions | null>(null);\n\n useEffect(() => {\n if (!actionsRef) return;\n // Proxy so callers always hit the live Base UI actions, even if the\n // popup remounts after this effect runs.\n actionsRef.current = {\n unmount: () => internalActionsRef.current?.unmount(),\n close: () => internalActionsRef.current?.close(),\n };\n return () => {\n actionsRef.current = null;\n };\n }, [actionsRef]);\n\n const handleOpenChange = useCallback(\n (open: boolean, eventDetails: { cancel: () => void }) => {\n if (!open && pendingRef.current) {\n eventDetails.cancel();\n return;\n }\n onOpenChange?.(open, eventDetails);\n },\n [onOpenChange],\n );\n\n const contextValue = useMemo<MenuPendingContextValue>(\n () => ({\n pending,\n beginPending: () => {\n pendingRef.current = true;\n setPending(true);\n },\n endPending: () => {\n pendingRef.current = false;\n setPending(false);\n },\n close: () => internalActionsRef.current?.close(),\n }),\n [pending],\n );\n\n return (\n <MenuPendingContext.Provider value={contextValue}>\n <BaseRoot\n {...(props as P)}\n actionsRef={internalActionsRef}\n onOpenChange={handleOpenChange}\n />\n </MenuPendingContext.Provider>\n );\n }\n\n return PendingRoot as ComponentType<P>;\n}\n\nexport type AsyncMenuItemClick = (\n event: ReactMouseEvent<HTMLElement>,\n) => void | Promise<unknown>;\n\n/**\n * Shared click handler for `Menu.Item` / `ContextMenu.Item` promise support.\n * Returns whether the item should show its loading state.\n */\nexport function useAsyncMenuItem({\n onClick,\n closeOnClick = true,\n loading = false,\n disabled = false,\n}: {\n onClick?: AsyncMenuItemClick;\n closeOnClick?: boolean;\n loading?: boolean;\n disabled?: boolean;\n}) {\n const pendingContext = useContext(MenuPendingContext);\n const menuPending = pendingContext?.pending ?? false;\n const [busy, setBusy] = useState(false);\n const mountedRef = useRef(true);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const inert = busy || loading;\n // Disable siblings while any item promise is in flight — not the busy item itself.\n const blocked = disabled || (menuPending && !busy);\n\n function handleClick(event: ReactMouseEvent<HTMLElement>) {\n if (inert || (menuPending && !busy)) {\n (event as PreventableMouseEvent).preventBaseUIHandler?.();\n return;\n }\n const result = onClick?.(event);\n if (!isThenable(result)) {\n return;\n }\n // Stop Base UI's closeOnClick for this press; close on resolve instead.\n (event as PreventableMouseEvent).preventBaseUIHandler?.();\n setBusy(true);\n pendingContext?.beginPending();\n result.then(\n () => {\n pendingContext?.endPending();\n if (mountedRef.current) setBusy(false);\n if (closeOnClick) pendingContext?.close();\n },\n (error: unknown) => {\n pendingContext?.endPending();\n if (mountedRef.current) setBusy(false);\n if (\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV !== \"production\"\n ) {\n console.error(\"Menu.Item: async onClick rejected —\", error);\n }\n },\n );\n }\n\n return { inert, blocked, handleClick };\n}\n\n/** Menu-row spinner — keeps `data-menu-spinner` for the hide-siblings CSS. */\nexport function MenuItemSpinner() {\n return <Spinner data-menu-spinner=\"\" size=\"md\" />;\n}\n","import {\n type ComponentProps,\n type ElementType,\n type MouseEvent as ReactMouseEvent,\n} from \"react\";\nimport { Menu as BaseMenu } from \"@base-ui/react/menu\";\nimport { Separator as BaseSeparator } from \"@base-ui/react/separator\";\nimport { cn } from \"../../utils/cn\";\nimport { resolveVariant, type VariantMap } from \"../../utils/resolve-variant\";\nimport {\n MenuItemSpinner,\n useAsyncMenuItem,\n useMenuPending,\n withMenuPendingRoot,\n type AsyncMenuItemClick,\n} from \"./menu-pending\";\n\nexport { useMenuPending };\n\n/** Override Base UI's `string | (state) => string` className with plain string. */\ntype StyledProps<T extends ElementType> = Omit<\n ComponentProps<T>,\n \"className\" | \"color\"\n> & {\n className?: string;\n};\n\n/** Shared popup + item styles, reused by `ContextMenu` and `Menubar` menus. */\n// Floating layer: keeps its elevation shadow, sits on the primary surface\n// with the site's hairline (ring-pho-secondary, #eee/#202020) as its edge.\n// `base` radius + p-1.5 inset — the highlight floats 6px clear of the edge.\nexport const MENU_POPUP =\n \"bg-pho-primary text-pho-primary shadow-pho-lg ring-pho-secondary origin-[var(--transform-origin)] min-w-44 rounded-base p-1.5 ring-1 ring-inset outline-none transition-[transform,opacity] duration-150 ease-out data-[starting-style]:scale-95 data-[starting-style]:opacity-0 data-[ending-style]:scale-95 data-[ending-style]:opacity-0\";\n// Rest at secondary; highlighted lifts to primary with the quiet ghost wash.\n// Icons use `text-current` so they follow the row ink (including destructive).\n// While loading, the spinner is first; hide the rest of the leading icons.\n// Concentric correction (see /scale): row radius = popup radius − popup\n// padding (base − 6px). py-2 + 20px line-height lands the 36px row height.\nexport const MENU_ITEM =\n \"relative flex cursor-pointer items-center gap-2 rounded-[calc(var(--radius-base)-6px)] py-2 pr-2.5 pl-2.5 text-base text-pho-secondary outline-none select-none data-[highlighted]:bg-pho-ghost-hover data-[highlighted]:text-pho-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-loading:data-[disabled]:opacity-100 data-loading:[&>svg:not([data-menu-spinner])]:hidden [&>svg]:size-4 [&>svg]:text-current\";\n\n/** Semantic ink for `Menu.Item` / `LinkItem` / `ContextMenu.Item`. */\nexport const MENU_ITEM_COLORS = {\n default: { classes: \"\", description: \"Secondary → primary on highlight\" },\n destructive: {\n classes:\n \"text-pho-error data-[highlighted]:bg-pho-error-primary data-[highlighted]:text-pho-error\",\n description: \"Error ink + error wash on highlight\",\n },\n} satisfies VariantMap;\n\nexport type MenuItemColor = keyof typeof MENU_ITEM_COLORS;\n\ntype ItemColorProps = {\n /** Semantic ink. @default \"default\" */\n color?: MenuItemColor;\n};\n\nexport type MenuItemProps = Omit<StyledProps<typeof BaseMenu.Item>, \"onClick\"> &\n ItemColorProps & {\n /**\n * Action handler. Return a promise to keep the menu open while it runs:\n * the item shows a spinner, dismissal is locked, and the menu closes when\n * the promise resolves (if `closeOnClick`). On rejection the menu stays\n * open — surface errors inside the handler.\n */\n onClick?: AsyncMenuItemClick;\n /**\n * Declarative loading state (same visual as an in-flight promise).\n * @default false\n */\n loading?: boolean;\n };\n\nconst Root = withMenuPendingRoot(BaseMenu.Root);\nconst Trigger = BaseMenu.Trigger;\nconst Portal = BaseMenu.Portal;\nconst Group = BaseMenu.Group;\nconst RadioGroup = BaseMenu.RadioGroup;\n// Don't wrap SubmenuRoot — nested pending context would shadow the parent\n// Root and fail to lock dismissal for async items inside a submenu.\nconst SubmenuRoot = BaseMenu.SubmenuRoot;\n\nfunction Positioner({\n className,\n sideOffset = 6,\n ...props\n}: StyledProps<typeof BaseMenu.Positioner>) {\n return (\n <BaseMenu.Positioner\n sideOffset={sideOffset}\n className={cn(\"z-50\", className)}\n {...props}\n />\n );\n}\n\nfunction Popup({ className, ...props }: StyledProps<typeof BaseMenu.Popup>) {\n return <BaseMenu.Popup className={cn(MENU_POPUP, className)} {...props} />;\n}\n\nfunction Item({\n className,\n color = \"default\",\n onClick,\n closeOnClick = true,\n loading = false,\n disabled = false,\n children,\n ...props\n}: MenuItemProps) {\n const { inert, blocked, handleClick } = useAsyncMenuItem({\n onClick,\n closeOnClick,\n loading,\n disabled,\n });\n\n return (\n <BaseMenu.Item\n className={cn(\n MENU_ITEM,\n resolveVariant(MENU_ITEM_COLORS, color),\n className,\n )}\n closeOnClick={closeOnClick}\n disabled={blocked}\n data-loading={inert || undefined}\n aria-busy={inert || undefined}\n onClick={handleClick as (event: ReactMouseEvent<HTMLElement>) => void}\n {...props}\n >\n {inert ? (\n <>\n <MenuItemSpinner />\n <output className=\"sr-only\" aria-live=\"polite\" />\n </>\n ) : null}\n {children}\n </BaseMenu.Item>\n );\n}\n\nfunction LinkItem({\n className,\n color = \"default\",\n ...props\n}: StyledProps<typeof BaseMenu.LinkItem> & ItemColorProps) {\n return (\n <BaseMenu.LinkItem\n className={cn(\n MENU_ITEM,\n resolveVariant(MENU_ITEM_COLORS, color),\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction GroupLabel({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.GroupLabel>) {\n return (\n <BaseMenu.GroupLabel\n className={cn(\n \"text-pho-secondary px-2.5 py-1.5 text-xs font-medium\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Separator({ className, ...props }: StyledProps<typeof BaseSeparator>) {\n return (\n <BaseSeparator\n className={cn(\n \"-mx-1.5 my-1.5 h-px bg-[var(--border-color-pho-secondary)]\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SubmenuTrigger({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.SubmenuTrigger>) {\n return (\n <BaseMenu.SubmenuTrigger\n className={cn(\n MENU_ITEM,\n \"data-[popup-open]:bg-pho-ghost-hover\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction CheckboxItem({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.CheckboxItem>) {\n return (\n <BaseMenu.CheckboxItem\n className={cn(MENU_ITEM, \"pl-8\", className)}\n {...props}\n />\n );\n}\n\nfunction RadioItem({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.RadioItem>) {\n return (\n <BaseMenu.RadioItem\n className={cn(MENU_ITEM, \"pl-8\", className)}\n {...props}\n />\n );\n}\n\nconst CHECK_INDICATOR =\n \"text-pho-brand absolute left-2.5 flex items-center [&>svg]:size-4\";\n\nfunction CheckboxItemIndicator({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.CheckboxItemIndicator>) {\n return (\n <BaseMenu.CheckboxItemIndicator\n className={cn(CHECK_INDICATOR, className)}\n {...props}\n />\n );\n}\n\nfunction RadioItemIndicator({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.RadioItemIndicator>) {\n return (\n <BaseMenu.RadioItemIndicator\n className={cn(CHECK_INDICATOR, className)}\n {...props}\n />\n );\n}\n\n/**\n * Dropdown menu built on Base UI `Menu` — a list of actions opened from a\n * trigger. Supports grouped items, separators, submenus\n * (`SubmenuRoot` + `SubmenuTrigger`), and stateful `CheckboxItem` /\n * `RadioItem`s. `Menu.Item` has native promise support (spinner + locked\n * dismissal while pending). For a right-click menu use `ContextMenu`.\n */\nexport const Menu = {\n Root,\n Trigger,\n Portal,\n Positioner,\n Popup,\n Item,\n LinkItem,\n Group,\n GroupLabel,\n Separator,\n SubmenuRoot,\n SubmenuTrigger,\n CheckboxItem,\n CheckboxItemIndicator,\n RadioGroup,\n RadioItem,\n RadioItemIndicator,\n};\n"],"mappings":"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"}
|