@code-sherpas/pharos-react 0.17.0 → 0.19.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/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs","../node_modules/.pnpm/class-variance-authority@0.7.1/node_modules/class-variance-authority/dist/index.mjs","../src/lib/cn.ts","../src/components/Button.module.css","../src/components/Button.tsx","../src/components/Spinner.module.css","../src/components/Spinner.tsx","../src/components/IconButton.module.css","../src/components/IconButton.tsx","../src/components/Badge.module.css","../src/components/Badge.tsx","../src/components/Input.module.css","../src/components/Input.tsx","../src/components/Textarea.module.css","../src/components/Textarea.tsx","../src/components/Separator.module.css","../src/components/Separator.tsx","../src/components/Card.module.css","../src/components/Card.tsx","../src/components/Avatar.module.css","../src/components/Avatar.tsx","../src/components/DropdownMenu.module.css","../src/components/DropdownMenu.tsx","../src/components/Popover.module.css","../src/components/Popover.tsx","../src/components/Select.module.css","../src/components/Select.tsx","../src/components/Combobox.module.css","../src/components/Combobox.tsx","../src/components/Sheet.module.css","../src/components/Sheet.tsx","../src/components/Dialog.module.css","../src/components/Dialog.tsx","../src/components/Checkbox.module.css","../src/components/Checkbox.tsx"],"sourcesContent":["function r(e){var t,f,n=\"\";if(\"string\"==typeof e||\"number\"==typeof e)n+=e;else if(\"object\"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=\" \"),n+=f)}else for(f in e)e[f]&&(n&&(n+=\" \"),n+=f);return n}export function clsx(){for(var e,t,f=0,n=\"\",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=\" \"),n+=t);return n}export default clsx;","/**\n * Copyright 2022 Joe Bell. All rights reserved.\n *\n * This file is licensed to you under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with the\n * License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */ import { clsx } from \"clsx\";\nconst falsyToString = (value)=>typeof value === \"boolean\" ? `${value}` : value === 0 ? \"0\" : value;\nexport const cx = clsx;\nexport const cva = (base, config)=>(props)=>{\n var _config_compoundVariants;\n if ((config === null || config === void 0 ? void 0 : config.variants) == null) return cx(base, props === null || props === void 0 ? void 0 : props.class, props === null || props === void 0 ? void 0 : props.className);\n const { variants, defaultVariants } = config;\n const getVariantClassNames = Object.keys(variants).map((variant)=>{\n const variantProp = props === null || props === void 0 ? void 0 : props[variant];\n const defaultVariantProp = defaultVariants === null || defaultVariants === void 0 ? void 0 : defaultVariants[variant];\n if (variantProp === null) return null;\n const variantKey = falsyToString(variantProp) || falsyToString(defaultVariantProp);\n return variants[variant][variantKey];\n });\n const propsWithoutUndefined = props && Object.entries(props).reduce((acc, param)=>{\n let [key, value] = param;\n if (value === undefined) {\n return acc;\n }\n acc[key] = value;\n return acc;\n }, {});\n const getCompoundVariantClassNames = config === null || config === void 0 ? void 0 : (_config_compoundVariants = config.compoundVariants) === null || _config_compoundVariants === void 0 ? void 0 : _config_compoundVariants.reduce((acc, param)=>{\n let { class: cvClass, className: cvClassName, ...compoundVariantOptions } = param;\n return Object.entries(compoundVariantOptions).every((param)=>{\n let [key, value] = param;\n return Array.isArray(value) ? value.includes({\n ...defaultVariants,\n ...propsWithoutUndefined\n }[key]) : ({\n ...defaultVariants,\n ...propsWithoutUndefined\n })[key] === value;\n }) ? [\n ...acc,\n cvClass,\n cvClassName\n ] : acc;\n }, []);\n return cx(base, getVariantClassNames, getCompoundVariantClassNames, props === null || props === void 0 ? void 0 : props.class, props === null || props === void 0 ? void 0 : props.className);\n };\n\n","import { clsx, type ClassValue } from 'clsx';\n\n/**\n * Merge a list of class-name inputs into a single string.\n *\n * `clsx`-only by design (Decision D9): pharos-react no longer ships Tailwind\n * classes, so the previous `twMerge` step was wrapping each call with a\n * Tailwind-conflict resolver that nothing in the bundle benefited from.\n *\n * Consumers that build with Tailwind and want the conflict-resolution\n * behavior should keep using their own `tailwind-merge` helper alongside\n * Pharos components — `<Button className={twMerge(myClasses)} />` works\n * because Pharos-side class names are CSS Modules hashes that no Tailwind\n * resolver will ever try to deduplicate.\n */\nexport function cn(...inputs: ClassValue[]): string {\n return clsx(inputs);\n}\n","/* Button.module.css\n *\n * Per-component CSS Modules definition (Decision D9). The build emits hashed\n * class names (e.g. `Button_button__a3f2`) into dist/styles.css; the runtime\n * JS references those names through the `styles` object imported by Button.tsx.\n *\n * Every visual value comes from a `--pharos-*` token. No hex, no raw px/rem\n * for design dimensions, no Tailwind. Hover / active / focus states are\n * authored with native CSS pseudo-classes — no preprocessor, no utility-first\n * shorthand, no framework dependency at runtime.\n *\n * Consumers do not need Tailwind (any version) or any other CSS framework to\n * use this component. They only need to load the published `dist/styles.css`\n * once and the consumer's app must define `--pharos-*` (achieved by importing\n * `@code-sherpas/pharos-tokens/css`, which is a peer dependency).\n */\n\n.button {\n /* Strip native button rendering. The component is fully custom-styled,\n * so platform-specific drawing (rounded corners, shadows, focus rings\n * on macOS, push-button gradient on iOS) only adds variance without\n * adding value. `appearance: none` keeps every consumer rendering the\n * exact same pixel set. The `-webkit-appearance` line is a no-op on\n * modern Chromium but stays for older WebKit. */\n appearance: none;\n -webkit-appearance: none;\n\n /* === Box model ===\n * Explicit so the rendered control matches the height set in the size\n * rules even when the consumer's app does not ship a CSS reset. With\n * the consumer-side preflight gone, `border-box`, `margin: 0` and the\n * zero block-padding neutralise the user-agent's `padding: 1px 6px`\n * (Chrome) or `padding: 0 8px` (Firefox) on `<button>`. */\n box-sizing: border-box;\n margin: 0;\n padding-top: 0;\n padding-bottom: 0;\n border: 0 solid transparent;\n border-radius: var(--pharos-radius-full);\n\n /* === Layout === */\n display: inline-flex;\n align-items: center;\n justify-content: center;\n white-space: nowrap;\n\n /* === Optical text centering ===\n * `text-box-trim` strips the leading above the cap-line and below the\n * alphabetic baseline so `align-items: center` centres the actual\n * cap-block instead of the line-box (which includes per-font half-\n * leading that biases the visual centre upward or downward depending\n * on the font's vertical metrics). Without this, fonts with a high\n * baseline like Outfit (the D8 sans) render visibly above the\n * geometric centre even though `align-items: center` is correctly\n * applied — matching what Chromatic flagged on the migration PR.\n * The `text-box-edge` longhand picks the cap-line at the start and\n * the alphabetic baseline at the end so descenders (\"y\", \"g\", ...)\n * still hang below the cap-block as the font intended; only the\n * extra leading is trimmed.\n *\n * Browser support: Chromium 133+ / Safari 18.2+ ship the property,\n * Firefox has it behind a flag at the time of writing. In engines\n * without support the declaration is ignored and the text falls\n * back to standard line-box centring — which is the same behaviour\n * the bundle had before this rule, so the fallback is safe. */\n text-box-trim: trim-both;\n text-box-edge: cap alphabetic;\n\n /* === Typography ===\n * Each font longhand is set explicitly. The shorthand `font: inherit`\n * was tempting but the CSS spec says the `font` shorthand resets a\n * dozen related properties (`font-feature-settings`,\n * `font-variation-settings`, `font-kerning`, `font-optical-sizing`,\n * `font-size-adjust`, etc.) to their *initial* values rather than\n * inheriting them. That subtle reset shifted the rendered baseline\n * by a fraction of a pixel against the consumer's surrounding text,\n * which Chromatic flagged as a diff vs 0.2.x. The size variants\n * below override `font-size` and `line-height` per size. */\n font-family: var(--pharos-font-family-sans);\n font-style: normal;\n font-weight: var(--pharos-font-weight-medium);\n font-stretch: normal;\n font-variant: normal;\n font-kerning: auto;\n font-optical-sizing: auto;\n font-size-adjust: none;\n font-feature-settings: normal;\n font-variation-settings: normal;\n letter-spacing: normal;\n text-decoration: none;\n text-transform: none;\n /* Default size (overridden per size variant) — keeps a sensible\n * fallback if a Button is somehow rendered without a size class. */\n font-size: var(--pharos-font-size-sm);\n line-height: 1.5;\n\n /* === Interactivity === */\n user-select: none;\n cursor: pointer;\n background-color: transparent;\n color: inherit;\n\n /* === Transitions === */\n transition-property: color, background-color, border-color, box-shadow;\n transition-duration: var(--pharos-duration-normal);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.button:focus-visible {\n outline: none;\n /* Two-stop box-shadow mimics the Tailwind `ring-offset-2 ring-2` pattern:\n * an inner halo in the surface color separates the ring from the button\n * fill, and the outer color is the per-intent focus accent. */\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--button-focus-ring, var(--pharos-color-primary-600));\n}\n\n.button:disabled {\n pointer-events: none;\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n/* === Intents === */\n\n.primary {\n background-color: var(--pharos-color-neutral-900);\n color: var(--pharos-color-base-white);\n --button-focus-ring: var(--pharos-color-primary-600);\n}\n\n.primary:hover {\n background-color: var(--pharos-color-neutral-700);\n}\n\n.primary:active {\n background-color: var(--pharos-color-neutral-950);\n}\n\n.secondary {\n background-color: var(--pharos-color-base-white);\n color: var(--pharos-color-neutral-900);\n /* `neutral-500` clears WCAG 1.4.11 (Non-text Contrast, 3:1 against the\n * surrounding surface). See Input.module.css for the full rationale and\n * the cross-DS comparison; Button secondary, Input and Badge outline\n * share the same border tone so the system has one accessible boundary\n * intensity across primitives. */\n border: 1px solid var(--pharos-color-neutral-500);\n --button-focus-ring: var(--pharos-color-primary-600);\n}\n\n.secondary:hover {\n background-color: var(--pharos-color-neutral-50);\n}\n\n.secondary:active {\n background-color: var(--pharos-color-neutral-100);\n}\n\n.ghost {\n background-color: transparent;\n color: var(--pharos-color-neutral-900);\n --button-focus-ring: var(--pharos-color-primary-600);\n}\n\n.ghost:hover {\n background-color: var(--pharos-color-neutral-100);\n}\n\n.ghost:active {\n background-color: var(--pharos-color-neutral-200);\n}\n\n.destructive {\n background-color: var(--pharos-color-semantic-error-fg);\n color: var(--pharos-color-semantic-error-on);\n --button-focus-ring: var(--pharos-color-semantic-error-fg);\n}\n\n.destructive:hover {\n background-color: var(--pharos-color-semantic-error-fg-hover);\n}\n\n.destructive:active {\n background-color: var(--pharos-color-semantic-error-fg-active);\n}\n\n/* === Sizes ===\n *\n * Heights and paddings are derived from `--pharos-spacing-*` so a runtime\n * override of the spacing scale rescales the whole control proportionally.\n * Font sizes use `--pharos-font-size-*` tokens; the line-height ratios match\n * the Tailwind v4 defaults that shipped in 0.2.x so visual fidelity is\n * preserved across the migration. */\n\n.sizeSm {\n height: var(--pharos-spacing-8);\n gap: var(--pharos-spacing-1-5);\n padding: 0 var(--pharos-spacing-3);\n font-size: var(--pharos-font-size-sm);\n line-height: calc(1.25 / 0.875);\n}\n\n.sizeMd {\n height: var(--pharos-spacing-10);\n gap: var(--pharos-spacing-2);\n padding: 0 var(--pharos-spacing-4);\n font-size: var(--pharos-font-size-sm);\n line-height: calc(1.25 / 0.875);\n}\n\n.sizeLg {\n height: var(--pharos-spacing-12);\n gap: var(--pharos-spacing-2);\n padding: 0 var(--pharos-spacing-6);\n font-size: var(--pharos-font-size-base);\n line-height: 1.5;\n}\n","import { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Button.module.css';\n\n/**\n * Maps the two public variant axes (`intent`, `size`) to the per-class names\n * inside `Button.module.css`. CVA stays as the typed-variants mechanism\n * (Decision D9 keeps the authoring DX) but each value points at a hashed\n * CSS Modules class instead of a Tailwind utility string. The base class\n * carries layout, typography and the focus-visible ring — every variant\n * stacks on top of it.\n */\nconst buttonVariants = cva(styles.button, {\n variants: {\n intent: {\n // `primary` is the most emphasized CTA. It renders in neutral-900,\n // matching Alexandria's `NewButton variant=\"filled\" tone=\"default\"`\n // and the Linear/Vercel/Notion convention of \"brand color as accent,\n // dark neutral as primary action\". The blue `primary-*` palette is\n // reserved for brand accents (focus rings, highlights, badges) —\n // not for the default filled button.\n primary: styles.primary,\n secondary: styles.secondary,\n ghost: styles.ghost,\n destructive: styles.destructive,\n },\n size: {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n },\n },\n defaultVariants: {\n intent: 'primary',\n size: 'md',\n },\n});\n\ntype ButtonVariantProps = VariantProps<typeof buttonVariants>;\n\n/**\n * Props accepted by `<Button>`. Extends Base UI's `useRender.ComponentProps<'button'>`,\n * which means the component accepts every native `<button>` attribute plus the\n * `render` prop for composition:\n *\n * ```tsx\n * // Render as a router link (inherits all Button styles and behavior):\n * <Button render={<Link to=\"/dashboard\" />}>Dashboard</Button>\n * // Or as a function receiving props + state:\n * <Button render={(props) => <a {...props} href=\"/x\" />}>External</Button>\n * ```\n */\nexport interface ButtonProps extends useRender.ComponentProps<'button'>, ButtonVariantProps {}\n\nexport function Button({\n render,\n className,\n intent,\n size,\n type = 'button',\n ref,\n ...rest\n}: ButtonProps) {\n const resolvedIntent = intent ?? 'primary';\n const resolvedSize = size ?? 'md';\n return useRender({\n render: render ?? <button />,\n ref,\n defaultTagName: 'button',\n props: {\n type,\n // `data-pharos-intent` / `data-pharos-size` are stable hooks for\n // selectors and tests. They mirror the CSS Modules class but stay\n // semantic — consumer queries do not need to know the hashed class\n // name and tests do not need to import the module to assert variant.\n 'data-pharos-intent': resolvedIntent,\n 'data-pharos-size': resolvedSize,\n className: cn(buttonVariants({ intent: resolvedIntent, size: resolvedSize }), className),\n ...rest,\n },\n });\n}\n\nButton.displayName = 'Button';\n\nexport { buttonVariants };\n","/* Spinner.module.css\n *\n * Per-component CSS Modules definition (Decision D9). The build emits hashed\n * class names (`Spinner_spinner__xxxx`) into `dist/styles.css`; the runtime\n * JS references them through the `styles` object imported by Spinner.tsx.\n *\n * The Spinner is intentionally minimal: a `<span role=\"status\">` that\n * inherits its colour from the parent via `currentColor`. There is no\n * `variant` / `tone` axis — the consumer chooses the colour by setting\n * `color` on the parent (or by overriding via `className`). This matches\n * the shadcn / Radix Themes / Mantine pattern.\n */\n\n@keyframes pharos-spinner-spin {\n /* Single full rotation. Authored as a uniquely-named keyframe (prefixed\n * `pharos-spinner-`) so it cannot collide with consumer keyframes. The\n * `from`/`to` form is two-line and clear; using `100%` would compile to\n * the same output. */\n from {\n transform: rotate(0deg);\n }\n\n to {\n transform: rotate(360deg);\n }\n}\n\n.spinner {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n /* `currentColor` makes the Spinner inherit the parent's text colour by\n * default, so `<Button intent=\"primary\"><Spinner /></Button>` works out\n * of the box (the Button's white text colour cascades into the SVG's\n * `stroke=\"currentColor\"`). */\n color: currentColor;\n /* Vertical alignment with surrounding inline content (e.g. text inside a\n * Button label). Without this, the spinner sits a couple of pixels low\n * because the SVG box's baseline is its bottom edge. */\n vertical-align: middle;\n /* The animation lives on the icon, not the wrapper, so a consumer that\n * targets the wrapper for layout (margin, gap-as-flex-child) does not\n * accidentally inherit the spin transform. */\n}\n\n.icon {\n width: 100%;\n height: 100%;\n animation: pharos-spinner-spin var(--pharos-duration-slow, 1s) linear infinite;\n}\n\n/* Respect the user's reduced-motion preference. The animation slows down\n * dramatically rather than disappearing; some assistive interactions\n * still need a \"this is in progress\" visual cue, so we keep movement but\n * make it gentler. Same compromise Radix Themes uses. */\n@media (prefers-reduced-motion: reduce) {\n .icon {\n animation-duration: 4s;\n }\n}\n\n/* === Sizes ===\n *\n * Heights align with the form-control grid so a Spinner sits flush inside\n * a Button / Input / Textarea of the same size. The visual diameter of the\n * SVG itself fills the wrapper (`width: 100%; height: 100%` on the icon),\n * so the wrapper size IS the spinner size. */\n\n.sizeSm {\n width: var(--pharos-spacing-4); /* 16 px */\n height: var(--pharos-spacing-4);\n}\n\n.sizeMd {\n width: var(--pharos-spacing-5); /* 20 px */\n height: var(--pharos-spacing-5);\n}\n\n.sizeLg {\n width: var(--pharos-spacing-6); /* 24 px */\n height: var(--pharos-spacing-6);\n}\n\n/* Visually-hidden utility for the screen-reader label. Identical to the\n * Tailwind `sr-only` recipe — preserved as a local utility because Pharos\n * does not ship Tailwind in `dist/` (Decision D9). */\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n","import type { ComponentProps } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Spinner.module.css';\n\n/**\n * Maps the `size` axis to per-class names inside `Spinner.module.css`. Sizes\n * align with the Button / Input / Textarea grid (16 / 20 / 24 px) so a\n * Spinner sits flush inside a control of the same size — Composition cases\n * like `<Button><Spinner /></Button>` work out of the box because the\n * Spinner inherits the Button's `currentColor`.\n *\n * No `variant` / `tone` axis on purpose. State-of-the-art DSes (Radix\n * Themes, Mantine, shadcn) keep the Spinner as a single chrome that uses\n * `currentColor`; the consumer picks the colour by setting `color` on the\n * parent (or via `className`). This keeps the atom small and removes the\n * need for a tone palette mapping.\n */\nconst spinnerVariants = cva(styles.spinner, {\n variants: {\n size: {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n },\n },\n defaultVariants: {\n size: 'md',\n },\n});\n\ntype SpinnerVariantProps = VariantProps<typeof spinnerVariants>;\n\n/**\n * Props accepted by `<Spinner>`. The component renders a `<span role=\"status\">`\n * with an inline SVG that rotates via CSS keyframes (no JS, no animation\n * libraries). Screen readers announce the `srLabel` (default `\"Loading…\"`)\n * via a visually hidden `<span>` inside the status node — the same a11y\n * pattern shadcn / Radix Themes / Material UI use.\n *\n * - `size` aligns with the control grid (Button / Input / Textarea).\n * - `srLabel` is the announced text. Pass an explicit one when the loading\n * action is specific (e.g. `\"Saving template…\"`); the default suits\n * generic \"something is loading\" cases.\n * - Native `<span>` props (e.g. `style`, `aria-*`) pass through. If the\n * consumer sets `aria-label` directly, that wins over `srLabel` for\n * assistive tech (the `srLabel` `<span>` is then redundant but harmless).\n */\nexport interface SpinnerProps extends ComponentProps<'span'>, SpinnerVariantProps {\n /**\n * Visually hidden text announced by assistive tech. Pass an explicit\n * label tied to the action (e.g. `\"Submitting form…\"`) when relevant.\n *\n * @default \"Loading…\"\n */\n srLabel?: string;\n}\n\nexport function Spinner({\n size,\n className,\n srLabel = 'Loading…',\n children,\n ref,\n ...rest\n}: SpinnerProps) {\n const resolvedSize = size ?? 'md';\n return (\n <span\n ref={ref}\n role=\"status\"\n data-pharos-size={resolvedSize}\n className={cn(spinnerVariants({ size: resolvedSize }), className)}\n {...rest}\n >\n <svg\n className={styles.icon}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2.5}\n strokeLinecap=\"round\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n {/*\n * Single arc that completes ~75 % of the circle. The remaining gap\n * gives the visual rotation a clear lead/tail under the CSS spin\n * animation. Same shape Radix Themes / Mantine use; differs from\n * shadcn's lucide-Loader2 only in being inline and dependency-free.\n */}\n <circle cx=\"12\" cy=\"12\" r=\"10\" strokeDasharray=\"50 25\" />\n </svg>\n <span className={styles.srOnly}>{srLabel}</span>\n {children}\n </span>\n );\n}\n\nSpinner.displayName = 'Spinner';\n\nexport { spinnerVariants };\n","/* IconButton.module.css\n *\n * Per-component CSS Modules definition (Decision D9). The build emits hashed\n * class names (`IconButton_button__xxxx`) into `dist/styles.css`; the runtime\n * JS references them through the `styles` object imported by IconButton.tsx.\n *\n * Square dimensions across every size — the wrapper width matches the\n * height, so the rounded-full radius produces a perfect circle (matches\n * Material 3 / MUI / Mantine ActionIcon transparent variant). Icon child\n * SVGs are sized via the `> svg` direct-child selector so a Lucide icon\n * does not need explicit `size` props on the consumer side.\n */\n\n.button {\n /* Strip native button rendering. Same rationale as Button.module.css —\n * platform-specific drawing only adds variance without value. */\n appearance: none;\n -webkit-appearance: none;\n\n /* === Box model ===\n * `padding: 0` plus equal width / height gives the square that the\n * `border-radius: var(--pharos-radius-full)` turns into a circle. Hard\n * `flex-shrink: 0` so the IconButton keeps its dimensions inside a flex\n * row (toolbar, header) where surrounding labels could otherwise force\n * it to compress. */\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid transparent;\n border-radius: var(--pharos-radius-full);\n flex-shrink: 0;\n\n /* === Layout === */\n display: inline-flex;\n align-items: center;\n justify-content: center;\n\n /* === Interactivity === */\n user-select: none;\n cursor: pointer;\n background-color: transparent;\n color: inherit;\n font-family: var(--pharos-font-family-sans);\n\n /* === Transitions === */\n transition-property: color, background-color, border-color, box-shadow;\n transition-duration: var(--pharos-duration-normal);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.button:focus-visible {\n outline: none;\n /* Two-stop box-shadow mirrors the Button focus ring so a row of mixed\n * Buttons + IconButtons share a single focus look. */\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--icon-button-focus-ring, var(--pharos-color-primary-600));\n}\n\n.button:disabled {\n pointer-events: none;\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n/* === Intents ===\n *\n * Same palette as Button (D-cardinal: state-of-the-art coherence first,\n * Alexandria adapts). `ghost` is the most common icon-button case\n * (toolbars, close buttons, action icons), but the four intents stay\n * available for the rare deliberate emphasis (primary \"send\" icon,\n * destructive \"delete\" icon, secondary bordered circle navigation). */\n\n.primary {\n background-color: var(--pharos-color-neutral-900);\n color: var(--pharos-color-base-white);\n --icon-button-focus-ring: var(--pharos-color-primary-600);\n}\n\n.primary:hover {\n background-color: var(--pharos-color-neutral-700);\n}\n\n.primary:active {\n background-color: var(--pharos-color-neutral-950);\n}\n\n.secondary {\n background-color: var(--pharos-color-base-white);\n color: var(--pharos-color-neutral-900);\n /* `neutral-500` clears WCAG 1.4.11 (Non-text Contrast, 3:1 vs surface).\n * Same border tone Button secondary / Input / Badge outline use — the\n * three-tier border hierarchy (D10) treats every interactive control\n * the same way. */\n border: 1px solid var(--pharos-color-neutral-500);\n --icon-button-focus-ring: var(--pharos-color-primary-600);\n}\n\n.secondary:hover {\n background-color: var(--pharos-color-neutral-50);\n}\n\n.secondary:active {\n background-color: var(--pharos-color-neutral-100);\n}\n\n.ghost {\n background-color: transparent;\n color: var(--pharos-color-neutral-900);\n --icon-button-focus-ring: var(--pharos-color-primary-600);\n}\n\n.ghost:hover {\n background-color: var(--pharos-color-neutral-100);\n}\n\n.ghost:active {\n background-color: var(--pharos-color-neutral-200);\n}\n\n.destructive {\n background-color: var(--pharos-color-semantic-error-fg);\n color: var(--pharos-color-semantic-error-on);\n --icon-button-focus-ring: var(--pharos-color-semantic-error-fg);\n}\n\n.destructive:hover {\n background-color: var(--pharos-color-semantic-error-fg-hover);\n}\n\n.destructive:active {\n background-color: var(--pharos-color-semantic-error-fg-active);\n}\n\n/* === Sizes ===\n *\n * Square dimensions match the Button height grid so an IconButton + Button\n * pair on the same row sit flush (32 / 40 / 48 px). Direct-child `> svg`\n * sizing forces a passed Lucide icon (which renders with hardcoded\n * width/height=\"24\") to fill the icon slot proportionally — 16 / 20 / 24\n * matching the Spinner sizes so `<IconButton isLoading>` looks identical\n * in size to the resting icon state.\n *\n * `> svg` (direct child) is deliberate: Spinner renders a `<span>` wrapper\n * around its own SVG and sizes itself via `size={resolvedSize}`, so the\n * rule does not need to (and should not) double-size the spinner SVG.\n * Consumers wrapping an icon in a custom component remain responsible for\n * sizing — same contract shadcn / Mantine document. */\n\n.sizeSm {\n width: var(--pharos-spacing-8); /* 32 px */\n height: var(--pharos-spacing-8);\n}\n\n.sizeSm > svg {\n width: var(--pharos-spacing-4); /* 16 px */\n height: var(--pharos-spacing-4);\n}\n\n.sizeMd {\n width: var(--pharos-spacing-10); /* 40 px */\n height: var(--pharos-spacing-10);\n}\n\n.sizeMd > svg {\n width: var(--pharos-spacing-5); /* 20 px */\n height: var(--pharos-spacing-5);\n}\n\n.sizeLg {\n width: var(--pharos-spacing-12); /* 48 px */\n height: var(--pharos-spacing-12);\n}\n\n.sizeLg > svg {\n width: var(--pharos-spacing-6); /* 24 px */\n height: var(--pharos-spacing-6);\n}\n","import { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport { Spinner } from './Spinner';\nimport styles from './IconButton.module.css';\n\n/**\n * Maps the two public variant axes (`intent`, `size`) to the per-class names\n * inside `IconButton.module.css`. Mirrors the Button atom so an IconButton\n * sits next to a Button of the same size with matching height (32 / 40 /\n * 48 px) and shares the same intent vocabulary — `ghost` is the dominant\n * intent for icon-only controls (close, expand, dismiss…) so it is the\n * default, unlike Button where `primary` defaults.\n */\nconst iconButtonVariants = cva(styles.button, {\n variants: {\n intent: {\n primary: styles.primary,\n secondary: styles.secondary,\n ghost: styles.ghost,\n destructive: styles.destructive,\n },\n size: {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n },\n },\n defaultVariants: {\n intent: 'ghost',\n size: 'md',\n },\n});\n\ntype IconButtonVariantProps = VariantProps<typeof iconButtonVariants>;\n\n/**\n * Either `aria-label` or `aria-labelledby` is required at the type level.\n * An icon-only control with no accessible name is unusable for assistive\n * tech (WCAG 4.1.2). The atom enforces the constraint by construction —\n * the equivalent runtime check Chakra / Mantine emit as a console warning\n * is here a TypeScript error, so a missing label cannot ship.\n */\ntype IconButtonRequiredA11y =\n | { 'aria-label': string; 'aria-labelledby'?: never }\n | { 'aria-label'?: never; 'aria-labelledby': string };\n\n/**\n * Props accepted by `<IconButton>`. Extends Base UI's\n * `useRender.ComponentProps<'button'>` minus the optional `aria-label` /\n * `aria-labelledby` (re-added as required), plus the variant axes and an\n * `isLoading` toggle that swaps the icon for a `<Spinner>` of the same\n * size.\n *\n * The `render` prop composes the IconButton as a different element while\n * keeping its styles and behaviour — same shape as Button, useful for\n * `<IconButton render={<Link to=\"/next\" />} aria-label=\"Next\">…</IconButton>`\n * call-sites (e.g. circle navigation buttons).\n */\nexport type IconButtonProps = Omit<\n useRender.ComponentProps<'button'>,\n 'aria-label' | 'aria-labelledby'\n> &\n IconButtonVariantProps &\n IconButtonRequiredA11y & {\n /**\n * When `true`, replaces the icon with a `<Spinner>` of matching size,\n * sets `disabled` so the action cannot fire mid-flight, and exposes\n * `aria-busy=\"true\"` so screen readers announce the in-progress state.\n */\n isLoading?: boolean;\n };\n\nexport function IconButton({\n render,\n className,\n intent,\n size,\n isLoading,\n disabled,\n type = 'button',\n children,\n ref,\n ...rest\n}: IconButtonProps) {\n const resolvedIntent = intent ?? 'ghost';\n const resolvedSize = size ?? 'md';\n const isDisabled = Boolean(disabled) || Boolean(isLoading);\n return useRender({\n render: render ?? <button />,\n ref,\n defaultTagName: 'button',\n props: {\n type,\n 'data-pharos-intent': resolvedIntent,\n 'data-pharos-size': resolvedSize,\n 'aria-busy': isLoading ? true : undefined,\n disabled: isDisabled,\n className: cn(iconButtonVariants({ intent: resolvedIntent, size: resolvedSize }), className),\n ...rest,\n children: isLoading ? <Spinner size={resolvedSize} /> : children,\n },\n });\n}\n\nIconButton.displayName = 'IconButton';\n\nexport { iconButtonVariants };\n","/* Base layout. Same across every variant — only surface, label and border\n * change. Inline-flex so a child SVG (rendered by the consumer as plain\n * children) sits on the same baseline as the label. The font-relative SVG\n * sizing rule mirrors shadcn's `[&>svg]:size-3` utility. */\n.badge {\n display: inline-flex;\n align-items: center;\n gap: var(--pharos-spacing-1);\n padding: 0 var(--pharos-spacing-2);\n height: var(--pharos-spacing-5);\n border: 1px solid transparent;\n border-radius: var(--pharos-radius-full);\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-xs);\n font-weight: var(--pharos-font-weight-medium);\n line-height: 1;\n white-space: nowrap;\n width: fit-content;\n text-box-trim: trim-both;\n text-box-edge: cap alphabetic;\n}\n\n.badge > svg {\n width: 0.75em;\n height: 0.75em;\n flex: 0 0 auto;\n pointer-events: none;\n}\n\n.default {\n background: var(--pharos-color-neutral-900);\n color: var(--pharos-color-base-white);\n}\n\n.secondary {\n background: var(--pharos-color-neutral-100);\n color: var(--pharos-color-neutral-900);\n}\n\n.destructive {\n background: var(--pharos-color-semantic-error-fg);\n color: var(--pharos-color-semantic-error-on);\n}\n\n.outline {\n background: transparent;\n color: var(--pharos-color-neutral-900);\n /* `neutral-500` clears WCAG 1.4.11 (Non-text Contrast, 3:1 against the\n * surrounding surface) and matches the resting border of Input and\n * Button secondary so the system has one accessible boundary intensity\n * across primitives. See `Input.module.css` for the cross-DS rationale. */\n border-color: var(--pharos-color-neutral-500);\n}\n\n.success {\n background: var(--pharos-color-semantic-success-fg);\n color: var(--pharos-color-semantic-success-on);\n}\n\n.warning {\n background: var(--pharos-color-semantic-warning-fg);\n color: var(--pharos-color-semantic-warning-on);\n}\n\n.info {\n background: var(--pharos-color-semantic-info-fg);\n color: var(--pharos-color-semantic-info-on);\n}\n","import type { ComponentPropsWithoutRef } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Badge.module.css';\n\n/**\n * Maps the `variant` axis to per-class names inside `Badge.module.css`.\n * The variant set follows shadcn/ui — `default`, `secondary`, `destructive`,\n * `outline` — extended with the semantic tones every modern DS exposes\n * (`success`, `warning`, `info`). Naming stays asymmetric on purpose:\n * `destructive` is the canonical shadcn name for \"negative tone\" and we\n * keep it instead of renaming it to `error`, so consumers familiar with\n * shadcn pick the right value without re-learning the API.\n */\nconst badgeVariants = cva(styles.badge, {\n variants: {\n variant: {\n default: styles.default,\n secondary: styles.secondary,\n destructive: styles.destructive,\n outline: styles.outline,\n success: styles.success,\n warning: styles.warning,\n info: styles.info,\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n});\n\ntype BadgeVariantProps = VariantProps<typeof badgeVariants>;\n\nexport interface BadgeProps extends ComponentPropsWithoutRef<'span'>, BadgeVariantProps {}\n\nexport function Badge({ variant, className, ...rest }: BadgeProps) {\n const resolvedVariant = variant ?? 'default';\n return (\n <span\n data-pharos-variant={resolvedVariant}\n className={cn(badgeVariants({ variant: resolvedVariant }), className)}\n {...rest}\n />\n );\n}\n\nBadge.displayName = 'Badge';\n\nexport { badgeVariants };\n","/* Input.module.css\n *\n * Per-component CSS Modules definition (Decision D9). The build emits hashed\n * class names (`Input_input__xxxx`) into `dist/styles.css`; the runtime JS\n * references them through the `styles` object imported by Input.tsx.\n *\n * Every visual value comes from a `--pharos-*` token. No hex, no Tailwind,\n * no preflight. Hover / focus-visible / invalid / disabled states are\n * authored with native CSS pseudo-classes and the `[aria-invalid=\"true\"]`\n * attribute selector — the component does not own any \"error mode\" state\n * itself; it reflects what the consumer puts on the DOM.\n */\n\n.input {\n /* Strip the user-agent input chrome (gradient backgrounds, internal\n * padding, rounded corners on macOS, the right-edge spinner on number).\n * With preflight gone from the consumer, this is the only line that\n * normalizes the rendering across browsers. */\n appearance: none;\n -webkit-appearance: none;\n\n /* === Box model === */\n box-sizing: border-box;\n width: 100%;\n margin: 0;\n /* `neutral-500` lands the resting border at ~5.4:1 contrast against a\n * white surface, clearing WCAG 1.4.11 (Non-text Contrast, 3:1 for UI\n * component boundaries). The shadcn / Radix Themes / Mantine default\n * sits around oklch 92% (~1.3:1) and fails the requirement; Pharos\n * deliberately diverges to comply. Carbon `border-strong-01` (#8d8d8d)\n * and Adobe Spectrum's outline tone are at the same intensity. */\n border: 1px solid var(--pharos-color-neutral-500);\n border-radius: var(--pharos-radius-md);\n background-color: var(--pharos-color-base-white);\n color: var(--pharos-color-neutral-900);\n\n /* === Typography ===\n * `font-family` and weight are explicit so the input does not inherit a\n * system-ui fallback when it lives inside surfaces that haven't set the\n * Pharos sans stack (e.g. inside a `<dialog>` rendered to the top layer).\n * The size variants below override `font-size` and the `padding-inline`. */\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n font-weight: var(--pharos-font-weight-regular);\n line-height: 1.25;\n\n /* === Transitions === */\n transition-property: border-color, box-shadow;\n transition-duration: var(--pharos-duration-normal);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.input::placeholder {\n color: var(--pharos-color-neutral-500);\n /* Some Linux/Chromium builds dim placeholders to ~54% by default; the\n * explicit `opacity: 1` keeps the rendered tone equal to the token. */\n opacity: 1;\n}\n\n.input:focus-visible {\n outline: none;\n border-color: var(--pharos-color-primary-600);\n /* Two-stop box-shadow mirrors the Button focus ring: an inner halo in the\n * surface color separates the ring from the input fill, and the outer\n * stop is the brand accent. Same tokens as Button so the system has a\n * single focus look. */\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-primary-600);\n}\n\n.input:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.input[aria-invalid='true'] {\n border-color: var(--pharos-color-semantic-error-fg);\n}\n\n.input[aria-invalid='true']:focus-visible {\n border-color: var(--pharos-color-semantic-error-fg);\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-semantic-error-fg);\n}\n\n/* === Sizes ===\n *\n * Heights match the Button grid exactly — the system has one vertical\n * rhythm across pressable controls and form fields, so an `<Input size>`\n * sits flush next to a `<Button size>` on the same row. Horizontal padding\n * follows the same scale Button uses; font-size hops to `base` (16px) at\n * `lg` because larger controls usually pair with prominent layouts where\n * the body 14px starts to feel undersized. */\n\n.sizeSm {\n height: var(--pharos-spacing-8);\n padding: 0 var(--pharos-spacing-3);\n font-size: var(--pharos-font-size-sm);\n}\n\n.sizeMd {\n height: var(--pharos-spacing-10);\n padding: 0 var(--pharos-spacing-4);\n font-size: var(--pharos-font-size-sm);\n}\n\n.sizeLg {\n height: var(--pharos-spacing-12);\n padding: 0 var(--pharos-spacing-6);\n font-size: var(--pharos-font-size-base);\n}\n","import type { ComponentProps } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Input.module.css';\n\n/**\n * Maps the `size` axis to per-class names inside `Input.module.css`. Input\n * has no `variant` axis on purpose — shadcn's primitive contract is a single\n * styled `<input>` and the design-system best practice for form controls is\n * one chrome with explicit error / disabled / readonly states. Composition\n * around the input (label, helper, error message) belongs to a future\n * `<Field>` molecule, not to this atom (Escuela 1 from the field-pattern\n * decision, 2026-04-27).\n */\nconst inputVariants = cva(styles.input, {\n variants: {\n size: {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n },\n },\n defaultVariants: {\n size: 'md',\n },\n});\n\ntype InputVariantProps = VariantProps<typeof inputVariants>;\n\n/**\n * Props accepted by `<Input>`. Extends every native `<input>` attribute\n * except `size` — the native `size` attribute is a number (visible width\n * in characters) that is rarely used in modern apps where width comes from\n * CSS, so the slot is reused for the typed Pharos size axis. Consumers that\n * truly need the HTML attribute can set it through `style.width` or via a\n * dedicated `htmlSize` prop if we expose one later.\n *\n * Error state is conveyed through the standard `aria-invalid` attribute,\n * not a custom `error` prop. The CSS reacts to `[aria-invalid=\"true\"]`,\n * matching shadcn / Base UI / Mantine. The actual error message is rendered\n * by the consumer next to the input (or by the future `<Field>` molecule).\n */\nexport interface InputProps extends Omit<ComponentProps<'input'>, 'size'>, InputVariantProps {}\n\nexport function Input({ size, className, type = 'text', ref, ...rest }: InputProps) {\n const resolvedSize = size ?? 'md';\n return (\n <input\n ref={ref}\n type={type}\n data-pharos-size={resolvedSize}\n className={cn(inputVariants({ size: resolvedSize }), className)}\n {...rest}\n />\n );\n}\n\nInput.displayName = 'Input';\n\nexport { inputVariants };\n","/* Textarea.module.css\n *\n * Per-component CSS Modules definition (Decision D9). The build emits hashed\n * class names (`Textarea_textarea__xxxx`) into `dist/styles.css`; the runtime\n * JS references them through the `styles` object imported by Textarea.tsx.\n *\n * Every visual value comes from a `--pharos-*` token. No hex, no Tailwind,\n * no preflight. Hover / focus-visible / invalid / disabled states are\n * authored with native CSS pseudo-classes and the `[aria-invalid=\"true\"]`\n * attribute selector — the component does not own any \"error mode\" state\n * itself; it reflects what the consumer puts on the DOM.\n *\n * The chrome (border, radius, focus ring, invalid ring, disabled opacity)\n * is identical to `Input.module.css` so the two atoms read as siblings of\n * the same form-control family. The two genuine differences are:\n * - `min-height` instead of fixed `height` (textareas grow with content).\n * - `resize: vertical` (state-of-the-art shadcn default; consumer can\n * override via `style.resize` if needed).\n */\n\n.textarea {\n /* Strip the user-agent textarea chrome. With preflight gone from the\n * consumer, this is the only line that normalizes the rendering across\n * browsers. */\n appearance: none;\n -webkit-appearance: none;\n\n /* === Box model === */\n box-sizing: border-box;\n width: 100%;\n margin: 0;\n /* `neutral-500` lands the resting border at ~5.4:1 contrast against a\n * white surface, clearing WCAG 1.4.11 (Non-text Contrast, 3:1 for UI\n * component boundaries). Same tone Input uses; the form-control family\n * shares the resting border. */\n border: 1px solid var(--pharos-color-neutral-500);\n border-radius: var(--pharos-radius-md);\n background-color: var(--pharos-color-base-white);\n color: var(--pharos-color-neutral-900);\n\n /* === Layout ===\n * `resize: vertical` matches shadcn / Mantine / Radix Themes — the user\n * can drag the bottom edge to grow the textarea but cannot stretch it\n * horizontally (which would break form layouts). Consumers can opt out\n * via `style={{ resize: 'none' }}` if needed. */\n resize: vertical;\n\n /* === Typography ===\n * `font-family` and weight are explicit so the textarea does not inherit\n * a system-ui fallback when it lives inside surfaces that haven't set the\n * Pharos sans stack. Line-height 1.5 is more legible than Input's 1.25\n * because users frequently paste multi-line content here. */\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n font-weight: var(--pharos-font-weight-regular);\n line-height: 1.5;\n\n /* === Transitions === */\n transition-property: border-color, box-shadow;\n transition-duration: var(--pharos-duration-normal);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.textarea::placeholder {\n color: var(--pharos-color-neutral-500);\n opacity: 1;\n}\n\n.textarea:focus-visible {\n outline: none;\n border-color: var(--pharos-color-primary-600);\n /* Two-stop box-shadow mirrors Input + Button focus rings: an inner halo\n * in the surface color separates the ring from the textarea fill, and\n * the outer stop is the brand accent. Same tokens as Input so the\n * system has a single focus look across every form control. */\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-primary-600);\n}\n\n.textarea:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n /* Dragging the resize handle on a disabled control is jarring; mirror\n * `cursor: not-allowed` by removing the affordance entirely. */\n resize: none;\n}\n\n.textarea[aria-invalid='true'] {\n border-color: var(--pharos-color-semantic-error-fg);\n}\n\n.textarea[aria-invalid='true']:focus-visible {\n border-color: var(--pharos-color-semantic-error-fg);\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-semantic-error-fg);\n}\n\n/* === Sizes ===\n *\n * Inline padding matches Input exactly so a textarea sits flush next to an\n * input of the same size (no horizontal jog when stacked). Vertical padding\n * is set explicitly because textareas don't have the fixed-height that lets\n * Input use vertical centring. `min-height` is sized to ~3-4 lines of body\n * text at the corresponding font-size; `resize: vertical` lets the user\n * grow it past that.\n *\n * Font-size hops to `base` (16px) at `lg` for the same reason Input does:\n * larger controls usually pair with prominent layouts where 14px feels\n * undersized. */\n\n.sizeSm {\n min-height: var(--pharos-spacing-16);\n padding: var(--pharos-spacing-2) var(--pharos-spacing-3);\n font-size: var(--pharos-font-size-sm);\n}\n\n.sizeMd {\n min-height: var(--pharos-spacing-20);\n padding: var(--pharos-spacing-2-5) var(--pharos-spacing-4);\n font-size: var(--pharos-font-size-sm);\n}\n\n.sizeLg {\n min-height: var(--pharos-spacing-24);\n padding: var(--pharos-spacing-3) var(--pharos-spacing-6);\n font-size: var(--pharos-font-size-base);\n}\n","import type { ComponentProps } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Textarea.module.css';\n\n/**\n * Maps the `size` axis to per-class names inside `Textarea.module.css`. Like\n * `Input`, `Textarea` has no `variant` axis on purpose — shadcn's primitive\n * contract is a single styled `<textarea>` and the design-system best\n * practice for form controls is one chrome with explicit error / disabled /\n * readonly states. Composition around the textarea (label, helper, error\n * message) belongs to the future `<Field>` molecule, not to this atom\n * (Escuela 1 from the field-pattern decision, 2026-04-27).\n *\n * The three sizes share the resting visual contract with `<Input size>` so\n * a textarea sits flush next to inputs of the same size in a form: identical\n * inline padding, identical resting border tone (`neutral-500`), identical\n * font-size hop at `lg`, identical focus ring. The vertical dimension is\n * `min-height` instead of a fixed `height` because textareas grow with\n * content and the user can drag them taller (`resize: vertical`).\n */\nconst textareaVariants = cva(styles.textarea, {\n variants: {\n size: {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n },\n },\n defaultVariants: {\n size: 'md',\n },\n});\n\ntype TextareaVariantProps = VariantProps<typeof textareaVariants>;\n\n/**\n * Props accepted by `<Textarea>`. Extends every native `<textarea>`\n * attribute. Unlike `<Input>`, no native attribute is shadowed — the native\n * `<textarea>` does not have a `size` attribute, so the typed Pharos size\n * axis takes the slot without collision.\n *\n * Error state is conveyed through the standard `aria-invalid` attribute,\n * not a custom `error` prop. The CSS reacts to `[aria-invalid=\"true\"]`,\n * matching shadcn / Base UI / Mantine. The actual error message is rendered\n * by the consumer next to the textarea (or by the future `<Field>` molecule).\n */\nexport interface TextareaProps extends ComponentProps<'textarea'>, TextareaVariantProps {}\n\nexport function Textarea({ size, className, ref, ...rest }: TextareaProps) {\n const resolvedSize = size ?? 'md';\n return (\n <textarea\n ref={ref}\n data-pharos-size={resolvedSize}\n className={cn(textareaVariants({ size: resolvedSize }), className)}\n {...rest}\n />\n );\n}\n\nTextarea.displayName = 'Textarea';\n\nexport { textareaVariants };\n","/* Separator.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css` like the rest of pharos-react. The token of the\n * border colour comes from D12 (subtle borders for non-interactive\n * surfaces): a Separator is decorative or semantic but never an\n * interactive control, so WCAG 1.4.11 (3:1) does not apply and the\n * resting tone stays at `neutral-200` — same intensity Carbon\n * `border-subtle`, Adobe Spectrum gray-200 and Polaris use for\n * dividers.\n */\n\n.separator {\n /* `flex-shrink: 0` keeps a vertical Separator at 1px wide inside flex\n * rows that would otherwise compress it to zero. shadcn does the same\n * via the Tailwind `shrink-0` utility; we hard-code it because every\n * Separator wants this behaviour and there is no axis where dropping\n * it would be useful. */\n flex-shrink: 0;\n background-color: var(--pharos-color-neutral-200);\n /* Reset any user-agent margin (notably the default `<hr>` margin) when\n * the Separator is rendered as a `<div>`. A consumer that wants\n * spacing controls it from the outside via flex / margin / padding,\n * not via implicit user-agent rules. */\n margin: 0;\n border: 0;\n}\n\n.horizontal {\n width: 100%;\n height: 1px;\n}\n\n.vertical {\n height: 100%;\n width: 1px;\n}\n","import type { ComponentPropsWithoutRef } from 'react';\nimport { cn } from '../lib/cn';\nimport styles from './Separator.module.css';\n\n/**\n * Visual or semantic divider between two regions. Renders a thin line in\n * either orientation. Two contracts coexist behind the same component:\n *\n * - **Decorative** (default, `decorative={true}`) — the line is purely\n * visual chrome. Marked `role=\"none\"` so assistive tech ignores it,\n * the same way shadcn / Radix Separator do.\n * - **Semantic** (`decorative={false}`) — the line announces a logical\n * partition (e.g. between groups in a menu). Marked `role=\"separator\"`\n * and, for vertical separators, `aria-orientation=\"vertical\"` per the\n * ARIA APG (the spec defaults `aria-orientation` to \"horizontal\" for\n * role=separator, so we emit the attribute only when it differs).\n *\n * The resting border tone is `--pharos-color-neutral-200` (Decision D12:\n * subtle borders for non-interactive surfaces). WCAG 1.4.11 does not\n * apply to decorative separators or to the boundaries of non-interactive\n * containers; the system reserves the stronger `neutral-500` tone for\n * interactive controls (Input, Button secondary, Badge outline — D10).\n */\nexport interface SeparatorProps extends ComponentPropsWithoutRef<'div'> {\n orientation?: 'horizontal' | 'vertical';\n decorative?: boolean;\n}\n\nexport function Separator({\n orientation = 'horizontal',\n decorative = true,\n className,\n ...rest\n}: SeparatorProps) {\n return (\n <div\n role={decorative ? 'none' : 'separator'}\n aria-orientation={!decorative && orientation === 'vertical' ? 'vertical' : undefined}\n data-pharos-orientation={orientation}\n className={cn(styles.separator, styles[orientation], className)}\n {...rest}\n />\n );\n}\n\nSeparator.displayName = 'Separator';\n","/* Card.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. The variant tones land on the border-intensity\n * hierarchy from D12: `default` and `elevated` carry the subtle tier,\n * `outlined` carries the emphasised tier. None of them reach the\n * strong tier reserved for interactive controls.\n */\n\n.card {\n /* === Surface === */\n background-color: var(--pharos-color-base-white);\n border-radius: var(--pharos-radius-2xl);\n /* Default to no border / no shadow at the base level — variants opt\n * into one or the other. Keeping the base neutral means a consumer\n * that overrides via `className` does not have to fight defaults. */\n border: 0 solid transparent;\n\n /* === Layout ===\n * Cards default to vertical stacking of their slots (Header / Content /\n * Footer). The slots themselves carry their own padding so the Card\n * never imposes a global padding that would conflict with full-bleed\n * media headers. */\n display: flex;\n flex-direction: column;\n gap: var(--pharos-spacing-6);\n\n /* === Typography ===\n * Set the body baseline for the card so `<CardTitle>` and\n * `<CardDescription>` can size relatively if a future caller wants. */\n font-family: var(--pharos-font-family-sans);\n color: var(--pharos-color-neutral-900);\n\n /* === Transitions === */\n transition-property: border-color, box-shadow;\n transition-duration: var(--pharos-duration-normal);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n/* === Variants === */\n\n.default {\n border: 1px solid var(--pharos-color-neutral-200);\n}\n\n.elevated {\n border: 0 solid transparent;\n box-shadow: var(--pharos-shadow-md);\n}\n\n.outlined {\n border: 1px solid var(--pharos-color-neutral-300);\n}\n\n/* === Slots ===\n *\n * Every slot is a styled `<div>` (CardTitle stays a `<div>` like shadcn,\n * so the consumer picks the semantic heading level). Padding is consistent\n * across the three slots — the consumer composes them inside `<Card>`\n * without thinking about layout details. */\n\n.header {\n display: flex;\n flex-direction: column;\n gap: var(--pharos-spacing-1-5);\n padding: var(--pharos-spacing-6) var(--pharos-spacing-6) 0;\n}\n\n.title {\n margin: 0;\n font-size: var(--pharos-font-size-base);\n font-weight: var(--pharos-font-weight-semibold);\n line-height: 1.25;\n color: var(--pharos-color-neutral-900);\n}\n\n.description {\n margin: 0;\n font-size: var(--pharos-font-size-sm);\n font-weight: var(--pharos-font-weight-regular);\n line-height: 1.5;\n color: var(--pharos-color-neutral-500);\n}\n\n.content {\n /* Header padding-top + Footer padding-bottom already provide the\n * vertical breathing room. Content itself only carries inline padding;\n * its vertical rhythm comes from the parent `gap`. When the card has\n * no Header/Footer, Content's lack of vertical padding means the card\n * is content-only with the card-level `gap` collapsing to zero — the\n * consumer applies their own padding via `style` or `className`. */\n padding: 0 var(--pharos-spacing-6);\n}\n\n.footer {\n display: flex;\n align-items: center;\n gap: var(--pharos-spacing-3);\n padding: 0 var(--pharos-spacing-6) var(--pharos-spacing-6);\n}\n","import type { ComponentPropsWithoutRef } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Card.module.css';\n\n/**\n * Container surface for grouped content. Three variants on the\n * border-intensity hierarchy (Decision D12):\n *\n * - `default` (subtle border, no shadow) — the most common card. Sits\n * on the same `neutral-200` tone as `Separator`, so panels and\n * dividers harmonise visually.\n * - `elevated` (no border, soft shadow) — for surfaces that should lift\n * off the canvas (modals' inner cards, important callouts).\n * - `outlined` (deliberately stronger border, no shadow) — emphasised\n * container. Sits on `neutral-300`, between subtle non-interactive\n * tone and the strong interactive tone reserved for controls.\n *\n * Sub-components (`CardHeader`, `CardTitle`, `CardDescription`,\n * `CardContent`, `CardFooter`) are slot primitives, not separate atoms\n * — same pattern shadcn uses. They are exported individually so the\n * consumer composes the structure they need; an empty Card with no\n * slots is equally valid.\n */\nconst cardVariants = cva(styles.card, {\n variants: {\n variant: {\n default: styles.default,\n elevated: styles.elevated,\n outlined: styles.outlined,\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n});\n\ntype CardVariantProps = VariantProps<typeof cardVariants>;\n\nexport interface CardProps extends ComponentPropsWithoutRef<'div'>, CardVariantProps {}\n\nexport function Card({ variant, className, ...rest }: CardProps) {\n const resolvedVariant = variant ?? 'default';\n return (\n <div\n data-pharos-variant={resolvedVariant}\n className={cn(cardVariants({ variant: resolvedVariant }), className)}\n {...rest}\n />\n );\n}\n\nCard.displayName = 'Card';\n\nexport interface CardHeaderProps extends ComponentPropsWithoutRef<'div'> {}\n\nexport function CardHeader({ className, ...rest }: CardHeaderProps) {\n return <div data-pharos-slot=\"card-header\" className={cn(styles.header, className)} {...rest} />;\n}\n\nCardHeader.displayName = 'CardHeader';\n\nexport interface CardTitleProps extends ComponentPropsWithoutRef<'div'> {}\n\nexport function CardTitle({ className, ...rest }: CardTitleProps) {\n // Renders as a `<div>` so the consumer picks the semantic heading\n // level (`<h2>`, `<h3>`, etc.) by wrapping or by using the native\n // `role` and `aria-level` attributes if needed. Same default as\n // shadcn's CardTitle — atoms do not impose document outline.\n return <div data-pharos-slot=\"card-title\" className={cn(styles.title, className)} {...rest} />;\n}\n\nCardTitle.displayName = 'CardTitle';\n\nexport interface CardDescriptionProps extends ComponentPropsWithoutRef<'p'> {}\n\nexport function CardDescription({ className, ...rest }: CardDescriptionProps) {\n return (\n <p\n data-pharos-slot=\"card-description\"\n className={cn(styles.description, className)}\n {...rest}\n />\n );\n}\n\nCardDescription.displayName = 'CardDescription';\n\nexport interface CardContentProps extends ComponentPropsWithoutRef<'div'> {}\n\nexport function CardContent({ className, ...rest }: CardContentProps) {\n return (\n <div data-pharos-slot=\"card-content\" className={cn(styles.content, className)} {...rest} />\n );\n}\n\nCardContent.displayName = 'CardContent';\n\nexport interface CardFooterProps extends ComponentPropsWithoutRef<'div'> {}\n\nexport function CardFooter({ className, ...rest }: CardFooterProps) {\n return <div data-pharos-slot=\"card-footer\" className={cn(styles.footer, className)} {...rest} />;\n}\n\nCardFooter.displayName = 'CardFooter';\n\nexport { cardVariants };\n","/* Avatar.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Compound:\n * `Avatar` (root), `AvatarImage`, `AvatarFallback`, `AvatarGroup`. The\n * build emits hashed class names (`Avatar_root__xxxx`) into\n * `dist/styles.css`; the runtime JS references them through the\n * `styles` object imported by Avatar.tsx.\n *\n * Sizes (D14): 3 named (sm / md / lg = 32 / 40 / 48 px) aligned to the\n * IconButton height grid. Consumers needing a one-off (a 108px profile\n * picture, a 20px stack avatar) pass `size={number}` and the runtime\n * sets `width` / `height` inline — same escape pattern Mantine /\n * Ant Design ship. The `--pharos-avatar-overlap` CSS variable cascades\n * to AvatarGroup so the negative margin scales with the chosen size.\n *\n * Shape: `circle` (default) via `border-radius: full`; `square` via\n * `radius-md`. The atom does not impose a breakpoint-conditional\n * shape — that is the consumer's call.\n */\n\n.root {\n /* === Box model ===\n * Square dimensions with `border-radius` doing the circle/rounded\n * shape. `flex-shrink: 0` prevents the avatar from collapsing inside\n * a flex row (typical in stacks and people lists). `overflow: hidden`\n * clips the inner `<img>` to the shape — the same approach Radix /\n * Base UI document.\n *\n * `position: relative` anchors the absolutely-positioned image so it\n * stacks on top of the fallback. Without this the image and the\n * fallback would compete as siblings of the same flex container, and\n * during the loading window both would render at `width: 100%` —\n * shrinking to 50/50 inside the avatar (visible as a split chrome\n * until the image paints). Layering the image on top keeps the\n * fallback as the single flex item that centres its content, and\n * the image fades in over it once the browser decodes the pixels. */\n position: relative;\n box-sizing: border-box;\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n\n /* === Visual baseline ===\n * Neutral surface behind the fallback so the slot reads as a defined\n * shape even before the image (or text) renders. `color: inherit`\n * lets icons inside Fallback pick the parent's text colour. */\n background-color: var(--pharos-color-neutral-100);\n color: var(--pharos-color-neutral-700);\n font-family: var(--pharos-font-family-sans);\n font-weight: 500;\n line-height: 1;\n user-select: none;\n\n /* Default overlap step for grouped avatars. The size classes below\n * override it so the negative margin scales with the avatar. */\n --pharos-avatar-overlap: calc(var(--pharos-spacing-2) * -1); /* -8px */\n}\n\n/* === Shape === */\n\n.shapeCircle {\n border-radius: var(--pharos-radius-full);\n}\n\n.shapeSquare {\n /* Matches the `Card` outlined corner radius family — squared avatars\n * (org logos, product icons) sit consistently next to cards on the\n * same surface. */\n border-radius: var(--pharos-radius-md);\n}\n\n/* === Sizes ===\n *\n * Named sizes match the IconButton grid (32 / 40 / 48 px). Initials\n * type scale follows the body 14 / 20 rhythm (D3): sm shrinks to 12 px,\n * md / lg sit at 14 / 16 px. The fallback `font-size` is set on\n * `.root` so the text reflows correctly when the consumer passes an\n * inline-style override.\n *\n * Each size also pegs the `--pharos-avatar-overlap` so AvatarGroup\n * pulls the next sibling proportionally — ~20% of the avatar diameter,\n * the same ratio MUI / Chakra / Mantine ship by default. */\n\n.sizeSm {\n width: var(--pharos-spacing-8); /* 32 px */\n height: var(--pharos-spacing-8);\n font-size: 0.75rem; /* 12 px */\n --pharos-avatar-overlap: calc(var(--pharos-spacing-2) * -0.75); /* -6 px */\n}\n\n.sizeMd {\n width: var(--pharos-spacing-10); /* 40 px */\n height: var(--pharos-spacing-10);\n font-size: 0.875rem; /* 14 px */\n --pharos-avatar-overlap: calc(var(--pharos-spacing-2) * -1); /* -8 px */\n}\n\n.sizeLg {\n width: var(--pharos-spacing-12); /* 48 px */\n height: var(--pharos-spacing-12);\n font-size: 1rem; /* 16 px */\n --pharos-avatar-overlap: calc(var(--pharos-spacing-2) * -1.25); /* -10 px */\n}\n\n/* === Image ===\n *\n * The img fills the root completely with `object-fit: cover` so a\n * non-square portrait crops to the avatar shape without distortion.\n * `position: absolute` lifts the image out of the flex flow so it can\n * stack on top of the fallback during the load window — without this\n * the two would split the root's width 50/50 because each carries\n * `width: 100%` (visible as a fractured chrome on size=lg, story\n * grids, and any docs page that renders multiple Avatars before the\n * images decode). */\n\n.image {\n position: absolute;\n inset: 0;\n display: block;\n width: 100%;\n height: 100%;\n object-fit: cover;\n /* Hairline border-radius inheritance to keep clipping crisp under\n * sub-pixel rendering — `overflow: hidden` on the root handles the\n * clip but the inner `<img>` repeating the radius prevents a\n * one-pixel halo on some browsers. */\n border-radius: inherit;\n}\n\n/* === Fallback === */\n\n.fallback {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n text-align: center;\n /* Inherit the typography baked into the root via `.sizeX`. */\n font: inherit;\n color: inherit;\n}\n\n/* The Fallback owns a direct-child `> svg` sizing rule for icon-only\n * fallbacks (e.g. `<UserIcon/>`). Icons render at ~60% of the avatar\n * diameter — matching the Material 3 / MUI default when an Avatar\n * falls back to its generic person silhouette. */\n\n.sizeSm .fallback > svg {\n width: var(--pharos-spacing-5); /* 20 px ≈ 62.5% of 32 */\n height: var(--pharos-spacing-5);\n}\n\n.sizeMd .fallback > svg {\n width: var(--pharos-spacing-6); /* 24 px = 60% of 40 */\n height: var(--pharos-spacing-6);\n}\n\n.sizeLg .fallback > svg {\n width: var(--pharos-spacing-7); /* 28 px ≈ 58% of 48 */\n height: var(--pharos-spacing-7);\n}\n\n/* === Group ===\n *\n * Stack avatars with negative left margin. The first child sits flush;\n * each subsequent child slides under the previous one. The `ring` is\n * applied as a `box-shadow` (not `border`) so it does not shrink the\n * avatar's content box — same trick Tailwind UI / MUI use. The ring\n * colour reads from a CSS var so the consumer can match the\n * surrounding surface (e.g. a card with a tinted background). */\n\n.group {\n display: inline-flex;\n align-items: center;\n /* Direction control: `flex-direction: row-reverse` would reverse\n * stacking order; we keep the default and let the consumer pass\n * children in the order they prefer. The first-rendered child sits\n * on top of the stack because z-index increases via :nth-child\n * below. */\n}\n\n.group > .root {\n /* The ring fills the gap that the negative margin opens between\n * stacked avatars so each silhouette reads as a separate shape.\n * `--pharos-avatar-group-ring` defaults to white (the page surface);\n * a consumer with a tinted backdrop overrides it. */\n box-shadow: 0 0 0 2px var(--pharos-avatar-group-ring, var(--pharos-color-base-white));\n /* Position relative + z-index lets the box-shadow ring sit above\n * the next sibling so the gap stays visible. */\n position: relative;\n}\n\n.group > .root + .root {\n margin-left: var(--pharos-avatar-overlap);\n}\n\n/* === Loading status data attributes ===\n *\n * The Image element exposes `data-pharos-loading` while it transitions\n * through `idle` → `loaded` / `error`. The Fallback reads the same\n * attribute on the root via context (no CSS selector needed, but the\n * data attribute remains queryable from e2e tests). */\n","'use client';\n\n/* The `'use client'` directive at the top of this file is load-bearing.\n * Avatar runs `createContext(...)` three times at module top level (shape /\n * loading / group). Without the directive, Next.js evaluates the module in\n * its build-time RSC analysis pass, where React is wired in a way that\n * makes `createContext` blow up with\n * `TypeError: (0 , c.createContext) is not a function` during page-data\n * collection. The directive marks the module as client-only in the\n * published bundle (esbuild / Vite preserve the banner verbatim) and the\n * RSC analysis stops at the import boundary.\n *\n * The other Pharos atoms (Button / Spinner / etc.) do not need this\n * because their hook calls happen inside the component body, not at module\n * scope — module evaluation is side-effect free for them.\n */\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ComponentPropsWithoutRef,\n type CSSProperties,\n type ReactNode,\n type Ref,\n} from 'react';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cn } from '../lib/cn';\nimport styles from './Avatar.module.css';\n\n/**\n * Compound atom (Decision D14). The state-of-the-art convention across\n * shadcn / Radix / Base UI / Chakra is to expose `Avatar`,\n * `AvatarImage`, and `AvatarFallback` as discrete parts so the consumer\n * composes the fallback content (initials, icon, default image)\n * directly — same Escuela 1 composition principle Pharos applies for\n * Input / Textarea (D11).\n *\n * Two contexts coordinate the parts:\n *\n * - `AvatarShapeContext` carries the resolved `size` / `shape` for\n * nested `> svg` sizing and AvatarGroup cascade.\n * - `AvatarLoadingContext` carries the image load status so\n * `AvatarFallback` only renders when the image is missing or has\n * failed — the same render-gate pattern Radix Primitives document.\n *\n * The group reads `AvatarGroupContext` to cascade `size` / `shape` to\n * every Avatar child and to inject the overflow \"+N\" badge when the\n * children exceed `max`.\n */\n\ntype AvatarSize = 'sm' | 'md' | 'lg' | number;\ntype AvatarShape = 'circle' | 'square';\n\ninterface AvatarShapeContextValue {\n size: AvatarSize;\n shape: AvatarShape;\n}\n\nconst AvatarShapeContext = createContext<AvatarShapeContextValue | null>(null);\n\ntype AvatarLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error';\n\ninterface AvatarLoadingContextValue {\n status: AvatarLoadingStatus;\n setStatus: (next: AvatarLoadingStatus) => void;\n}\n\nconst AvatarLoadingContext = createContext<AvatarLoadingContextValue | null>(null);\n\ninterface AvatarGroupContextValue {\n size: AvatarSize;\n shape: AvatarShape;\n}\n\nconst AvatarGroupContext = createContext<AvatarGroupContextValue | null>(null);\n\nconst SIZE_CLASSNAME: Record<Exclude<AvatarSize, number>, string> = {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n};\n\nconst SHAPE_CLASSNAME: Record<AvatarShape, string> = {\n circle: styles.shapeCircle,\n square: styles.shapeSquare,\n};\n\nfunction resolveSizeStyle(size: AvatarSize): CSSProperties | undefined {\n if (typeof size !== 'number') return undefined;\n // Numeric escape: consumer-supplied one-off dimension. Font-size\n // follows the 0.4 ratio the named sizes (12/14/16 at 32/40/48) imply\n // so an initials fallback still reads correctly inside a non-grid\n // avatar.\n return {\n width: size,\n height: size,\n fontSize: `${Math.round(size * 0.4)}px`,\n };\n}\n\nfunction resolveSizeClassName(size: AvatarSize): string | undefined {\n if (typeof size === 'number') return undefined;\n return SIZE_CLASSNAME[size];\n}\n\n// ---------------------------------------------------------------------------\n// Avatar (Root)\n// ---------------------------------------------------------------------------\n\nexport interface AvatarProps extends Omit<useRender.ComponentProps<'span'>, 'children'> {\n /**\n * Avatar dimensions. Named sizes (`sm` / `md` / `lg` = 32 / 40 / 48 px)\n * align with the IconButton / Button height grid. A numeric value\n * sets one-off `width` / `height` / `font-size` inline — use it for\n * profile-picture sized images (e.g. 108 px) or compact stacks\n * (20 px) outside the canonical grid.\n *\n * @default 'md'\n */\n size?: AvatarSize;\n /**\n * Border-radius family. `circle` (default) clips the avatar to a\n * perfect circle via `border-radius: full`; `square` uses the\n * `radius-md` token so an org or product avatar harmonises with the\n * Card corner family on the same surface.\n *\n * @default 'circle'\n */\n shape?: AvatarShape;\n /**\n * The Image and Fallback parts, plus any content the consumer wishes\n * to layer (status badge, ring overlay, etc.). The atom does not\n * inject a default `<AvatarFallback>` — composition is explicit so\n * the consumer is in control of the fallback content (initials,\n * icon, default PNG).\n */\n children?: ReactNode;\n}\n\nexport function Avatar({\n size,\n shape,\n className,\n style,\n render,\n ref,\n children,\n ...rest\n}: AvatarProps) {\n const group = useContext(AvatarGroupContext);\n const resolvedSize: AvatarSize = size ?? group?.size ?? 'md';\n const resolvedShape: AvatarShape = shape ?? group?.shape ?? 'circle';\n\n const [status, setStatus] = useState<AvatarLoadingStatus>('idle');\n\n const shapeCtx = useMemo<AvatarShapeContextValue>(\n () => ({ size: resolvedSize, shape: resolvedShape }),\n [resolvedSize, resolvedShape],\n );\n const loadingCtx = useMemo<AvatarLoadingContextValue>(() => ({ status, setStatus }), [status]);\n\n // Numeric vs named: the named path applies a size class that owns\n // width / height / font-size / --pharos-avatar-overlap; the numeric\n // path skips the class and writes width / height / font-size inline.\n // The shape class always applies — same border-radius regardless of\n // sizing path.\n const sizeClassName = resolveSizeClassName(resolvedSize);\n const sizeStyle = resolveSizeStyle(resolvedSize);\n\n const resolvedSizeAttr = typeof resolvedSize === 'number' ? String(resolvedSize) : resolvedSize;\n\n return (\n <AvatarShapeContext.Provider value={shapeCtx}>\n <AvatarLoadingContext.Provider value={loadingCtx}>\n {useRender({\n render: render ?? <span />,\n ref,\n defaultTagName: 'span',\n props: {\n 'data-pharos-size': resolvedSizeAttr,\n 'data-pharos-shape': resolvedShape,\n 'data-pharos-loading': status,\n className: cn(styles.root, sizeClassName, SHAPE_CLASSNAME[resolvedShape], className),\n style: sizeStyle ? { ...sizeStyle, ...style } : style,\n ...rest,\n children,\n },\n })}\n </AvatarLoadingContext.Provider>\n </AvatarShapeContext.Provider>\n );\n}\n\nAvatar.displayName = 'Avatar';\n\n// ---------------------------------------------------------------------------\n// AvatarImage\n// ---------------------------------------------------------------------------\n\nexport interface AvatarImageProps extends ComponentPropsWithoutRef<'img'> {\n /**\n * Optional callback invoked when the image transitions between load\n * states. Mirrors Radix / Base UI's `onLoadingStatusChange` so an\n * external consumer can react to the cascade (e.g. emit telemetry\n * when a CDN avatar fails).\n */\n onLoadingStatusChange?: (status: AvatarLoadingStatus) => void;\n ref?: Ref<HTMLImageElement>;\n}\n\nexport function AvatarImage({\n className,\n src,\n onLoad,\n onError,\n onLoadingStatusChange,\n ref,\n ...rest\n}: AvatarImageProps) {\n const loading = useContext(AvatarLoadingContext);\n\n // When no `src` lands, the image stays hidden and the Fallback\n // renders. Same contract Radix and Base UI document. Without this\n // guard the browser would request the current URL relatively\n // (interpreting `undefined` as empty string), polluting the network\n // tab with a useless 404.\n const hasSrc = typeof src === 'string' && src.length > 0;\n\n useEffect(() => {\n if (!loading) return;\n // Whenever `src` changes we re-enter the loading phase. The browser\n // re-fires `onLoad` / `onError` for the new URL; the local state\n // resets here so the Fallback returns until the new load resolves.\n loading.setStatus(hasSrc ? 'loading' : 'idle');\n onLoadingStatusChange?.(hasSrc ? 'loading' : 'idle');\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [src]);\n\n const handleLoad = useCallback(\n (event: React.SyntheticEvent<HTMLImageElement, Event>) => {\n loading?.setStatus('loaded');\n onLoadingStatusChange?.('loaded');\n onLoad?.(event);\n },\n [loading, onLoad, onLoadingStatusChange],\n );\n\n const handleError = useCallback(\n (event: React.SyntheticEvent<HTMLImageElement, Event>) => {\n loading?.setStatus('error');\n onLoadingStatusChange?.('error');\n onError?.(event);\n },\n [loading, onError, onLoadingStatusChange],\n );\n\n if (!hasSrc) return null;\n if (loading && loading.status === 'error') return null;\n\n return (\n <img\n ref={ref}\n src={src}\n onLoad={handleLoad}\n onError={handleError}\n data-pharos-loading={loading?.status}\n className={cn(styles.image, className)}\n {...rest}\n />\n );\n}\n\nAvatarImage.displayName = 'AvatarImage';\n\n// ---------------------------------------------------------------------------\n// AvatarFallback\n// ---------------------------------------------------------------------------\n\nexport interface AvatarFallbackProps extends ComponentPropsWithoutRef<'span'> {\n /**\n * Milliseconds to wait before rendering the fallback. Useful when\n * the image is likely to load quickly — delaying the fallback avoids\n * a flash of placeholder before the real avatar paints. Same\n * `delayMs` contract Radix / Base UI document. Defaults to `0`\n * (render immediately while the image is still loading or after an\n * error).\n */\n delayMs?: number;\n ref?: Ref<HTMLSpanElement>;\n}\n\nexport function AvatarFallback({\n className,\n delayMs = 0,\n children,\n ref,\n ...rest\n}: AvatarFallbackProps) {\n const loading = useContext(AvatarLoadingContext);\n const [canRender, setCanRender] = useState(delayMs === 0);\n\n useEffect(() => {\n if (delayMs === 0) {\n setCanRender(true);\n return;\n }\n const handle = window.setTimeout(() => setCanRender(true), delayMs);\n return () => window.clearTimeout(handle);\n }, [delayMs]);\n\n // The fallback hides as soon as the image reports `loaded`. Until\n // then (idle / loading / error) the slot is on screen. A consumer\n // that prefers to keep the fallback while the image is mid-flight\n // gets the default; a consumer that only wants the fallback after a\n // hard failure can branch on `data-pharos-loading=\"error\"` from the\n // root.\n if (loading && loading.status === 'loaded') return null;\n if (!canRender) return null;\n\n return (\n <span\n ref={ref}\n data-pharos-slot=\"avatar-fallback\"\n data-pharos-loading={loading?.status}\n className={cn(styles.fallback, className)}\n {...rest}\n >\n {children}\n </span>\n );\n}\n\nAvatarFallback.displayName = 'AvatarFallback';\n\n// ---------------------------------------------------------------------------\n// AvatarGroup\n// ---------------------------------------------------------------------------\n\nexport interface AvatarGroupProps extends ComponentPropsWithoutRef<'div'> {\n /**\n * Default `size` cascaded to every Avatar child that does not set\n * its own. Mirrors MUI / Chakra / Mantine Avatar.Group sizing.\n *\n * @default 'md'\n */\n size?: AvatarSize;\n /**\n * Default `shape` cascaded to every Avatar child that does not set\n * its own.\n *\n * @default 'circle'\n */\n shape?: AvatarShape;\n /**\n * Maximum number of Avatar children to render before an overflow\n * indicator takes over the remaining slots. When the children count\n * exceeds `max`, the group renders `max - 1` avatars followed by a\n * compact \"+N\" Avatar that summarises the surplus.\n *\n * Omitting the prop displays every Avatar without an overflow\n * indicator — the contract MUI / Chakra apply.\n */\n max?: number;\n /**\n * Optional renderer for the overflow indicator. Receives the surplus\n * count and returns the React node placed at the end of the stack\n * (wrapped in an Avatar with matching `size` / `shape`). Defaults to\n * a `+N` text fallback.\n */\n renderOverflow?: (surplus: number) => ReactNode;\n ref?: Ref<HTMLDivElement>;\n}\n\nexport function AvatarGroup({\n size,\n shape,\n max,\n renderOverflow,\n className,\n children,\n ref,\n ...rest\n}: AvatarGroupProps) {\n const resolvedSize: AvatarSize = size ?? 'md';\n const resolvedShape: AvatarShape = shape ?? 'circle';\n\n const childArray = useMemo(() => {\n const list: ReactNode[] = [];\n // `React.Children.toArray` would assign sequential keys, but using\n // a `Children.forEach` keeps the consumer-provided keys intact\n // when present. The order Avatar children appear in the DOM is the\n // order the consumer passed them — first child paints on top.\n let index = 0;\n const items: ReactNode[] = [];\n const append = (child: ReactNode) => {\n items.push(child);\n index += 1;\n };\n if (Array.isArray(children)) {\n children.forEach((child) => append(child));\n } else if (children) {\n append(children);\n }\n return items.concat(list).slice(0, index);\n }, [children]);\n\n const visible =\n typeof max === 'number' && childArray.length > max\n ? childArray.slice(0, Math.max(max - 1, 0))\n : childArray;\n const surplus =\n typeof max === 'number' && childArray.length > max ? childArray.length - visible.length : 0;\n\n const groupCtx = useMemo<AvatarGroupContextValue>(\n () => ({ size: resolvedSize, shape: resolvedShape }),\n [resolvedSize, resolvedShape],\n );\n\n return (\n <AvatarGroupContext.Provider value={groupCtx}>\n <div\n ref={ref}\n data-pharos-slot=\"avatar-group\"\n data-pharos-size={typeof resolvedSize === 'number' ? String(resolvedSize) : resolvedSize}\n data-pharos-shape={resolvedShape}\n className={cn(styles.group, className)}\n {...rest}\n >\n {visible}\n {surplus > 0 ? (\n <Avatar aria-label={`${surplus} more`} data-pharos-slot=\"avatar-group-overflow\">\n <AvatarFallback>\n {renderOverflow ? renderOverflow(surplus) : `+${surplus}`}\n </AvatarFallback>\n </Avatar>\n ) : null}\n </div>\n </AvatarGroupContext.Provider>\n );\n}\n\nAvatarGroup.displayName = 'AvatarGroup';\n","/* DropdownMenu.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. Styles the floating surface (`.content`), the\n * action rows (`.item`, with a `destructive` tone), the structural\n * parts (`.separator`, `.label`, `.group`), and the open/close\n * transition driven by Base UI's `data-*` state attributes.\n *\n * z-index lives on `.positioner` (see below). Base UI portals the menu to the\n * end of `<body>`; that floats it above in-flow content, but not above a\n * host-app overlay (drawer/modal) with its own z-index — and a z-index on the\n * Popup is trapped by the Positioner's transform stacking context. The\n * Positioner therefore carries `z-index: var(--pharos-z-index-popover, …)`,\n * the shared scale Combobox / Select / Popover all use now. */\n\n/* === Positioner (Menu.Positioner) ===\n * z-index lives here, not on `.content` (the Popup). Base UI's Positioner\n * anchors with `transform`, which creates a stacking context — so a z-index\n * on the inner Popup is trapped inside it and cannot rise above a host-app\n * overlay (e.g. a drawer with its own z-index). The z-index must sit on the\n * Positioner itself.\n *\n * Sourced from `--pharos-z-index-popover` (no hardcoded value — NON-NEGOTIABLE\n * rule 1). The fallback suits apps with no high z-index scale; an app whose\n * own overlays sit higher (drawers, modals) raises every Pharos popup at once\n * by overriding `--pharos-z-index-popover` in `:root`. */\n.positioner {\n z-index: var(--pharos-z-index-popover, 1000);\n}\n\n/* === Floating surface (Menu.Popup) === */\n.content {\n box-sizing: border-box;\n min-width: 12rem;\n padding: var(--pharos-spacing-1);\n\n background-color: var(--pharos-color-base-white);\n border: 1px solid var(--pharos-color-neutral-200);\n border-radius: var(--pharos-radius-md);\n box-shadow: var(--pharos-shadow-lg);\n\n font-family: var(--pharos-font-family-sans);\n color: var(--pharos-color-neutral-900);\n\n /* Open/close transition keyed off Base UI's `data-starting-style` /\n * `data-ending-style`. The popup fades and lifts a few pixels from\n * the anchored side — the same motion shadcn / Radix ship. */\n transform-origin: var(--transform-origin);\n transition-property: opacity, transform;\n transition-duration: var(--pharos-duration-fast);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.content[data-starting-style],\n.content[data-ending-style] {\n opacity: 0;\n transform: scale(0.96);\n}\n\n/* === Action row (Menu.Item) === */\n.item {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: var(--pharos-spacing-2);\n width: 100%;\n padding: var(--pharos-spacing-1-5) var(--pharos-spacing-2);\n\n border-radius: var(--pharos-radius-sm);\n font-size: var(--pharos-font-size-sm);\n font-weight: var(--pharos-font-weight-medium);\n line-height: 1.25;\n color: var(--pharos-color-neutral-700);\n\n cursor: default;\n user-select: none;\n outline: none;\n scroll-margin: var(--pharos-spacing-1);\n\n transition-property: background-color, color;\n transition-duration: var(--pharos-duration-fast);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n/* Base UI marks the active row (keyboard navigation or pointer, when\n * `highlightItemOnHover` is on) with `data-highlighted`. A single\n * highlight state covers both pointer hover and arrow-key focus, so\n * the menu reads identically whichever input drives it. */\n.item[data-highlighted] {\n background-color: var(--pharos-color-neutral-100);\n color: var(--pharos-color-neutral-900);\n}\n\n.item[data-disabled] {\n color: var(--pharos-color-neutral-500);\n opacity: 0.6;\n pointer-events: none;\n}\n\n/* Icons composed inside the row inherit the row colour and size to the\n * text cap height, matching the IconButton `> svg` convention. */\n.item > svg {\n width: 1rem;\n height: 1rem;\n flex-shrink: 0;\n}\n\n/* === Destructive tone ===\n * shadcn ships a `destructive` variant on DropdownMenuItem; Alexandria\n * has several delete / remove actions (career-ladder delete, lesson\n * delete, RemoveBox) that map onto it. The error foreground darkens on\n * highlight the same way the destructive Button does. */\n.itemDestructive {\n color: var(--pharos-color-semantic-error-fg);\n}\n\n.itemDestructive[data-highlighted] {\n background-color: var(--pharos-color-semantic-error-bg);\n color: var(--pharos-color-semantic-error-fg-hover);\n}\n\n/* === Separator (Menu.Separator) === */\n.separator {\n height: 1px;\n margin: var(--pharos-spacing-1) calc(var(--pharos-spacing-1) * -1);\n background-color: var(--pharos-color-neutral-200);\n}\n\n/* === Group label (Menu.GroupLabel) === */\n.label {\n padding: var(--pharos-spacing-1-5) var(--pharos-spacing-2);\n font-size: var(--pharos-font-size-sm);\n font-weight: var(--pharos-font-weight-semibold);\n color: var(--pharos-color-neutral-500);\n}\n","import type { ComponentProps } from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport { cn } from '../lib/cn';\nimport styles from './DropdownMenu.module.css';\n\n/**\n * Action menu anchored to a trigger (Decision D15). Wraps Base UI's\n * `Menu.*` parts, which implement the ARIA APG **menu button** pattern:\n * `role=\"menu\"` / `menuitem`, roving focus, arrow-key navigation,\n * typeahead, and Escape-to-close with focus return to the trigger.\n *\n * This is deliberately distinct from a future `Popover` atom. A\n * DropdownMenu is for **commands** (the menu-button contract); free-form\n * anchored content (forms, navigation links) belongs in a Popover (the\n * disclosure/dialog contract). 7 of 8 surveyed design systems separate\n * the two for exactly this reason — putting `role=\"menu\"` around\n * arbitrary content breaks screen-reader expectations. See\n * `NAMING-decisions.md` § DropdownMenu.\n *\n * Naming follows shadcn (`DropdownMenu*`, not Base UI's `Menu`). The\n * compound surface collapses Base UI's `Portal` + `Positioner` + `Popup`\n * into a single `DropdownMenuContent` with `side` / `align` /\n * `sideOffset` props — the same ergonomics shadcn exposes over Radix.\n *\n * v1 ships the parts the known consumers need (kebab row menus, the\n * MobileHeader user actions): Trigger, Content, Item, Separator, Label,\n * Group. `CheckboxItem`, `RadioItem`, submenus and `LinkItem` are\n * deliberately deferred — no Alexandria call-site exercises them, and the\n * compound API makes them additive without a breaking change.\n *\n * @example\n * <DropdownMenu>\n * <DropdownMenuTrigger render={<IconButton aria-label=\"Options\"><MoreIcon /></IconButton>} />\n * <DropdownMenuContent>\n * <DropdownMenuItem onClick={onRename}>Rename</DropdownMenuItem>\n * <DropdownMenuSeparator />\n * <DropdownMenuItem variant=\"destructive\" onClick={onDelete}>Delete</DropdownMenuItem>\n * </DropdownMenuContent>\n * </DropdownMenu>\n */\nexport type DropdownMenuProps = ComponentProps<typeof Menu.Root>;\n\nexport function DropdownMenu(props: DropdownMenuProps) {\n return <Menu.Root {...props} />;\n}\n\nDropdownMenu.displayName = 'DropdownMenu';\n\n/**\n * The element that opens the menu. Renders a native `<button>` by\n * default; pass `render` to compose any control while keeping the menu\n * wiring — the dominant case is the kebab `IconButton` (D13):\n * `<DropdownMenuTrigger render={<IconButton aria-label=\"Options\"><MoreIcon /></IconButton>} />`.\n */\nexport type DropdownMenuTriggerProps = ComponentProps<typeof Menu.Trigger>;\n\nexport function DropdownMenuTrigger(props: DropdownMenuTriggerProps) {\n return <Menu.Trigger {...props} />;\n}\n\nDropdownMenuTrigger.displayName = 'DropdownMenuTrigger';\n\n/**\n * The floating surface. Collapses Base UI's `Portal` + `Positioner` +\n * `Popup` so the consumer positions the menu with the three props that\n * actually matter at the call site. Defaults match the common dropdown:\n * opens below the trigger, aligned to its start edge, 8px away.\n */\nexport interface DropdownMenuContentProps extends ComponentProps<typeof Menu.Popup> {\n /**\n * Which side of the trigger the menu opens on.\n * @default 'bottom'\n */\n side?: ComponentProps<typeof Menu.Positioner>['side'];\n /**\n * Alignment of the menu relative to the trigger along the chosen side.\n * @default 'start'\n */\n align?: ComponentProps<typeof Menu.Positioner>['align'];\n /**\n * Gap in pixels between the trigger and the menu.\n * @default 8\n */\n sideOffset?: ComponentProps<typeof Menu.Positioner>['sideOffset'];\n}\n\nexport function DropdownMenuContent({\n side = 'bottom',\n align = 'start',\n sideOffset = 8,\n className,\n children,\n ...rest\n}: DropdownMenuContentProps) {\n return (\n <Menu.Portal>\n <Menu.Positioner\n className={styles.positioner}\n side={side}\n align={align}\n sideOffset={sideOffset}\n >\n <Menu.Popup\n data-pharos-slot=\"dropdown-menu-content\"\n className={cn(styles.content, className)}\n {...rest}\n >\n {children}\n </Menu.Popup>\n </Menu.Positioner>\n </Menu.Portal>\n );\n}\n\nDropdownMenuContent.displayName = 'DropdownMenuContent';\n\n/**\n * A single action row. `variant=\"destructive\"` tints the row with the\n * error foreground for delete / remove actions — the same tone the\n * destructive Button uses, and the equivalent of shadcn's destructive\n * item variant. By default a click closes the menu (`closeOnClick`);\n * pass `closeOnClick={false}` for rows that should keep it open.\n */\nexport interface DropdownMenuItemProps extends ComponentProps<typeof Menu.Item> {\n /**\n * Visual tone of the row.\n * @default 'default'\n */\n variant?: 'default' | 'destructive';\n}\n\nexport function DropdownMenuItem({\n variant = 'default',\n className,\n ...rest\n}: DropdownMenuItemProps) {\n return (\n <Menu.Item\n data-pharos-variant={variant}\n className={cn(styles.item, variant === 'destructive' && styles.itemDestructive, className)}\n {...rest}\n />\n );\n}\n\nDropdownMenuItem.displayName = 'DropdownMenuItem';\n\n/** A thin divider between groups of items. */\nexport type DropdownMenuSeparatorProps = ComponentProps<typeof Menu.Separator>;\n\nexport function DropdownMenuSeparator({ className, ...rest }: DropdownMenuSeparatorProps) {\n return (\n <Menu.Separator\n data-pharos-slot=\"dropdown-menu-separator\"\n className={cn(styles.separator, className)}\n {...rest}\n />\n );\n}\n\nDropdownMenuSeparator.displayName = 'DropdownMenuSeparator';\n\n/**\n * A non-interactive heading for a group of items. Renders Base UI's\n * `Menu.GroupLabel`, which wires `aria-labelledby` on the surrounding\n * `DropdownMenuGroup` — so use the two together when a section needs an\n * accessible name.\n */\nexport type DropdownMenuLabelProps = ComponentProps<typeof Menu.GroupLabel>;\n\nexport function DropdownMenuLabel({ className, ...rest }: DropdownMenuLabelProps) {\n return (\n <Menu.GroupLabel\n data-pharos-slot=\"dropdown-menu-label\"\n className={cn(styles.label, className)}\n {...rest}\n />\n );\n}\n\nDropdownMenuLabel.displayName = 'DropdownMenuLabel';\n\n/**\n * Groups related items under an optional `DropdownMenuLabel` for\n * accessible sectioning (`role=\"group\"`).\n */\nexport type DropdownMenuGroupProps = ComponentProps<typeof Menu.Group>;\n\nexport function DropdownMenuGroup(props: DropdownMenuGroupProps) {\n return <Menu.Group data-pharos-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nDropdownMenuGroup.displayName = 'DropdownMenuGroup';\n","/* Popover.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. Styles the floating surface (`.content`), the dialog\n * labelling parts (`.title`, `.description`), and the open/close transition\n * driven by Base UI's `data-*` state attributes.\n *\n * Unlike DropdownMenu, a Popover carries arbitrary content rather than a\n * list of rows, so the surface uses a more generous block padding and a\n * `max-width` to keep prose readable. The `.content` chrome (white surface,\n * subtle border, large shadow, fade-and-scale motion) is intentionally\n * identical to DropdownMenu — both are anchored overlays and should read as\n * the same surface family.\n *\n * z-index lives on `.positioner` (see below). Base UI portals the popover to\n * the end of `<body>`; that floats it above in-flow content, but not above a\n * host-app overlay (drawer/modal) with its own z-index — and a z-index on the\n * Popup is trapped by the Positioner's transform stacking context. The\n * Positioner therefore carries `z-index: var(--pharos-z-index-popover, …)`,\n * the shared scale Combobox / Select / DropdownMenu all use now. */\n\n/* === Positioner (Popover.Positioner) ===\n * z-index lives here, not on `.content` (the Popup). Base UI's Positioner\n * anchors with `transform`, which creates a stacking context — so a z-index\n * on the inner Popup is trapped inside it and cannot rise above a host-app\n * overlay (e.g. a drawer with its own z-index). The z-index must sit on the\n * Positioner itself.\n *\n * Sourced from `--pharos-z-index-popover` (no hardcoded value — NON-NEGOTIABLE\n * rule 1). The fallback suits apps with no high z-index scale; an app whose\n * own overlays sit higher (drawers, modals) raises every Pharos popup at once\n * by overriding `--pharos-z-index-popover` in `:root`. */\n.positioner {\n z-index: var(--pharos-z-index-popover, 1000);\n}\n\n/* === Floating surface (Popover.Popup) === */\n.content {\n box-sizing: border-box;\n max-width: 20rem;\n padding: var(--pharos-spacing-4);\n\n background-color: var(--pharos-color-base-white);\n border: 1px solid var(--pharos-color-neutral-200);\n border-radius: var(--pharos-radius-md);\n box-shadow: var(--pharos-shadow-lg);\n\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n color: var(--pharos-color-neutral-900);\n\n outline: none;\n\n /* Open/close transition keyed off Base UI's `data-starting-style` /\n * `data-ending-style`. The popup fades and lifts a few pixels from the\n * anchored side — the same motion DropdownMenu / shadcn / Radix ship. */\n transform-origin: var(--transform-origin);\n transition-property: opacity, transform;\n transition-duration: var(--pharos-duration-fast);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.content[data-starting-style],\n.content[data-ending-style] {\n opacity: 0;\n transform: scale(0.96);\n}\n\n/* === Title (Popover.Title) ===\n * The accessible name. Renders as <h2> but is sized as body-strong, not as\n * a document heading — a popover title is a label, not page structure. */\n.title {\n margin: 0;\n font-size: var(--pharos-font-size-base);\n font-weight: var(--pharos-font-weight-semibold);\n line-height: 1.4;\n color: var(--pharos-color-neutral-900);\n}\n\n/* === Description (Popover.Description) ===\n * Supporting copy under the title. Sits a hair below the title with the\n * muted foreground used for secondary text across the system. */\n.description {\n margin: var(--pharos-spacing-1) 0 0;\n font-size: var(--pharos-font-size-sm);\n line-height: 1.5;\n color: var(--pharos-color-neutral-600);\n}\n","import type { ComponentProps } from 'react';\nimport { Popover as BasePopover } from '@base-ui/react/popover';\nimport { cn } from '../lib/cn';\nimport styles from './Popover.module.css';\n\n/**\n * Free-form content anchored to a trigger (Decision D16). Wraps Base UI's\n * `Popover.*` parts, which implement the ARIA **dialog** pattern in a\n * **non-modal** disclosure: `role=\"dialog\"` on the popup, focus moves into\n * it on open and returns to the trigger on close, Escape and outside-click\n * dismiss it — but focus is NOT trapped and the rest of the page stays\n * interactive.\n *\n * This is deliberately distinct from the `DropdownMenu` atom (D15). A\n * DropdownMenu is for **commands** (the menu-button contract: `role=\"menu\"`\n * / `menuitem`, roving focus, typeahead); a Popover holds **arbitrary\n * content** — forms, navigation, descriptive panels. 7 of 8 surveyed design\n * systems separate the two for exactly this reason: wrapping `role=\"menu\"`\n * around free-form content breaks screen-reader expectations. The two atoms\n * wrap different Base UI primitives (`Menu` vs `Popover`) — they do not\n * share machinery. See `NAMING-decisions.md` § Popover.\n *\n * Naming follows shadcn (`Popover*`). The compound surface collapses Base\n * UI's `Portal` + `Positioner` + `Popup` into a single `PopoverContent`\n * with `side` / `align` / `sideOffset` / `alignOffset` props — the same\n * ergonomics shadcn exposes over Radix, mirroring the Pharos DropdownMenu.\n *\n * v1 ships the parts the known consumers need (the MobileHeader user panel,\n * the \"add users\" / \"add skills\" panels): Trigger, Content, Close, Title,\n * Description. `Arrow`, an `Anchor` (anchoring to an element other than the\n * trigger — the future Combobox will want it) and a modal `Backdrop` are\n * deliberately deferred; the compound API makes them additive without a\n * breaking change.\n *\n * @example\n * <Popover>\n * <PopoverTrigger render={<IconButton aria-label=\"Account\"><Avatar … /></IconButton>} />\n * <PopoverContent>\n * <PopoverTitle>Signed in as Ada</PopoverTitle>\n * <PopoverDescription>ada@example.com</PopoverDescription>\n * <button onClick={onSignOut}>Sign out</button>\n * </PopoverContent>\n * </Popover>\n */\nexport type PopoverProps = ComponentProps<typeof BasePopover.Root>;\n\nexport function Popover(props: PopoverProps) {\n return <BasePopover.Root {...props} />;\n}\n\nPopover.displayName = 'Popover';\n\n/**\n * The element that opens the popover. Renders a native `<button>` by\n * default; pass `render` to compose any control while keeping the popover\n * wiring — e.g. the MobileHeader avatar:\n * `<PopoverTrigger render={<IconButton aria-label=\"Account\">…</IconButton>} />`.\n */\nexport type PopoverTriggerProps = ComponentProps<typeof BasePopover.Trigger>;\n\nexport function PopoverTrigger(props: PopoverTriggerProps) {\n return <BasePopover.Trigger {...props} />;\n}\n\nPopoverTrigger.displayName = 'PopoverTrigger';\n\n/**\n * The floating surface. Collapses Base UI's `Portal` + `Positioner` +\n * `Popup` so the consumer positions the popover with the four props that\n * actually matter at the call site. Defaults match the common popover:\n * opens below the trigger, centred on it, 8px away.\n *\n * `align` defaults to `center` (not `start` like DropdownMenu): a menu opens\n * flush to the trigger's start edge, but a popover carries arbitrary content\n * and reads better centred — the shadcn / Radix / Base UI default.\n */\nexport interface PopoverContentProps extends ComponentProps<typeof BasePopover.Popup> {\n /**\n * Which side of the trigger the popover opens on.\n * @default 'bottom'\n */\n side?: ComponentProps<typeof BasePopover.Positioner>['side'];\n /**\n * Alignment of the popover relative to the trigger along the chosen side.\n * @default 'center'\n */\n align?: ComponentProps<typeof BasePopover.Positioner>['align'];\n /**\n * Gap in pixels between the trigger and the popover.\n * @default 8\n */\n sideOffset?: ComponentProps<typeof BasePopover.Positioner>['sideOffset'];\n /**\n * Offset in pixels along the alignment axis.\n * @default 0\n */\n alignOffset?: ComponentProps<typeof BasePopover.Positioner>['alignOffset'];\n}\n\nexport function PopoverContent({\n side = 'bottom',\n align = 'center',\n sideOffset = 8,\n alignOffset = 0,\n className,\n children,\n ...rest\n}: PopoverContentProps) {\n return (\n <BasePopover.Portal>\n <BasePopover.Positioner\n className={styles.positioner}\n side={side}\n align={align}\n sideOffset={sideOffset}\n alignOffset={alignOffset}\n >\n <BasePopover.Popup\n data-pharos-slot=\"popover-content\"\n className={cn(styles.content, className)}\n {...rest}\n >\n {children}\n </BasePopover.Popup>\n </BasePopover.Positioner>\n </BasePopover.Portal>\n );\n}\n\nPopoverContent.displayName = 'PopoverContent';\n\n/**\n * An accessible name for the popover. Renders Base UI's `Popover.Title`,\n * which wires `aria-labelledby` on the popup — the dialog labelling that\n * distinguishes a Popover from a Menu. Renders as a `<h2>` by default; pass\n * `render` to change the element.\n */\nexport type PopoverTitleProps = ComponentProps<typeof BasePopover.Title>;\n\nexport function PopoverTitle({ className, ...rest }: PopoverTitleProps) {\n return (\n <BasePopover.Title\n data-pharos-slot=\"popover-title\"\n className={cn(styles.title, className)}\n {...rest}\n />\n );\n}\n\nPopoverTitle.displayName = 'PopoverTitle';\n\n/**\n * A supporting description for the popover. Renders Base UI's\n * `Popover.Description`, which wires `aria-describedby` on the popup.\n * Renders as a `<p>` by default.\n */\nexport type PopoverDescriptionProps = ComponentProps<typeof BasePopover.Description>;\n\nexport function PopoverDescription({ className, ...rest }: PopoverDescriptionProps) {\n return (\n <BasePopover.Description\n data-pharos-slot=\"popover-description\"\n className={cn(styles.description, className)}\n {...rest}\n />\n );\n}\n\nPopoverDescription.displayName = 'PopoverDescription';\n\n/**\n * A button that closes the popover. Renders Base UI's `Popover.Close` (a\n * native `<button>`); compose any control via `render`. Unlike a menu — whose\n * items close it on selection — a popover holds content that stays open until\n * explicitly dismissed, so an in-popup close affordance is the common case.\n */\nexport type PopoverCloseProps = ComponentProps<typeof BasePopover.Close>;\n\nexport function PopoverClose(props: PopoverCloseProps) {\n return <BasePopover.Close data-pharos-slot=\"popover-close\" {...props} />;\n}\n\nPopoverClose.displayName = 'PopoverClose';\n","/* Select.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. Styles the trigger (which shares the Input chrome so a\n * Select sits flush next to an Input on the same form row), the floating\n * listbox surface, and the option rows.\n *\n * The trigger border tone is `neutral-500` for the same WCAG 1.4.11 reason\n * Input uses it (D10): a Select trigger is an interactive control boundary,\n * so it must clear the 3:1 non-text contrast bar. The popup surface chrome\n * (white, subtle `neutral-200` border, large shadow, fade-and-scale motion)\n * is deliberately identical to DropdownMenu and Popover — every anchored\n * overlay reads as the same surface family.\n *\n * Anchor-width: the popup takes `width: var(--anchor-width)` so the listbox\n * matches the trigger that opened it exactly — long options truncate (see\n * `.itemText`) rather than ballooning the panel wider than the field, the\n * Ant / Mantine / MUI / shadcn-combobox default. Base UI sets `--anchor-width`\n * on the Positioner; it cascades to the Popup. This is the affordance that\n * was missing from Popover v1 and forced the picker call-sites to be deferred\n * to this atom. Consumers needing a wider panel override `width` via\n * `className` per call-site.\n *\n * z-index lives on `.positioner` (see below). Base UI portals the popup to\n * the end of `<body>`; that floats it above in-flow content, but not above a\n * host-app overlay (drawer/modal) with its own z-index — and a z-index on the\n * Popup is trapped by the Positioner's transform stacking context. The\n * Positioner therefore carries `z-index: var(--pharos-z-index-popover, …)`,\n * the shared scale Combobox / DropdownMenu / Popover all use now. */\n\n/* === Trigger (Select.Trigger) ===\n * Same chrome as Input: white fill, `neutral-500` resting border, brand\n * focus ring. Laid out as a row so the value grows and the chevron pins to\n * the trailing edge. */\n.trigger {\n appearance: none;\n -webkit-appearance: none;\n\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--pharos-spacing-2);\n width: 100%;\n margin: 0;\n\n border: 1px solid var(--pharos-color-neutral-500);\n border-radius: var(--pharos-radius-md);\n background-color: var(--pharos-color-base-white);\n color: var(--pharos-color-neutral-900);\n\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n font-weight: var(--pharos-font-weight-regular);\n line-height: 1.25;\n text-align: start;\n cursor: default;\n\n transition-property: border-color, box-shadow;\n transition-duration: var(--pharos-duration-normal);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.trigger:focus-visible {\n outline: none;\n border-color: var(--pharos-color-primary-600);\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-primary-600);\n}\n\n.trigger:disabled,\n.trigger[data-disabled] {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.trigger[aria-invalid='true'] {\n border-color: var(--pharos-color-semantic-error-fg);\n}\n\n.trigger[aria-invalid='true']:focus-visible {\n border-color: var(--pharos-color-semantic-error-fg);\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-semantic-error-fg);\n}\n\n/* === Sizes ===\n * Heights match the Input/Button grid exactly so a Select aligns with the\n * other controls on a row. */\n.sizeSm {\n height: var(--pharos-spacing-8);\n padding: 0 var(--pharos-spacing-3);\n font-size: var(--pharos-font-size-sm);\n}\n\n.sizeMd {\n height: var(--pharos-spacing-10);\n padding: 0 var(--pharos-spacing-4);\n font-size: var(--pharos-font-size-sm);\n}\n\n.sizeLg {\n height: var(--pharos-spacing-12);\n padding: 0 var(--pharos-spacing-6);\n font-size: var(--pharos-font-size-base);\n}\n\n/* === Value (Select.Value) ===\n * Grows to fill the trigger and truncates rather than wrapping. The\n * placeholder state is tinted with the same muted tone as Input's. */\n.value {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.value[data-placeholder] {\n color: var(--pharos-color-neutral-500);\n}\n\n/* === Chevron (Select.Icon) ===\n * Inline SVG sized to the control. Rotates when the popup is open. */\n.icon {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n color: var(--pharos-color-neutral-500);\n transition: transform var(--pharos-duration-fast) var(--pharos-easing-out);\n}\n\n.icon svg {\n width: 1rem;\n height: 1rem;\n}\n\n.icon[data-popup-open] {\n transform: rotate(180deg);\n}\n\n/* === Positioner (Select.Positioner) ===\n * z-index lives here, not on `.content` (the Popup). Base UI's Positioner\n * anchors with `transform`, which creates a stacking context — so a z-index\n * on the inner Popup is trapped inside it and cannot rise above a host-app\n * overlay (e.g. a drawer with its own z-index). The z-index must sit on the\n * Positioner itself.\n *\n * Sourced from `--pharos-z-index-popover` (no hardcoded value — NON-NEGOTIABLE\n * rule 1). The fallback suits apps with no high z-index scale; an app whose\n * own overlays sit higher (drawers, modals) raises every Pharos popup at once\n * by overriding `--pharos-z-index-popover` in `:root`. */\n.positioner {\n z-index: var(--pharos-z-index-popover, 1000);\n}\n\n/* === Floating surface (Select.Popup) === */\n.content {\n box-sizing: border-box;\n width: var(--anchor-width);\n max-height: var(--available-height);\n overflow-y: auto;\n padding: var(--pharos-spacing-1);\n\n background-color: var(--pharos-color-base-white);\n border: 1px solid var(--pharos-color-neutral-200);\n border-radius: var(--pharos-radius-md);\n box-shadow: var(--pharos-shadow-lg);\n\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n color: var(--pharos-color-neutral-900);\n\n outline: none;\n\n transform-origin: var(--transform-origin);\n transition-property: opacity, transform;\n transition-duration: var(--pharos-duration-fast);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.content[data-starting-style],\n.content[data-ending-style] {\n opacity: 0;\n transform: scale(0.96);\n}\n\n/* === Option (Select.Item) ===\n * Row layout with the label growing and the selected-state check pinned to\n * the trailing edge. Highlight tracks Base UI's `data-highlighted` (keyboard\n * / pointer) the same way DropdownMenu items do. */\n.item {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--pharos-spacing-2);\n\n padding: var(--pharos-spacing-2) var(--pharos-spacing-3);\n border-radius: var(--pharos-radius-sm);\n\n color: var(--pharos-color-neutral-900);\n cursor: default;\n user-select: none;\n outline: none;\n scroll-margin: var(--pharos-spacing-1);\n}\n\n.item[data-highlighted] {\n background-color: var(--pharos-color-neutral-100);\n}\n\n.item[data-disabled] {\n color: var(--pharos-color-neutral-400);\n pointer-events: none;\n}\n\n.itemText {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.itemIndicator {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n color: var(--pharos-color-primary-600);\n}\n\n.itemIndicator svg {\n width: 1rem;\n height: 1rem;\n}\n\n/* === Group + label (Select.Group / Select.GroupLabel) === */\n.label {\n padding: var(--pharos-spacing-2) var(--pharos-spacing-3) var(--pharos-spacing-1);\n font-size: var(--pharos-font-size-xs);\n font-weight: var(--pharos-font-weight-semibold);\n color: var(--pharos-color-neutral-600);\n}\n\n/* === Separator (Select.Separator) === */\n.separator {\n height: 1px;\n margin: var(--pharos-spacing-1) 0;\n background-color: var(--pharos-color-neutral-200);\n}\n","import type { ComponentProps } from 'react';\nimport { Select as BaseSelect } from '@base-ui/react/select';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Select.module.css';\n\n/**\n * Single- or multi-select from a **known set of options** (Decision D17).\n * Wraps Base UI's `Select.*` parts, which implement the ARIA **listbox**\n * pattern: a trigger button opens a popup with `role=\"listbox\"` whose rows\n * are `role=\"option\"`, with roving focus, typeahead and Escape-to-close.\n *\n * This is deliberately distinct from the `Combobox` atom (also D17). A\n * Select has **no text input** — the user picks from a closed list. When the\n * option set is large enough to need filtering, or the value can be typed,\n * that is a Combobox (the APG combobox pattern). shadcn, Base UI and ARIA APG\n * all separate the two under exactly these names, which is why Pharos ships\n * two atoms rather than one component with a `searchable` axis. See\n * `NAMING-decisions.md` § Select.\n *\n * Multi-select is the `multiple` axis on the root (not a separate atom),\n * matching Base UI / shadcn / React Aria: pass `multiple` and `value`\n * becomes an array. Single-select closes on pick; multi-select keeps the\n * popup open so selections accumulate — Base UI derives this from `multiple`\n * and Pharos does not override it.\n *\n * Naming follows shadcn (`Select*`). `SelectTrigger` folds Base UI's\n * `Trigger` + the chevron `Icon`; `SelectContent` collapses `Portal` +\n * `Positioner` + `Popup` + `List` with `side` / `align` / `sideOffset`\n * props; `SelectItem` folds `Item` + `ItemText` + the selected-state\n * `ItemIndicator` — the same ergonomics shadcn exposes over Radix. The popup\n * takes `width: var(--anchor-width)` so it matches the trigger width exactly\n * (long options truncate; override `width` via `className` for a wider panel).\n *\n * Following the form-control composition rule (Escuela 1, D11), the atom owns\n * no label / helper / error message — the consumer composes those, and the\n * error state is conveyed through the standard `aria-invalid` attribute on\n * the trigger (the CSS reacts to `[aria-invalid=\"true\"]`).\n *\n * **Always pass `items` (a value→label map) when the value differs from its\n * label.** Base UI's `SelectValue` resolves the trigger label from the root's\n * `items` prop, NOT from the `SelectItem` children — those are the listbox\n * label + typeahead text. This holds for interactive selection too: without\n * `items` the trigger shows the serialized raw value (the enum\n * `\"organization\"`, not `\"Organization\"`). The idiomatic, drift-free pattern\n * is a single options source that feeds both `items` and the rendered items.\n *\n * @example\n * const VISIBILITY = { private: 'Private', organization: 'Organization', public: 'Public' };\n *\n * <Select value={value} onValueChange={setValue} items={VISIBILITY}>\n * <SelectTrigger aria-label=\"Visibility\">\n * <SelectValue placeholder=\"Select visibility\" />\n * </SelectTrigger>\n * <SelectContent>\n * {Object.entries(VISIBILITY).map(([v, label]) => (\n * <SelectItem key={v} value={v}>\n * {label}\n * </SelectItem>\n * ))}\n * </SelectContent>\n * </Select>\n */\nexport type SelectProps<\n Value,\n Multiple extends boolean | undefined = false,\n> = BaseSelect.Root.Props<Value, Multiple>;\n\nexport function Select<Value, Multiple extends boolean | undefined = false>(\n props: SelectProps<Value, Multiple>,\n) {\n return <BaseSelect.Root {...props} />;\n}\n\nSelect.displayName = 'Select';\n\n/**\n * A downward chevron, inlined as an SVG so the published bundle pulls no icon\n * dependency (Pharos does not bundle `lucide-react`; D4 keeps Lucide as the\n * canonical source for the path data). Rotates 180° when the popup opens via\n * the `[data-popup-open]` selector on the wrapping `SelectIcon`.\n */\nfunction ChevronDownIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden=\"true\">\n <path d=\"m6 9 6 6 6-6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n\n/** A check mark for the selected option, inlined for the same reason. */\nfunction CheckIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden=\"true\">\n <path d=\"M20 6 9 17l-5-5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n\nconst triggerVariants = cva(styles.trigger, {\n variants: {\n size: {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n },\n },\n defaultVariants: {\n size: 'md',\n },\n});\n\ntype TriggerVariantProps = VariantProps<typeof triggerVariants>;\n\n/**\n * The button that opens the listbox. Shares the Input chrome (border tone,\n * focus ring, size grid) so a Select sits flush next to an Input on a form\n * row. Folds the trailing chevron, and lays children out before it — the\n * common case is a single `<SelectValue />`. The `size` axis (sm / md / lg)\n * matches the Input and Button heights.\n *\n * Give it an accessible name with `aria-label` (or wire a `<label>` /\n * `aria-labelledby`) — the trigger only shows the chosen value, not a label.\n */\nexport interface SelectTriggerProps\n extends ComponentProps<typeof BaseSelect.Trigger>, TriggerVariantProps {}\n\nexport function SelectTrigger({ size, className, children, ...rest }: SelectTriggerProps) {\n const resolvedSize = size ?? 'md';\n return (\n <BaseSelect.Trigger\n data-pharos-slot=\"select-trigger\"\n data-pharos-size={resolvedSize}\n className={cn(triggerVariants({ size: resolvedSize }), className)}\n {...rest}\n >\n {children}\n <BaseSelect.Icon className={styles.icon}>\n <ChevronDownIcon />\n </BaseSelect.Icon>\n </BaseSelect.Trigger>\n );\n}\n\nSelectTrigger.displayName = 'SelectTrigger';\n\n/**\n * Renders the selected option's label inside the trigger, or the\n * `placeholder` when nothing is selected. Wraps Base UI's `Select.Value`;\n * pass a render function as `children` to format the value yourself.\n */\nexport type SelectValueProps = ComponentProps<typeof BaseSelect.Value>;\n\nexport function SelectValue({ className, ...rest }: SelectValueProps) {\n return (\n <BaseSelect.Value\n data-pharos-slot=\"select-value\"\n className={cn(styles.value, className)}\n {...rest}\n />\n );\n}\n\nSelectValue.displayName = 'SelectValue';\n\n/**\n * The floating listbox. Collapses Base UI's `Portal` + `Positioner` +\n * `Popup` + `List` so the consumer positions it with the props that matter\n * at the call site. Defaults match the common select: opens below the\n * trigger, aligned to its start edge, 8px away, and matches the trigger width\n * (`width: var(--anchor-width)`).\n *\n * `alignItemWithTrigger` is forced to `false`. Base UI's `Select.Positioner`\n * defaults it to `true` — the native-`<select>` / macOS behaviour that floats\n * the popup *over* the trigger to put the selected item under the pointer,\n * and which silently overrides `side` / `align` / `sideOffset`\n * (`renderedSide` becomes `'none'`). Pharos anchors the listbox **below the\n * trigger** instead (D17 refinement): it is the dominant web convention\n * (shadcn `position=\"popper\"`, Ant, Mantine), it keeps the trigger box\n * visible, and — decisively — it makes `Select` consistent with `Combobox`,\n * whose Base UI primitive has no item-alignment mode and always anchors\n * adjacent. With it off, the `side` / `align` / `sideOffset` props below\n * actually take effect.\n */\nexport interface SelectContentProps extends ComponentProps<typeof BaseSelect.Popup> {\n /**\n * Which side of the trigger the listbox opens on.\n * @default 'bottom'\n */\n side?: ComponentProps<typeof BaseSelect.Positioner>['side'];\n /**\n * Alignment of the listbox relative to the trigger along the chosen side.\n * @default 'start'\n */\n align?: ComponentProps<typeof BaseSelect.Positioner>['align'];\n /**\n * Gap in pixels between the trigger and the listbox.\n * @default 8\n */\n sideOffset?: ComponentProps<typeof BaseSelect.Positioner>['sideOffset'];\n /**\n * Offset in pixels along the alignment axis.\n * @default 0\n */\n alignOffset?: ComponentProps<typeof BaseSelect.Positioner>['alignOffset'];\n}\n\nexport function SelectContent({\n side = 'bottom',\n align = 'start',\n sideOffset = 8,\n alignOffset = 0,\n className,\n children,\n ...rest\n}: SelectContentProps) {\n return (\n <BaseSelect.Portal>\n <BaseSelect.Positioner\n className={styles.positioner}\n alignItemWithTrigger={false}\n side={side}\n align={align}\n sideOffset={sideOffset}\n alignOffset={alignOffset}\n >\n <BaseSelect.Popup\n data-pharos-slot=\"select-content\"\n className={cn(styles.content, className)}\n {...rest}\n >\n <BaseSelect.List>{children}</BaseSelect.List>\n </BaseSelect.Popup>\n </BaseSelect.Positioner>\n </BaseSelect.Portal>\n );\n}\n\nSelectContent.displayName = 'SelectContent';\n\n/**\n * A single option. Folds Base UI's `Item` + `ItemText` (the label, also used\n * as the value shown in the trigger) + `ItemIndicator` (the check shown when\n * the option is selected). The `value` prop is required by Base UI and is\n * what `onValueChange` reports.\n */\nexport interface SelectItemProps extends ComponentProps<typeof BaseSelect.Item> {}\n\nexport function SelectItem({ className, children, ...rest }: SelectItemProps) {\n return (\n <BaseSelect.Item\n data-pharos-slot=\"select-item\"\n className={cn(styles.item, className)}\n {...rest}\n >\n <BaseSelect.ItemText className={styles.itemText}>{children}</BaseSelect.ItemText>\n <BaseSelect.ItemIndicator className={styles.itemIndicator}>\n <CheckIcon />\n </BaseSelect.ItemIndicator>\n </BaseSelect.Item>\n );\n}\n\nSelectItem.displayName = 'SelectItem';\n\n/**\n * Groups related options under an optional `SelectLabel` for accessible\n * sectioning (`role=\"group\"`).\n */\nexport type SelectGroupProps = ComponentProps<typeof BaseSelect.Group>;\n\nexport function SelectGroup(props: SelectGroupProps) {\n return <BaseSelect.Group data-pharos-slot=\"select-group\" {...props} />;\n}\n\nSelectGroup.displayName = 'SelectGroup';\n\n/**\n * A non-interactive heading for a `SelectGroup`. Renders Base UI's\n * `Select.GroupLabel`, which wires `aria-labelledby` on the surrounding\n * group — use the two together when a section needs an accessible name.\n */\nexport type SelectLabelProps = ComponentProps<typeof BaseSelect.GroupLabel>;\n\nexport function SelectLabel({ className, ...rest }: SelectLabelProps) {\n return (\n <BaseSelect.GroupLabel\n data-pharos-slot=\"select-label\"\n className={cn(styles.label, className)}\n {...rest}\n />\n );\n}\n\nSelectLabel.displayName = 'SelectLabel';\n\n/**\n * A thin **decorative** divider between groups of options.\n *\n * Rendered `aria-hidden` with `role=\"none\"`. Base UI's `Select.Separator`\n * re-exports the generic `Separator`, which emits `role=\"separator\"`. A\n * `separator` is **not** a permitted child of the `listbox` role (its content\n * model allows only `option` and `group`), so leaving it in the accessibility\n * tree fails axe's `aria-required-children` (critical) on a grouped Select.\n * The grouping is already conveyed semantically by `SelectGroup` (`role=\n * \"group\"`) + `SelectLabel` (`aria-labelledby`), so the divider is purely\n * visual — hiding it from the tree is the correct, drift-free fix (same as\n * Radix / shadcn, whose select separators are `aria-hidden`).\n */\nexport type SelectSeparatorProps = ComponentProps<typeof BaseSelect.Separator>;\n\nexport function SelectSeparator({ className, ...rest }: SelectSeparatorProps) {\n return (\n <BaseSelect.Separator\n data-pharos-slot=\"select-separator\"\n role=\"none\"\n aria-hidden\n className={cn(styles.separator, className)}\n {...rest}\n />\n );\n}\n\nSelectSeparator.displayName = 'SelectSeparator';\n","/* Combobox.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. Styles the bordered control (single-select input group\n * and multi-select chips container share the `.control` chrome), the chips,\n * the floating listbox surface, and the option rows.\n *\n * The control border tone is `neutral-500` for the same WCAG 1.4.11 reason\n * Input and Select use it (D10). The popup surface chrome and the option /\n * group / separator styling mirror Select exactly — Select and Combobox are\n * the two halves of the selection family and read as one system.\n *\n * Anchor-width: the popup takes `width: var(--anchor-width)` so the listbox\n * matches the control exactly — long options truncate (see `.itemText`)\n * rather than ballooning wider than the field (the Ant / Mantine / MUI /\n * shadcn-combobox default). `max-height: var(--available-height)` clamps it\n * to the viewport. Both vars come from Base UI's Positioner. Consumers\n * needing a wider panel override `width` via `className`. */\n\n/* === Control (Combobox.InputGroup, single) and chips box (Combobox.Chips,\n * multiple) — same bordered chrome === */\n.control {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: var(--pharos-spacing-2);\n width: 100%;\n\n border: 1px solid var(--pharos-color-neutral-500);\n border-radius: var(--pharos-radius-md);\n background-color: var(--pharos-color-base-white);\n\n /* Set the Pharos sans stack on the box so everything inside it — chips,\n * the input — shares one typography. Without this the chips fall back to\n * the user-agent serif/system font (the bundle ships no global font), so\n * they read in a different typeface than the input. Same reason Input sets\n * font-family explicitly. */\n font-family: var(--pharos-font-family-sans);\n\n transition-property: border-color, box-shadow;\n transition-duration: var(--pharos-duration-normal);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n/* Lift the focus ring to the control when the inner input is focused, so the\n * whole bordered box reads as the focused control (the input itself carries\n * no border). */\n.control:focus-within {\n border-color: var(--pharos-color-primary-600);\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-primary-600);\n}\n\n.control[aria-invalid='true'],\n.control:has([aria-invalid='true']) {\n border-color: var(--pharos-color-semantic-error-fg);\n}\n\n/* The multi-select chips box wraps its chips and grows vertically. */\n.chips {\n flex-wrap: wrap;\n gap: var(--pharos-spacing-1);\n padding: var(--pharos-spacing-1) var(--pharos-spacing-2);\n}\n\n/* === Bare input (Combobox.Input) ===\n * Carries no border of its own — the surrounding `.control` / `.chips` box\n * owns the chrome. Grows to fill the row. */\n.input {\n appearance: none;\n -webkit-appearance: none;\n\n flex: 1 1 auto;\n min-width: 0;\n margin: 0;\n border: none;\n outline: none;\n background: transparent;\n color: var(--pharos-color-neutral-900);\n\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n font-weight: var(--pharos-font-weight-regular);\n line-height: 1.25;\n}\n\n.input::placeholder {\n color: var(--pharos-color-neutral-500);\n opacity: 1;\n}\n\n.input:disabled {\n cursor: not-allowed;\n}\n\n/* Standalone single-select input gets the same padding/height grid as Input\n * so it sits flush with other fields. */\n.inputSizeSm {\n height: var(--pharos-spacing-8);\n padding: 0 var(--pharos-spacing-3);\n}\n\n.inputSizeMd {\n height: var(--pharos-spacing-10);\n padding: 0 var(--pharos-spacing-4);\n}\n\n.inputSizeLg {\n height: var(--pharos-spacing-12);\n padding: 0 var(--pharos-spacing-6);\n font-size: var(--pharos-font-size-base);\n}\n\n/* Inside the chips box the input is a flexible filler — no fixed height, it\n * shares the row with the chips. */\n.chipsInput {\n height: var(--pharos-spacing-7);\n padding: 0 var(--pharos-spacing-1);\n flex: 1 1 4rem;\n}\n\n/* === Trigger (chevron) and clear button === */\n.trigger,\n.clear {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: var(--pharos-spacing-6);\n height: var(--pharos-spacing-6);\n margin-inline-end: var(--pharos-spacing-1);\n padding: 0;\n border: none;\n background: transparent;\n color: var(--pharos-color-neutral-500);\n cursor: default;\n}\n\n.trigger svg,\n.clear svg {\n width: 1rem;\n height: 1rem;\n}\n\n.trigger[data-popup-open] svg {\n transform: rotate(180deg);\n transition: transform var(--pharos-duration-fast) var(--pharos-easing-out);\n}\n\n/* === Chip (Combobox.Chip) === */\n.chip {\n display: inline-flex;\n align-items: center;\n gap: var(--pharos-spacing-1);\n padding: 0 var(--pharos-spacing-1) 0 var(--pharos-spacing-2);\n height: var(--pharos-spacing-6);\n\n border-radius: var(--pharos-radius-sm);\n background-color: var(--pharos-color-neutral-100);\n color: var(--pharos-color-neutral-900);\n font-size: var(--pharos-font-size-xs);\n}\n\n.chip[data-highlighted] {\n background-color: var(--pharos-color-neutral-200);\n}\n\n.chipRemove {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--pharos-spacing-4);\n height: var(--pharos-spacing-4);\n padding: 0;\n border: none;\n border-radius: var(--pharos-radius-sm);\n background: transparent;\n color: var(--pharos-color-neutral-500);\n cursor: default;\n}\n\n.chipRemove:hover {\n color: var(--pharos-color-neutral-900);\n}\n\n.chipRemove svg {\n width: 0.875rem;\n height: 0.875rem;\n}\n\n/* === Positioner (Combobox.Positioner) ===\n * z-index lives here, not on `.content` (the Popup). Base UI's Positioner\n * anchors with `transform`, which creates a stacking context — so a z-index\n * on the inner Popup is trapped inside it and cannot rise above a host-app\n * overlay (e.g. a drawer with its own z-index). The z-index must sit on the\n * Positioner itself.\n *\n * Sourced from `--pharos-z-index-popover` (no hardcoded value — NON-NEGOTIABLE\n * rule 1). The fallback suits apps with no high z-index scale; an app whose\n * own overlays sit higher (drawers, modals) raises every Pharos popup at once\n * by overriding `--pharos-z-index-popover` in `:root`. */\n.positioner {\n z-index: var(--pharos-z-index-popover, 1000);\n}\n\n/* === Floating surface (Combobox.Popup) — mirrors Select === */\n.content {\n box-sizing: border-box;\n width: var(--anchor-width);\n max-height: var(--available-height);\n overflow-y: auto;\n padding: var(--pharos-spacing-1);\n\n background-color: var(--pharos-color-base-white);\n border: 1px solid var(--pharos-color-neutral-200);\n border-radius: var(--pharos-radius-md);\n box-shadow: var(--pharos-shadow-lg);\n\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n color: var(--pharos-color-neutral-900);\n\n outline: none;\n\n transform-origin: var(--transform-origin);\n transition-property: opacity, transform;\n transition-duration: var(--pharos-duration-fast);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.content[data-starting-style],\n.content[data-ending-style] {\n opacity: 0;\n transform: scale(0.96);\n}\n\n/* === Option (Combobox.Item) === */\n.item {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--pharos-spacing-2);\n\n padding: var(--pharos-spacing-2) var(--pharos-spacing-3);\n border-radius: var(--pharos-radius-sm);\n\n color: var(--pharos-color-neutral-900);\n cursor: default;\n user-select: none;\n outline: none;\n scroll-margin: var(--pharos-spacing-1);\n}\n\n.item[data-highlighted] {\n background-color: var(--pharos-color-neutral-100);\n}\n\n.item[data-disabled] {\n color: var(--pharos-color-neutral-400);\n pointer-events: none;\n}\n\n.itemText {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.itemIndicator {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n color: var(--pharos-color-primary-600);\n}\n\n.itemIndicator svg {\n width: 1rem;\n height: 1rem;\n}\n\n/* === Empty state (Combobox.Empty) ===\n * Base UI keeps this element mounted even when there ARE results (it is a\n * `role=\"status\"` live region and must not be removed / `display:none`-d, or\n * screen readers miss the \"no results\" announcement) — it only nulls its\n * children. So when results exist the element is present but childless;\n * `:not(:empty)` applies the padding only when a message is actually shown,\n * collapsing the box to zero height otherwise. Without this the empty live\n * region adds a block of padding above the list and the popup looks\n * top-heavy / asymmetric against its bottom edge. */\n.empty {\n color: var(--pharos-color-neutral-500);\n text-align: center;\n}\n\n.empty:not(:empty) {\n padding: var(--pharos-spacing-3);\n}\n\n/* === Group + label + separator (mirror Select) === */\n.label {\n padding: var(--pharos-spacing-2) var(--pharos-spacing-3) var(--pharos-spacing-1);\n font-size: var(--pharos-font-size-xs);\n font-weight: var(--pharos-font-weight-semibold);\n color: var(--pharos-color-neutral-600);\n}\n\n.separator {\n height: 1px;\n margin: var(--pharos-spacing-1) 0;\n background-color: var(--pharos-color-neutral-200);\n}\n","import {\n type ComponentProps,\n type Ref,\n createContext,\n useCallback,\n useContext,\n useMemo,\n useState,\n} from 'react';\nimport { Combobox as BaseCombobox } from '@base-ui/react/combobox';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Combobox.module.css';\n\n/**\n * Single- or multi-select with a **text input that filters the options**\n * (Decision D17). Wraps Base UI's `Combobox.*` parts, which implement the\n * ARIA APG **combobox** pattern: an `<input role=\"combobox\">` with\n * `aria-expanded` / `aria-controls` pointing at a popup listbox, typeahead\n * filtering, and arrow-key navigation of the results.\n *\n * This is deliberately distinct from the `Select` atom (also D17). A Select\n * has no input — the user picks from a closed list. A Combobox is for sets\n * large enough to need filtering, or where the user expects to type. shadcn,\n * Base UI and ARIA APG separate the two; Pharos ships both rather than a\n * single component with a `searchable` axis. (Free-text entry — typing a\n * value that is not in the list — is Base UI's separate `Autocomplete`\n * primitive and is deferred; every known Alexandria call-site filters a known\n * set.) See `NAMING-decisions.md` § Combobox.\n *\n * Multi-select is the `multiple` axis on the root (not a separate atom),\n * matching Base UI / shadcn / React Aria. With `multiple`, selections render\n * as removable chips inside a `ComboboxChips` box and the popup stays open so\n * they accumulate; single-select uses a `ComboboxControl` input box and\n * closes on pick.\n *\n * Naming follows Base UI (`Combobox*`; shadcn has no canonical primitive — it\n * composes a recipe). The control chrome is split into composable parts\n * because a combobox is inherently more compositional than a select:\n * `ComboboxControl` / `ComboboxChips` own the bordered box, `ComboboxInput`\n * is the bare field, `ComboboxTrigger` the chevron. `ComboboxContent`\n * collapses `Portal` + `Positioner` + `Popup`; `ComboboxList` and\n * `ComboboxEmpty` sit inside it. The popup takes `width:\n * var(--anchor-width)` so it matches the control width exactly (long options\n * truncate) — the anchor-width affordance the picker call-sites needed.\n *\n * Per the form-control composition rule (Escuela 1, D11) the atom owns no\n * label / helper / error message; the consumer composes those and conveys\n * error via `aria-invalid`.\n *\n * @example single-select\n * <Combobox items={fruits} onValueChange={setValue}>\n * <ComboboxControl>\n * <ComboboxInput placeholder=\"Search fruit…\" />\n * <ComboboxTrigger />\n * </ComboboxControl>\n * <ComboboxContent>\n * <ComboboxEmpty>No fruit found</ComboboxEmpty>\n * <ComboboxList>\n * {(item) => (\n * <ComboboxItem key={item.value} value={item}>\n * {item.label}\n * </ComboboxItem>\n * )}\n * </ComboboxList>\n * </ComboboxContent>\n * </Combobox>\n */\nexport type ComboboxProps<\n Value,\n Multiple extends boolean | undefined = false,\n> = BaseCombobox.Root.Props<Value, Multiple>;\n\n/**\n * Lets `ComboboxChips` publish its DOM node so `ComboboxContent` can anchor\n * the floating listbox to the control box instead of the bare input.\n *\n * Why this exists: Base UI resolves the popup anchor as\n * `inputGroupElement ?? inputElement`. In single-select Pharos wraps Base UI's\n * `InputGroup` (`ComboboxControl`), so the popup anchors to the full-width\n * control. In multi-select Pharos uses `Combobox.Chips` (`ComboboxChips`) as\n * the bordered box — and `Chips` does NOT register an `inputGroupElement`, so\n * the anchor falls back to the `Input`, which floats at the end of the chip\n * row. The popup then opens narrow and shifted right. Capturing the chips box\n * here and passing it as the Positioner's `anchor` keeps the panel the full\n * control width, left-aligned, regardless of where the input sits.\n */\ninterface ComboboxAnchorContextValue {\n anchorElement: HTMLElement | null;\n setAnchorElement: (element: HTMLElement | null) => void;\n}\n\nconst ComboboxAnchorContext = createContext<ComboboxAnchorContextValue | null>(null);\n\nexport function Combobox<Value, Multiple extends boolean | undefined = false>(\n props: ComboboxProps<Value, Multiple>,\n) {\n const [anchorElement, setAnchorElement] = useState<HTMLElement | null>(null);\n const anchorContext = useMemo<ComboboxAnchorContextValue>(\n () => ({ anchorElement, setAnchorElement }),\n [anchorElement],\n );\n return (\n <ComboboxAnchorContext.Provider value={anchorContext}>\n <BaseCombobox.Root {...props} />\n </ComboboxAnchorContext.Provider>\n );\n}\n\nCombobox.displayName = 'Combobox';\n\n/** A downward chevron, inlined as an SVG (Pharos bundles no icon dependency; D4). */\nfunction ChevronDownIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden=\"true\">\n <path d=\"m6 9 6 6 6-6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n\n/** A check mark for the selected option, inlined for the same reason. */\nfunction CheckIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden=\"true\">\n <path d=\"M20 6 9 17l-5-5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n\n/** A small ✕ for clearing / removing, inlined for the same reason. */\nfunction XIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden=\"true\">\n <path d=\"M18 6 6 18M6 6l12 12\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n\n/**\n * The bordered control box for **single-select**. Wraps Base UI's\n * `Combobox.InputGroup` and carries the Input chrome (border tone, focus\n * ring). Put a `ComboboxInput` and optionally a `ComboboxTrigger` /\n * `ComboboxClear` inside it.\n */\nexport type ComboboxControlProps = ComponentProps<typeof BaseCombobox.InputGroup>;\n\nexport function ComboboxControl({ className, ...rest }: ComboboxControlProps) {\n return (\n <BaseCombobox.InputGroup\n data-pharos-slot=\"combobox-control\"\n className={cn(styles.control, className)}\n {...rest}\n />\n );\n}\n\nComboboxControl.displayName = 'ComboboxControl';\n\n/**\n * The bordered control box for **multi-select**. Wraps Base UI's\n * `Combobox.Chips`; render `ComboboxChip`s for the current selections\n * followed by a `ComboboxInput` to keep typing. Shares the `ComboboxControl`\n * chrome.\n */\nexport type ComboboxChipsProps = ComponentProps<typeof BaseCombobox.Chips>;\n\nexport function ComboboxChips({ className, ref, ...rest }: ComboboxChipsProps) {\n const anchorContext = useContext(ComboboxAnchorContext);\n // Publish the chips box as the popup anchor (see ComboboxAnchorContext)\n // while still honouring any ref the consumer forwards.\n const setRef = useCallback(\n (element: HTMLDivElement | null) => {\n anchorContext?.setAnchorElement(element);\n if (typeof ref === 'function') {\n ref(element);\n } else if (ref) {\n (ref as { current: HTMLDivElement | null }).current = element;\n }\n },\n [anchorContext, ref],\n );\n return (\n <BaseCombobox.Chips\n data-pharos-slot=\"combobox-chips\"\n className={cn(styles.control, styles.chips, className)}\n ref={setRef as Ref<HTMLDivElement>}\n {...rest}\n />\n );\n}\n\nComboboxChips.displayName = 'ComboboxChips';\n\nconst inputVariants = cva(styles.input, {\n variants: {\n size: {\n sm: styles.inputSizeSm,\n md: styles.inputSizeMd,\n lg: styles.inputSizeLg,\n },\n },\n defaultVariants: {\n size: 'md',\n },\n});\n\ntype InputVariantProps = VariantProps<typeof inputVariants>;\n\n/**\n * The text field. Wraps Base UI's `Combobox.Input` (`role=\"combobox\"`),\n * which owns the filtering. Renders bare — the surrounding `ComboboxControl`\n * / `ComboboxChips` box owns the border. The `size` axis (sm / md / lg)\n * matches Input/Select and applies when the input is used standalone in a\n * `ComboboxControl`; inside a `ComboboxChips` box pass `inset` so it fills\n * the chip row instead of imposing its own height.\n */\nexport interface ComboboxInputProps\n extends Omit<ComponentProps<typeof BaseCombobox.Input>, 'size'>, InputVariantProps {\n /**\n * Render as a flexible filler that shares the row with chips, instead of a\n * fixed-height field. Use inside `ComboboxChips`.\n * @default false\n */\n inset?: boolean;\n}\n\nexport function ComboboxInput({ size, inset = false, className, ...rest }: ComboboxInputProps) {\n const resolvedSize = size ?? 'md';\n return (\n <BaseCombobox.Input\n data-pharos-slot=\"combobox-input\"\n data-pharos-size={resolvedSize}\n className={cn(\n inset ? cn(styles.input, styles.chipsInput) : inputVariants({ size: resolvedSize }),\n className,\n )}\n {...rest}\n />\n );\n}\n\nComboboxInput.displayName = 'ComboboxInput';\n\n/**\n * The chevron button that toggles the popup. Wraps Base UI's\n * `Combobox.Trigger`; the chevron flips when the popup is open.\n */\nexport type ComboboxTriggerProps = ComponentProps<typeof BaseCombobox.Trigger>;\n\nexport function ComboboxTrigger({ className, children, ...rest }: ComboboxTriggerProps) {\n return (\n <BaseCombobox.Trigger\n data-pharos-slot=\"combobox-trigger\"\n className={cn(styles.trigger, className)}\n {...rest}\n >\n {children ?? <ChevronDownIcon />}\n </BaseCombobox.Trigger>\n );\n}\n\nComboboxTrigger.displayName = 'ComboboxTrigger';\n\n/** A button that clears the current value(s). Wraps Base UI's `Combobox.Clear`. */\nexport type ComboboxClearProps = ComponentProps<typeof BaseCombobox.Clear>;\n\nexport function ComboboxClear({ className, children, ...rest }: ComboboxClearProps) {\n return (\n <BaseCombobox.Clear\n data-pharos-slot=\"combobox-clear\"\n className={cn(styles.clear, className)}\n {...rest}\n >\n {children ?? <XIcon />}\n </BaseCombobox.Clear>\n );\n}\n\nComboboxClear.displayName = 'ComboboxClear';\n\n/** A selected-value chip for multi-select. Wraps Base UI's `Combobox.Chip`. */\nexport type ComboboxChipProps = ComponentProps<typeof BaseCombobox.Chip>;\n\nexport function ComboboxChip({ className, ...rest }: ComboboxChipProps) {\n return (\n <BaseCombobox.Chip\n data-pharos-slot=\"combobox-chip\"\n className={cn(styles.chip, className)}\n {...rest}\n />\n );\n}\n\nComboboxChip.displayName = 'ComboboxChip';\n\n/** The ✕ that removes a chip's value. Wraps Base UI's `Combobox.ChipRemove`. */\nexport type ComboboxChipRemoveProps = ComponentProps<typeof BaseCombobox.ChipRemove>;\n\nexport function ComboboxChipRemove({\n className,\n children,\n 'aria-label': ariaLabel = 'Remove',\n ...rest\n}: ComboboxChipRemoveProps) {\n return (\n <BaseCombobox.ChipRemove\n data-pharos-slot=\"combobox-chip-remove\"\n aria-label={ariaLabel}\n className={cn(styles.chipRemove, className)}\n {...rest}\n >\n {children ?? <XIcon />}\n </BaseCombobox.ChipRemove>\n );\n}\n\nComboboxChipRemove.displayName = 'ComboboxChipRemove';\n\n/**\n * The floating listbox surface. Collapses Base UI's `Portal` + `Positioner` +\n * `Popup`. Put `ComboboxEmpty` and `ComboboxList` inside it. Defaults: opens\n * below the control, aligned to its start edge, 8px away, and matches the\n * control width (`width: var(--anchor-width)`).\n */\nexport interface ComboboxContentProps extends ComponentProps<typeof BaseCombobox.Popup> {\n /**\n * Which side of the control the listbox opens on.\n * @default 'bottom'\n */\n side?: ComponentProps<typeof BaseCombobox.Positioner>['side'];\n /**\n * Alignment of the listbox relative to the control along the chosen side.\n * @default 'start'\n */\n align?: ComponentProps<typeof BaseCombobox.Positioner>['align'];\n /**\n * Gap in pixels between the control and the listbox.\n * @default 8\n */\n sideOffset?: ComponentProps<typeof BaseCombobox.Positioner>['sideOffset'];\n /**\n * Offset in pixels along the alignment axis.\n * @default 0\n */\n alignOffset?: ComponentProps<typeof BaseCombobox.Positioner>['alignOffset'];\n}\n\nexport function ComboboxContent({\n side = 'bottom',\n align = 'start',\n sideOffset = 8,\n alignOffset = 0,\n className,\n children,\n ...rest\n}: ComboboxContentProps) {\n const anchorContext = useContext(ComboboxAnchorContext);\n // Anchor to the chips control box in multi-select (see\n // ComboboxAnchorContext); single-select leaves this undefined so Base UI\n // anchors to the `ComboboxControl` (InputGroup) as before.\n const anchor = anchorContext?.anchorElement ?? undefined;\n return (\n <BaseCombobox.Portal>\n <BaseCombobox.Positioner\n className={styles.positioner}\n anchor={anchor}\n side={side}\n align={align}\n sideOffset={sideOffset}\n alignOffset={alignOffset}\n >\n <BaseCombobox.Popup\n data-pharos-slot=\"combobox-content\"\n className={cn(styles.content, className)}\n {...rest}\n >\n {children}\n </BaseCombobox.Popup>\n </BaseCombobox.Positioner>\n </BaseCombobox.Portal>\n );\n}\n\nComboboxContent.displayName = 'ComboboxContent';\n\n/**\n * The list of results. Wraps Base UI's `Combobox.List`, which accepts either\n * static children or a render function `(item, index) => ReactNode` that maps\n * over the (already filtered) `items` passed to the root.\n */\nexport type ComboboxListProps = ComponentProps<typeof BaseCombobox.List>;\n\nexport function ComboboxList({ className, ...rest }: ComboboxListProps) {\n return <BaseCombobox.List data-pharos-slot=\"combobox-list\" className={className} {...rest} />;\n}\n\nComboboxList.displayName = 'ComboboxList';\n\n/**\n * A single result row. Folds Base UI's `Item` + the selected-state\n * `ItemIndicator` check. The `value` prop is what `onValueChange` reports.\n */\nexport interface ComboboxItemProps extends ComponentProps<typeof BaseCombobox.Item> {}\n\nexport function ComboboxItem({ className, children, ...rest }: ComboboxItemProps) {\n return (\n <BaseCombobox.Item\n data-pharos-slot=\"combobox-item\"\n className={cn(styles.item, className)}\n {...rest}\n >\n <span className={styles.itemText}>{children}</span>\n <BaseCombobox.ItemIndicator className={styles.itemIndicator}>\n <CheckIcon />\n </BaseCombobox.ItemIndicator>\n </BaseCombobox.Item>\n );\n}\n\nComboboxItem.displayName = 'ComboboxItem';\n\n/**\n * Shown in place of the list when the filter matches nothing. Wraps Base UI's\n * `Combobox.Empty`, which is only rendered when there are no results.\n */\nexport type ComboboxEmptyProps = ComponentProps<typeof BaseCombobox.Empty>;\n\nexport function ComboboxEmpty({ className, ...rest }: ComboboxEmptyProps) {\n return (\n <BaseCombobox.Empty\n data-pharos-slot=\"combobox-empty\"\n className={cn(styles.empty, className)}\n {...rest}\n />\n );\n}\n\nComboboxEmpty.displayName = 'ComboboxEmpty';\n\n/** Groups related results under an optional `ComboboxGroupLabel` (`role=\"group\"`). */\nexport type ComboboxGroupProps = ComponentProps<typeof BaseCombobox.Group>;\n\nexport function ComboboxGroup(props: ComboboxGroupProps) {\n return <BaseCombobox.Group data-pharos-slot=\"combobox-group\" {...props} />;\n}\n\nComboboxGroup.displayName = 'ComboboxGroup';\n\n/** A non-interactive heading for a `ComboboxGroup`. Wraps `Combobox.GroupLabel`. */\nexport type ComboboxGroupLabelProps = ComponentProps<typeof BaseCombobox.GroupLabel>;\n\nexport function ComboboxGroupLabel({ className, ...rest }: ComboboxGroupLabelProps) {\n return (\n <BaseCombobox.GroupLabel\n data-pharos-slot=\"combobox-group-label\"\n className={cn(styles.label, className)}\n {...rest}\n />\n );\n}\n\nComboboxGroupLabel.displayName = 'ComboboxGroupLabel';\n\n/** A thin divider between groups of results. Wraps `Combobox.Separator`. */\nexport type ComboboxSeparatorProps = ComponentProps<typeof BaseCombobox.Separator>;\n\nexport function ComboboxSeparator({ className, ...rest }: ComboboxSeparatorProps) {\n return (\n <BaseCombobox.Separator\n data-pharos-slot=\"combobox-separator\"\n className={cn(styles.separator, className)}\n {...rest}\n />\n );\n}\n\nComboboxSeparator.displayName = 'ComboboxSeparator';\n","/* Sheet.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. Styles a modal panel that docks to a viewport edge and\n * slides in — the shadcn \"Sheet\" contract over Base UI's `Dialog` (modal:\n * focus trap, scroll lock, backdrop). Motion is driven by Base UI's\n * `data-starting-style` / `data-ending-style` state attributes.\n *\n * z-index: the backdrop and the panel share `--pharos-z-index-popover` — the\n * SAME overlay layer DropdownMenu / Popover / Select / Combobox use. Nesting\n * resolves by portal open order (DOM order), NOT by a higher modal z-index:\n * a Select or Popover opened from INSIDE the Sheet portals to <body> AFTER the\n * Sheet, so at equal z-index it paints above the panel. That is the primary\n * use case (a LanguageSelector inside a mobile drawer), so a dedicated higher\n * modal z-index would break it. See NAMING-decisions.md § Sheet. The token is\n * still consumer-overridable (fallback 1000). Defining a real z-index scale in\n * pharos-tokens is a tracked follow-up.\n */\n\n/* === Backdrop (Dialog.Backdrop) === */\n.backdrop {\n position: fixed;\n inset: 0;\n z-index: var(--pharos-z-index-popover, 1000);\n /* Scrim from the base-black token at 50% via color-mix — no hardcoded\n * rgba (NON-NEGOTIABLE rule 1). */\n background-color: color-mix(in srgb, var(--pharos-color-base-black) 50%, transparent);\n transition: opacity var(--pharos-duration-normal) var(--pharos-easing-out);\n}\n\n.backdrop[data-starting-style],\n.backdrop[data-ending-style] {\n opacity: 0;\n}\n\n/* === Panel (Dialog.Popup) === */\n.content {\n position: fixed;\n z-index: var(--pharos-z-index-popover, 1000);\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--pharos-spacing-4);\n padding: var(--pharos-spacing-6);\n\n background-color: var(--pharos-color-base-white);\n box-shadow: var(--pharos-shadow-xl);\n\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n color: var(--pharos-color-neutral-900);\n\n outline: none;\n overflow-y: auto;\n\n transition: transform var(--pharos-duration-normal) var(--pharos-easing-out);\n}\n\n/* === Sides === */\n.sideRight {\n top: 0;\n bottom: 0;\n right: 0;\n height: 100dvh;\n width: min(24rem, 100vw);\n border-left: 1px solid var(--pharos-color-neutral-200);\n}\n.sideRight[data-starting-style],\n.sideRight[data-ending-style] {\n transform: translateX(100%);\n}\n\n.sideLeft {\n top: 0;\n bottom: 0;\n left: 0;\n height: 100dvh;\n width: min(24rem, 100vw);\n border-right: 1px solid var(--pharos-color-neutral-200);\n}\n.sideLeft[data-starting-style],\n.sideLeft[data-ending-style] {\n transform: translateX(-100%);\n}\n\n.sideTop {\n top: 0;\n left: 0;\n right: 0;\n width: 100vw;\n max-height: 100dvh;\n border-bottom: 1px solid var(--pharos-color-neutral-200);\n}\n.sideTop[data-starting-style],\n.sideTop[data-ending-style] {\n transform: translateY(-100%);\n}\n\n.sideBottom {\n bottom: 0;\n left: 0;\n right: 0;\n width: 100vw;\n max-height: 100dvh;\n border-top: 1px solid var(--pharos-color-neutral-200);\n}\n.sideBottom[data-starting-style],\n.sideBottom[data-ending-style] {\n transform: translateY(100%);\n}\n\n/* === Slots === */\n.header {\n display: flex;\n flex-direction: column;\n gap: var(--pharos-spacing-1);\n}\n\n.title {\n margin: 0;\n font-size: var(--pharos-font-size-lg);\n font-weight: var(--pharos-font-weight-semibold);\n line-height: 1.4;\n color: var(--pharos-color-neutral-900);\n}\n\n.description {\n margin: 0;\n font-size: var(--pharos-font-size-sm);\n line-height: 1.5;\n color: var(--pharos-color-neutral-600);\n}\n\n.footer {\n display: flex;\n gap: var(--pharos-spacing-2);\n justify-content: flex-end;\n margin-top: auto;\n}\n","import type { ComponentProps, ComponentPropsWithoutRef } from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Sheet.module.css';\n\n/**\n * Modal panel that docks to a viewport edge and slides in (the shadcn\n * **Sheet** contract). Wraps Base UI's `Dialog.*`, which implements the ARIA\n * **dialog** pattern as a **modal** disclosure: `role=\"dialog\"` +\n * `aria-modal`, focus trapped inside the panel, scroll locked, Escape and\n * backdrop-click dismiss, focus returns to the trigger on close.\n *\n * Distinct from `Popover` (D16, non-modal, anchored to its trigger) and\n * `DropdownMenu` (D15, menu-button). A Sheet is for a task surface that\n * should command the screen — mobile navigation, a filters panel, a\n * create/edit form. Naming follows shadcn (`Sheet*`); the compound surface\n * collapses Base UI's `Portal` + `Backdrop` + `Popup` into a single\n * `SheetContent` with a `side` prop, mirroring the ergonomics of the other\n * Pharos overlays.\n *\n * v1 ships the parts the known consumers need (the LanguageSelector /\n * MobileDrawer): Trigger, Content, Header, Footer, Title, Description, Close.\n *\n * @example\n * <Sheet>\n * <SheetTrigger render={<Button intent=\"secondary\">Menu</Button>} />\n * <SheetContent side=\"left\">\n * <SheetHeader>\n * <SheetTitle>Navigation</SheetTitle>\n * <SheetDescription>Jump to a section.</SheetDescription>\n * </SheetHeader>\n * <nav>…</nav>\n * <SheetFooter>\n * <SheetClose render={<Button intent=\"ghost\">Close</Button>} />\n * </SheetFooter>\n * </SheetContent>\n * </Sheet>\n */\nexport type SheetProps = ComponentProps<typeof Dialog.Root>;\n\nexport function Sheet(props: SheetProps) {\n return <Dialog.Root {...props} />;\n}\n\nSheet.displayName = 'Sheet';\n\n/**\n * The element that opens the sheet. Renders a native `<button>` by default;\n * pass `render` to compose any control (`render={<Button…/>}`,\n * `render={<IconButton…/>}`).\n */\nexport type SheetTriggerProps = ComponentProps<typeof Dialog.Trigger>;\n\nexport function SheetTrigger(props: SheetTriggerProps) {\n return <Dialog.Trigger {...props} />;\n}\n\nSheetTrigger.displayName = 'SheetTrigger';\n\nconst sheetVariants = cva(styles.content, {\n variants: {\n side: {\n top: styles.sideTop,\n right: styles.sideRight,\n bottom: styles.sideBottom,\n left: styles.sideLeft,\n },\n },\n defaultVariants: {\n side: 'right',\n },\n});\n\ntype SheetVariantProps = VariantProps<typeof sheetVariants>;\n\n/**\n * The panel surface. Collapses Base UI's `Portal` + `Backdrop` + `Popup`. The\n * `side` prop docks it to one of the four viewport edges (default `right`)\n * and picks the matching slide-in transition. The backdrop and panel share\n * the `--pharos-z-index-popover` overlay layer so a Select / Popover opened\n * from inside the sheet still stacks above it (portal open order).\n */\nexport interface SheetContentProps extends ComponentProps<typeof Dialog.Popup>, SheetVariantProps {}\n\nexport function SheetContent({ side = 'right', className, children, ...rest }: SheetContentProps) {\n return (\n <Dialog.Portal>\n <Dialog.Backdrop className={styles.backdrop} />\n <Dialog.Popup\n data-pharos-slot=\"sheet-content\"\n data-pharos-side={side ?? 'right'}\n className={cn(sheetVariants({ side }), className)}\n {...rest}\n >\n {children}\n </Dialog.Popup>\n </Dialog.Portal>\n );\n}\n\nSheetContent.displayName = 'SheetContent';\n\n/** Groups the title + description at the top of the panel. */\nexport interface SheetHeaderProps extends ComponentPropsWithoutRef<'div'> {}\n\nexport function SheetHeader({ className, ...rest }: SheetHeaderProps) {\n return <div data-pharos-slot=\"sheet-header\" className={cn(styles.header, className)} {...rest} />;\n}\n\nSheetHeader.displayName = 'SheetHeader';\n\n/**\n * The panel's accessible name. Renders Base UI's `Dialog.Title`, which wires\n * `aria-labelledby` on the popup — required for the dialog to be announced.\n * Renders as an `<h2>` by default.\n */\nexport type SheetTitleProps = ComponentProps<typeof Dialog.Title>;\n\nexport function SheetTitle({ className, ...rest }: SheetTitleProps) {\n return (\n <Dialog.Title\n data-pharos-slot=\"sheet-title\"\n className={cn(styles.title, className)}\n {...rest}\n />\n );\n}\n\nSheetTitle.displayName = 'SheetTitle';\n\n/**\n * Supporting copy under the title. Renders Base UI's `Dialog.Description`,\n * which wires `aria-describedby` on the popup. Renders as a `<p>` by default.\n */\nexport type SheetDescriptionProps = ComponentProps<typeof Dialog.Description>;\n\nexport function SheetDescription({ className, ...rest }: SheetDescriptionProps) {\n return (\n <Dialog.Description\n data-pharos-slot=\"sheet-description\"\n className={cn(styles.description, className)}\n {...rest}\n />\n );\n}\n\nSheetDescription.displayName = 'SheetDescription';\n\n/** Bottom action row, pushed to the panel's end. */\nexport interface SheetFooterProps extends ComponentPropsWithoutRef<'div'> {}\n\nexport function SheetFooter({ className, ...rest }: SheetFooterProps) {\n return <div data-pharos-slot=\"sheet-footer\" className={cn(styles.footer, className)} {...rest} />;\n}\n\nSheetFooter.displayName = 'SheetFooter';\n\n/**\n * A button that closes the sheet. Renders Base UI's `Dialog.Close` (a native\n * `<button>`); compose any control via `render`.\n */\nexport type SheetCloseProps = ComponentProps<typeof Dialog.Close>;\n\nexport function SheetClose(props: SheetCloseProps) {\n return <Dialog.Close data-pharos-slot=\"sheet-close\" {...props} />;\n}\n\nSheetClose.displayName = 'SheetClose';\n\nexport { sheetVariants };\n","/* Dialog.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. Styles a centered modal panel — the shadcn \"Dialog\"\n * contract over Base UI's `Dialog` (modal: focus trap, scroll lock, backdrop).\n * Sibling of Sheet (D18): same Base UI primitive, but centered with a\n * fade-and-scale transition instead of an edge slide. Motion is driven by Base\n * UI's `data-starting-style` / `data-ending-style` state attributes.\n *\n * z-index: backdrop and panel share `--pharos-z-index-popover` — the SAME\n * overlay layer DropdownMenu / Popover / Select / Combobox / Sheet use. Nesting\n * resolves by portal open order (DOM order), so a Select or Popover opened from\n * INSIDE the dialog still stacks above it. Consumer-overridable (fallback 1000).\n * See NAMING-decisions.md § Sheet for the full rationale.\n */\n\n/* === Backdrop (Dialog.Backdrop) === */\n.backdrop {\n position: fixed;\n inset: 0;\n z-index: var(--pharos-z-index-popover, 1000);\n background-color: color-mix(in srgb, var(--pharos-color-base-black) 50%, transparent);\n transition: opacity var(--pharos-duration-normal) var(--pharos-easing-out);\n}\n\n.backdrop[data-starting-style],\n.backdrop[data-ending-style] {\n opacity: 0;\n}\n\n/* === Panel (Dialog.Popup) === */\n.content {\n position: fixed;\n top: 50%;\n left: 50%;\n /* Centering lives in the transform; the scale for the open/close motion is\n * composed on top of the same translate (see starting/ending styles). */\n transform: translate(-50%, -50%);\n transform-origin: center;\n z-index: var(--pharos-z-index-popover, 1000);\n\n box-sizing: border-box;\n width: calc(100% - 2 * var(--pharos-spacing-4));\n max-width: 32rem;\n max-height: calc(100dvh - 2 * var(--pharos-spacing-6));\n overflow-y: auto;\n\n display: flex;\n flex-direction: column;\n gap: var(--pharos-spacing-4);\n padding: var(--pharos-spacing-6);\n\n background-color: var(--pharos-color-base-white);\n border-radius: var(--pharos-radius-lg);\n box-shadow: var(--pharos-shadow-xl);\n\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n color: var(--pharos-color-neutral-900);\n\n outline: none;\n\n transition-property: opacity, transform;\n transition-duration: var(--pharos-duration-fast);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.content[data-starting-style],\n.content[data-ending-style] {\n opacity: 0;\n transform: translate(-50%, -50%) scale(0.96);\n}\n\n/* === Slots === */\n.header {\n display: flex;\n flex-direction: column;\n gap: var(--pharos-spacing-1);\n}\n\n.title {\n margin: 0;\n font-size: var(--pharos-font-size-lg);\n font-weight: var(--pharos-font-weight-semibold);\n line-height: 1.4;\n color: var(--pharos-color-neutral-900);\n}\n\n.description {\n margin: 0;\n font-size: var(--pharos-font-size-sm);\n line-height: 1.5;\n color: var(--pharos-color-neutral-600);\n}\n\n.footer {\n display: flex;\n gap: var(--pharos-spacing-2);\n justify-content: flex-end;\n margin-top: auto;\n}\n","import type { ComponentProps, ComponentPropsWithoutRef } from 'react';\nimport { Dialog as BaseDialog } from '@base-ui/react/dialog';\nimport { cn } from '../lib/cn';\nimport styles from './Dialog.module.css';\n\n/**\n * Centered modal panel (the shadcn **Dialog** contract). Wraps Base UI's\n * `Dialog.*`, which implements the ARIA **dialog** pattern as a **modal**\n * disclosure: `role=\"dialog\"` + `aria-modal`, focus trapped inside the panel,\n * scroll locked, Escape and backdrop-click dismiss, focus returns to the\n * trigger on close.\n *\n * Sibling of `Sheet` (D18): both wrap the same Base UI `Dialog` primitive.\n * Dialog is the **centered** modal (confirmations, short forms, focused\n * tasks); Sheet is the **edge-docked** panel. Distinct from `Popover` (D16,\n * non-modal, anchored) and `DropdownMenu` (D15, menu-button). Naming follows\n * shadcn (`Dialog*`); `DialogContent` collapses Base UI's `Portal` +\n * `Backdrop` + `Popup` into one part, mirroring the other Pharos overlays.\n *\n * @example\n * <Dialog>\n * <DialogTrigger render={<Button intent=\"destructive\">Delete</Button>} />\n * <DialogContent>\n * <DialogHeader>\n * <DialogTitle>Delete project?</DialogTitle>\n * <DialogDescription>This cannot be undone.</DialogDescription>\n * </DialogHeader>\n * <DialogFooter>\n * <DialogClose render={<Button intent=\"ghost\">Cancel</Button>} />\n * <Button intent=\"destructive\" onClick={onDelete}>Delete</Button>\n * </DialogFooter>\n * </DialogContent>\n * </Dialog>\n */\nexport type DialogProps = ComponentProps<typeof BaseDialog.Root>;\n\nexport function Dialog(props: DialogProps) {\n return <BaseDialog.Root {...props} />;\n}\n\nDialog.displayName = 'Dialog';\n\n/**\n * The element that opens the dialog. Renders a native `<button>` by default;\n * pass `render` to compose any control (`render={<Button…/>}`).\n */\nexport type DialogTriggerProps = ComponentProps<typeof BaseDialog.Trigger>;\n\nexport function DialogTrigger(props: DialogTriggerProps) {\n return <BaseDialog.Trigger {...props} />;\n}\n\nDialogTrigger.displayName = 'DialogTrigger';\n\n/**\n * The centered panel. Collapses Base UI's `Portal` + `Backdrop` + `Popup`. The\n * backdrop and panel share the `--pharos-z-index-popover` overlay layer so a\n * Select / Popover opened from inside the dialog still stacks above it (portal\n * open order).\n */\nexport interface DialogContentProps extends ComponentProps<typeof BaseDialog.Popup> {}\n\nexport function DialogContent({ className, children, ...rest }: DialogContentProps) {\n return (\n <BaseDialog.Portal>\n <BaseDialog.Backdrop className={styles.backdrop} />\n <BaseDialog.Popup\n data-pharos-slot=\"dialog-content\"\n className={cn(styles.content, className)}\n {...rest}\n >\n {children}\n </BaseDialog.Popup>\n </BaseDialog.Portal>\n );\n}\n\nDialogContent.displayName = 'DialogContent';\n\n/** Groups the title + description at the top of the panel. */\nexport interface DialogHeaderProps extends ComponentPropsWithoutRef<'div'> {}\n\nexport function DialogHeader({ className, ...rest }: DialogHeaderProps) {\n return (\n <div data-pharos-slot=\"dialog-header\" className={cn(styles.header, className)} {...rest} />\n );\n}\n\nDialogHeader.displayName = 'DialogHeader';\n\n/**\n * The dialog's accessible name. Renders Base UI's `Dialog.Title`, which wires\n * `aria-labelledby` on the popup — required for the dialog to be announced.\n * Renders as an `<h2>` by default.\n */\nexport type DialogTitleProps = ComponentProps<typeof BaseDialog.Title>;\n\nexport function DialogTitle({ className, ...rest }: DialogTitleProps) {\n return (\n <BaseDialog.Title\n data-pharos-slot=\"dialog-title\"\n className={cn(styles.title, className)}\n {...rest}\n />\n );\n}\n\nDialogTitle.displayName = 'DialogTitle';\n\n/**\n * Supporting copy under the title. Renders Base UI's `Dialog.Description`,\n * which wires `aria-describedby` on the popup. Renders as a `<p>` by default.\n */\nexport type DialogDescriptionProps = ComponentProps<typeof BaseDialog.Description>;\n\nexport function DialogDescription({ className, ...rest }: DialogDescriptionProps) {\n return (\n <BaseDialog.Description\n data-pharos-slot=\"dialog-description\"\n className={cn(styles.description, className)}\n {...rest}\n />\n );\n}\n\nDialogDescription.displayName = 'DialogDescription';\n\n/** Bottom action row, pushed to the panel's end. */\nexport interface DialogFooterProps extends ComponentPropsWithoutRef<'div'> {}\n\nexport function DialogFooter({ className, ...rest }: DialogFooterProps) {\n return (\n <div data-pharos-slot=\"dialog-footer\" className={cn(styles.footer, className)} {...rest} />\n );\n}\n\nDialogFooter.displayName = 'DialogFooter';\n\n/**\n * A button that closes the dialog. Renders Base UI's `Dialog.Close` (a native\n * `<button>`); compose any control via `render`.\n */\nexport type DialogCloseProps = ComponentProps<typeof BaseDialog.Close>;\n\nexport function DialogClose(props: DialogCloseProps) {\n return <BaseDialog.Close data-pharos-slot=\"dialog-close\" {...props} />;\n}\n\nDialogClose.displayName = 'DialogClose';\n","/* Checkbox.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. Styles a single square control (the shadcn \"Checkbox\"\n * contract) over Base UI's `Checkbox`. State is driven by Base UI's\n * `data-checked` / `data-indeterminate` / `data-disabled` attributes.\n *\n * The atom owns no label (Escuela 1, D11): the consumer pairs it with a\n * `<label htmlFor>`. Error state is conveyed with `aria-invalid`, like Input /\n * Textarea. The check / dash marks are inline SVG — the library ships no icon\n * dependency (same convention as Spinner / Select).\n */\n\n.root {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: var(--pharos-spacing-5);\n height: var(--pharos-spacing-5);\n padding: 0;\n\n border: 1px solid var(--pharos-color-neutral-500);\n border-radius: var(--pharos-radius-sm);\n background-color: var(--pharos-color-base-white);\n /* The indicator inherits this via `currentColor`. */\n color: var(--pharos-color-base-white);\n\n cursor: pointer;\n transition-property: background-color, border-color, box-shadow;\n transition-duration: var(--pharos-duration-fast);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.root:hover:not([data-disabled]):not([data-checked]):not([data-indeterminate]) {\n border-color: var(--pharos-color-neutral-700);\n}\n\n/* Checked and indeterminate share the filled look — the mark differs. */\n.root[data-checked],\n.root[data-indeterminate] {\n background-color: var(--pharos-color-neutral-900);\n border-color: var(--pharos-color-neutral-900);\n}\n\n/* Error state via the standard aria-invalid attribute (D11). */\n.root[aria-invalid='true'] {\n border-color: var(--pharos-color-semantic-error-fg);\n}\n\n.root:focus-visible {\n outline: none;\n /* Same two-stop ring as Button / Input so the system has one focus look. */\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-primary-600);\n}\n\n.root[data-disabled] {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.indicator {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--pharos-color-base-white);\n}\n\n.icon {\n width: var(--pharos-spacing-3);\n height: var(--pharos-spacing-3);\n}\n\n/* The Indicator renders only when checked or indeterminate; inside it the\n * check shows by default and the dash takes over for the indeterminate state. */\n.iconIndeterminate {\n display: none;\n}\n\n.indicator[data-indeterminate] .iconCheck {\n display: none;\n}\n\n.indicator[data-indeterminate] .iconIndeterminate {\n display: block;\n}\n","import type { ComponentProps } from 'react';\nimport { Checkbox as BaseCheckbox } from '@base-ui/react/checkbox';\nimport { cn } from '../lib/cn';\nimport styles from './Checkbox.module.css';\n\n/**\n * A single square checkbox control (the shadcn **Checkbox** contract). Wraps\n * Base UI's `Checkbox`, which renders a `role=\"checkbox\"` button plus a hidden\n * form input, and carries the ARIA state (`aria-checked`, including the\n * `mixed` value for the indeterminate state), keyboard toggle (Space) and\n * focus handling.\n *\n * Like `Input` / `Textarea` (Escuela 1, D11), the atom owns no label: pair it\n * with a `<label htmlFor>`. Error state is conveyed via `aria-invalid`, not a\n * custom prop.\n *\n * Indeterminate uses Base UI's boolean `indeterminate` prop (not shadcn's\n * `checked=\"indeterminate\"` string union — a Radix quirk). Base UI's split is\n * cleaner and better typed; per Rule #0 the DS follows the primitive here.\n *\n * @example\n * <label htmlFor=\"terms\" className=\"flex items-center gap-2\">\n * <Checkbox id=\"terms\" checked={agreed} onCheckedChange={setAgreed} />\n * I accept the terms\n * </label>\n */\nexport interface CheckboxProps extends ComponentProps<typeof BaseCheckbox.Root> {}\n\nfunction CheckIcon() {\n return (\n <svg\n className={cn(styles.icon, styles.iconCheck)}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n );\n}\n\nfunction IndeterminateIcon() {\n return (\n <svg\n className={cn(styles.icon, styles.iconIndeterminate)}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M5 12h14\" />\n </svg>\n );\n}\n\nexport function Checkbox({ className, ...rest }: CheckboxProps) {\n return (\n <BaseCheckbox.Root data-pharos-slot=\"checkbox\" className={cn(styles.root, className)} {...rest}>\n <BaseCheckbox.Indicator className={styles.indicator}>\n <CheckIcon />\n <IndeterminateIcon />\n </BaseCheckbox.Indicator>\n </BaseCheckbox.Root>\n );\n}\n\nCheckbox.displayName = 'Checkbox';\n"],"x_google_ignoreList":[0,1],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs","../node_modules/.pnpm/class-variance-authority@0.7.1/node_modules/class-variance-authority/dist/index.mjs","../src/lib/cn.ts","../src/components/Button.module.css","../src/components/Button.tsx","../src/components/Spinner.module.css","../src/components/Spinner.tsx","../src/components/IconButton.module.css","../src/components/IconButton.tsx","../src/components/Badge.module.css","../src/components/Badge.tsx","../src/components/Input.module.css","../src/components/Input.tsx","../src/components/Textarea.module.css","../src/components/Textarea.tsx","../src/components/Separator.module.css","../src/components/Separator.tsx","../src/components/Card.module.css","../src/components/Card.tsx","../src/components/Avatar.module.css","../src/components/Avatar.tsx","../src/components/DropdownMenu.module.css","../src/components/DropdownMenu.tsx","../src/components/Popover.module.css","../src/components/Popover.tsx","../src/components/Select.module.css","../src/components/Select.tsx","../src/components/Combobox.module.css","../src/components/Combobox.tsx","../src/components/Sheet.module.css","../src/components/Sheet.tsx","../src/components/Dialog.module.css","../src/components/Dialog.tsx","../src/components/Checkbox.module.css","../src/components/Checkbox.tsx","../src/components/Switch.module.css","../src/components/Switch.tsx","../src/components/RadioGroup.module.css","../src/components/RadioGroup.tsx"],"sourcesContent":["function r(e){var t,f,n=\"\";if(\"string\"==typeof e||\"number\"==typeof e)n+=e;else if(\"object\"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=\" \"),n+=f)}else for(f in e)e[f]&&(n&&(n+=\" \"),n+=f);return n}export function clsx(){for(var e,t,f=0,n=\"\",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=\" \"),n+=t);return n}export default clsx;","/**\n * Copyright 2022 Joe Bell. All rights reserved.\n *\n * This file is licensed to you under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with the\n * License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */ import { clsx } from \"clsx\";\nconst falsyToString = (value)=>typeof value === \"boolean\" ? `${value}` : value === 0 ? \"0\" : value;\nexport const cx = clsx;\nexport const cva = (base, config)=>(props)=>{\n var _config_compoundVariants;\n if ((config === null || config === void 0 ? void 0 : config.variants) == null) return cx(base, props === null || props === void 0 ? void 0 : props.class, props === null || props === void 0 ? void 0 : props.className);\n const { variants, defaultVariants } = config;\n const getVariantClassNames = Object.keys(variants).map((variant)=>{\n const variantProp = props === null || props === void 0 ? void 0 : props[variant];\n const defaultVariantProp = defaultVariants === null || defaultVariants === void 0 ? void 0 : defaultVariants[variant];\n if (variantProp === null) return null;\n const variantKey = falsyToString(variantProp) || falsyToString(defaultVariantProp);\n return variants[variant][variantKey];\n });\n const propsWithoutUndefined = props && Object.entries(props).reduce((acc, param)=>{\n let [key, value] = param;\n if (value === undefined) {\n return acc;\n }\n acc[key] = value;\n return acc;\n }, {});\n const getCompoundVariantClassNames = config === null || config === void 0 ? void 0 : (_config_compoundVariants = config.compoundVariants) === null || _config_compoundVariants === void 0 ? void 0 : _config_compoundVariants.reduce((acc, param)=>{\n let { class: cvClass, className: cvClassName, ...compoundVariantOptions } = param;\n return Object.entries(compoundVariantOptions).every((param)=>{\n let [key, value] = param;\n return Array.isArray(value) ? value.includes({\n ...defaultVariants,\n ...propsWithoutUndefined\n }[key]) : ({\n ...defaultVariants,\n ...propsWithoutUndefined\n })[key] === value;\n }) ? [\n ...acc,\n cvClass,\n cvClassName\n ] : acc;\n }, []);\n return cx(base, getVariantClassNames, getCompoundVariantClassNames, props === null || props === void 0 ? void 0 : props.class, props === null || props === void 0 ? void 0 : props.className);\n };\n\n","import { clsx, type ClassValue } from 'clsx';\n\n/**\n * Merge a list of class-name inputs into a single string.\n *\n * `clsx`-only by design (Decision D9): pharos-react no longer ships Tailwind\n * classes, so the previous `twMerge` step was wrapping each call with a\n * Tailwind-conflict resolver that nothing in the bundle benefited from.\n *\n * Consumers that build with Tailwind and want the conflict-resolution\n * behavior should keep using their own `tailwind-merge` helper alongside\n * Pharos components — `<Button className={twMerge(myClasses)} />` works\n * because Pharos-side class names are CSS Modules hashes that no Tailwind\n * resolver will ever try to deduplicate.\n */\nexport function cn(...inputs: ClassValue[]): string {\n return clsx(inputs);\n}\n","/* Button.module.css\n *\n * Per-component CSS Modules definition (Decision D9). The build emits hashed\n * class names (e.g. `Button_button__a3f2`) into dist/styles.css; the runtime\n * JS references those names through the `styles` object imported by Button.tsx.\n *\n * Every visual value comes from a `--pharos-*` token. No hex, no raw px/rem\n * for design dimensions, no Tailwind. Hover / active / focus states are\n * authored with native CSS pseudo-classes — no preprocessor, no utility-first\n * shorthand, no framework dependency at runtime.\n *\n * Consumers do not need Tailwind (any version) or any other CSS framework to\n * use this component. They only need to load the published `dist/styles.css`\n * once and the consumer's app must define `--pharos-*` (achieved by importing\n * `@code-sherpas/pharos-tokens/css`, which is a peer dependency).\n */\n\n.button {\n /* Strip native button rendering. The component is fully custom-styled,\n * so platform-specific drawing (rounded corners, shadows, focus rings\n * on macOS, push-button gradient on iOS) only adds variance without\n * adding value. `appearance: none` keeps every consumer rendering the\n * exact same pixel set. The `-webkit-appearance` line is a no-op on\n * modern Chromium but stays for older WebKit. */\n appearance: none;\n -webkit-appearance: none;\n\n /* === Box model ===\n * Explicit so the rendered control matches the height set in the size\n * rules even when the consumer's app does not ship a CSS reset. With\n * the consumer-side preflight gone, `border-box`, `margin: 0` and the\n * zero block-padding neutralise the user-agent's `padding: 1px 6px`\n * (Chrome) or `padding: 0 8px` (Firefox) on `<button>`. */\n box-sizing: border-box;\n margin: 0;\n padding-top: 0;\n padding-bottom: 0;\n border: 0 solid transparent;\n border-radius: var(--pharos-radius-full);\n\n /* === Layout === */\n display: inline-flex;\n align-items: center;\n justify-content: center;\n white-space: nowrap;\n\n /* === Optical text centering ===\n * `text-box-trim` strips the leading above the cap-line and below the\n * alphabetic baseline so `align-items: center` centres the actual\n * cap-block instead of the line-box (which includes per-font half-\n * leading that biases the visual centre upward or downward depending\n * on the font's vertical metrics). Without this, fonts with a high\n * baseline like Outfit (the D8 sans) render visibly above the\n * geometric centre even though `align-items: center` is correctly\n * applied — matching what Chromatic flagged on the migration PR.\n * The `text-box-edge` longhand picks the cap-line at the start and\n * the alphabetic baseline at the end so descenders (\"y\", \"g\", ...)\n * still hang below the cap-block as the font intended; only the\n * extra leading is trimmed.\n *\n * Browser support: Chromium 133+ / Safari 18.2+ ship the property,\n * Firefox has it behind a flag at the time of writing. In engines\n * without support the declaration is ignored and the text falls\n * back to standard line-box centring — which is the same behaviour\n * the bundle had before this rule, so the fallback is safe. */\n text-box-trim: trim-both;\n text-box-edge: cap alphabetic;\n\n /* === Typography ===\n * Each font longhand is set explicitly. The shorthand `font: inherit`\n * was tempting but the CSS spec says the `font` shorthand resets a\n * dozen related properties (`font-feature-settings`,\n * `font-variation-settings`, `font-kerning`, `font-optical-sizing`,\n * `font-size-adjust`, etc.) to their *initial* values rather than\n * inheriting them. That subtle reset shifted the rendered baseline\n * by a fraction of a pixel against the consumer's surrounding text,\n * which Chromatic flagged as a diff vs 0.2.x. The size variants\n * below override `font-size` and `line-height` per size. */\n font-family: var(--pharos-font-family-sans);\n font-style: normal;\n font-weight: var(--pharos-font-weight-medium);\n font-stretch: normal;\n font-variant: normal;\n font-kerning: auto;\n font-optical-sizing: auto;\n font-size-adjust: none;\n font-feature-settings: normal;\n font-variation-settings: normal;\n letter-spacing: normal;\n text-decoration: none;\n text-transform: none;\n /* Default size (overridden per size variant) — keeps a sensible\n * fallback if a Button is somehow rendered without a size class. */\n font-size: var(--pharos-font-size-sm);\n line-height: 1.5;\n\n /* === Interactivity === */\n user-select: none;\n cursor: pointer;\n background-color: transparent;\n color: inherit;\n\n /* === Transitions === */\n transition-property: color, background-color, border-color, box-shadow;\n transition-duration: var(--pharos-duration-normal);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.button:focus-visible {\n outline: none;\n /* Two-stop box-shadow mimics the Tailwind `ring-offset-2 ring-2` pattern:\n * an inner halo in the surface color separates the ring from the button\n * fill, and the outer color is the per-intent focus accent. */\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--button-focus-ring, var(--pharos-color-primary-600));\n}\n\n.button:disabled {\n pointer-events: none;\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n/* === Intents === */\n\n.primary {\n background-color: var(--pharos-color-neutral-900);\n color: var(--pharos-color-base-white);\n --button-focus-ring: var(--pharos-color-primary-600);\n}\n\n.primary:hover {\n background-color: var(--pharos-color-neutral-700);\n}\n\n.primary:active {\n background-color: var(--pharos-color-neutral-950);\n}\n\n.secondary {\n background-color: var(--pharos-color-base-white);\n color: var(--pharos-color-neutral-900);\n /* `neutral-500` clears WCAG 1.4.11 (Non-text Contrast, 3:1 against the\n * surrounding surface). See Input.module.css for the full rationale and\n * the cross-DS comparison; Button secondary, Input and Badge outline\n * share the same border tone so the system has one accessible boundary\n * intensity across primitives. */\n border: 1px solid var(--pharos-color-neutral-500);\n --button-focus-ring: var(--pharos-color-primary-600);\n}\n\n.secondary:hover {\n background-color: var(--pharos-color-neutral-50);\n}\n\n.secondary:active {\n background-color: var(--pharos-color-neutral-100);\n}\n\n.ghost {\n background-color: transparent;\n color: var(--pharos-color-neutral-900);\n --button-focus-ring: var(--pharos-color-primary-600);\n}\n\n.ghost:hover {\n background-color: var(--pharos-color-neutral-100);\n}\n\n.ghost:active {\n background-color: var(--pharos-color-neutral-200);\n}\n\n.destructive {\n background-color: var(--pharos-color-semantic-error-fg);\n color: var(--pharos-color-semantic-error-on);\n --button-focus-ring: var(--pharos-color-semantic-error-fg);\n}\n\n.destructive:hover {\n background-color: var(--pharos-color-semantic-error-fg-hover);\n}\n\n.destructive:active {\n background-color: var(--pharos-color-semantic-error-fg-active);\n}\n\n/* === Sizes ===\n *\n * Heights and paddings are derived from `--pharos-spacing-*` so a runtime\n * override of the spacing scale rescales the whole control proportionally.\n * Font sizes use `--pharos-font-size-*` tokens; the line-height ratios match\n * the Tailwind v4 defaults that shipped in 0.2.x so visual fidelity is\n * preserved across the migration. */\n\n.sizeSm {\n height: var(--pharos-spacing-8);\n gap: var(--pharos-spacing-1-5);\n padding: 0 var(--pharos-spacing-3);\n font-size: var(--pharos-font-size-sm);\n line-height: calc(1.25 / 0.875);\n}\n\n.sizeMd {\n height: var(--pharos-spacing-10);\n gap: var(--pharos-spacing-2);\n padding: 0 var(--pharos-spacing-4);\n font-size: var(--pharos-font-size-sm);\n line-height: calc(1.25 / 0.875);\n}\n\n.sizeLg {\n height: var(--pharos-spacing-12);\n gap: var(--pharos-spacing-2);\n padding: 0 var(--pharos-spacing-6);\n font-size: var(--pharos-font-size-base);\n line-height: 1.5;\n}\n","import { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Button.module.css';\n\n/**\n * Maps the two public variant axes (`intent`, `size`) to the per-class names\n * inside `Button.module.css`. CVA stays as the typed-variants mechanism\n * (Decision D9 keeps the authoring DX) but each value points at a hashed\n * CSS Modules class instead of a Tailwind utility string. The base class\n * carries layout, typography and the focus-visible ring — every variant\n * stacks on top of it.\n */\nconst buttonVariants = cva(styles.button, {\n variants: {\n intent: {\n // `primary` is the most emphasized CTA. It renders in neutral-900,\n // matching Alexandria's `NewButton variant=\"filled\" tone=\"default\"`\n // and the Linear/Vercel/Notion convention of \"brand color as accent,\n // dark neutral as primary action\". The blue `primary-*` palette is\n // reserved for brand accents (focus rings, highlights, badges) —\n // not for the default filled button.\n primary: styles.primary,\n secondary: styles.secondary,\n ghost: styles.ghost,\n destructive: styles.destructive,\n },\n size: {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n },\n },\n defaultVariants: {\n intent: 'primary',\n size: 'md',\n },\n});\n\ntype ButtonVariantProps = VariantProps<typeof buttonVariants>;\n\n/**\n * Props accepted by `<Button>`. Extends Base UI's `useRender.ComponentProps<'button'>`,\n * which means the component accepts every native `<button>` attribute plus the\n * `render` prop for composition:\n *\n * ```tsx\n * // Render as a router link (inherits all Button styles and behavior):\n * <Button render={<Link to=\"/dashboard\" />}>Dashboard</Button>\n * // Or as a function receiving props + state:\n * <Button render={(props) => <a {...props} href=\"/x\" />}>External</Button>\n * ```\n */\nexport interface ButtonProps extends useRender.ComponentProps<'button'>, ButtonVariantProps {}\n\nexport function Button({\n render,\n className,\n intent,\n size,\n type = 'button',\n ref,\n ...rest\n}: ButtonProps) {\n const resolvedIntent = intent ?? 'primary';\n const resolvedSize = size ?? 'md';\n return useRender({\n render: render ?? <button />,\n ref,\n defaultTagName: 'button',\n props: {\n type,\n // `data-pharos-intent` / `data-pharos-size` are stable hooks for\n // selectors and tests. They mirror the CSS Modules class but stay\n // semantic — consumer queries do not need to know the hashed class\n // name and tests do not need to import the module to assert variant.\n 'data-pharos-intent': resolvedIntent,\n 'data-pharos-size': resolvedSize,\n className: cn(buttonVariants({ intent: resolvedIntent, size: resolvedSize }), className),\n ...rest,\n },\n });\n}\n\nButton.displayName = 'Button';\n\nexport { buttonVariants };\n","/* Spinner.module.css\n *\n * Per-component CSS Modules definition (Decision D9). The build emits hashed\n * class names (`Spinner_spinner__xxxx`) into `dist/styles.css`; the runtime\n * JS references them through the `styles` object imported by Spinner.tsx.\n *\n * The Spinner is intentionally minimal: a `<span role=\"status\">` that\n * inherits its colour from the parent via `currentColor`. There is no\n * `variant` / `tone` axis — the consumer chooses the colour by setting\n * `color` on the parent (or by overriding via `className`). This matches\n * the shadcn / Radix Themes / Mantine pattern.\n */\n\n@keyframes pharos-spinner-spin {\n /* Single full rotation. Authored as a uniquely-named keyframe (prefixed\n * `pharos-spinner-`) so it cannot collide with consumer keyframes. The\n * `from`/`to` form is two-line and clear; using `100%` would compile to\n * the same output. */\n from {\n transform: rotate(0deg);\n }\n\n to {\n transform: rotate(360deg);\n }\n}\n\n.spinner {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n /* `currentColor` makes the Spinner inherit the parent's text colour by\n * default, so `<Button intent=\"primary\"><Spinner /></Button>` works out\n * of the box (the Button's white text colour cascades into the SVG's\n * `stroke=\"currentColor\"`). */\n color: currentColor;\n /* Vertical alignment with surrounding inline content (e.g. text inside a\n * Button label). Without this, the spinner sits a couple of pixels low\n * because the SVG box's baseline is its bottom edge. */\n vertical-align: middle;\n /* The animation lives on the icon, not the wrapper, so a consumer that\n * targets the wrapper for layout (margin, gap-as-flex-child) does not\n * accidentally inherit the spin transform. */\n}\n\n.icon {\n width: 100%;\n height: 100%;\n animation: pharos-spinner-spin var(--pharos-duration-slow, 1s) linear infinite;\n}\n\n/* Respect the user's reduced-motion preference. The animation slows down\n * dramatically rather than disappearing; some assistive interactions\n * still need a \"this is in progress\" visual cue, so we keep movement but\n * make it gentler. Same compromise Radix Themes uses. */\n@media (prefers-reduced-motion: reduce) {\n .icon {\n animation-duration: 4s;\n }\n}\n\n/* === Sizes ===\n *\n * Heights align with the form-control grid so a Spinner sits flush inside\n * a Button / Input / Textarea of the same size. The visual diameter of the\n * SVG itself fills the wrapper (`width: 100%; height: 100%` on the icon),\n * so the wrapper size IS the spinner size. */\n\n.sizeSm {\n width: var(--pharos-spacing-4); /* 16 px */\n height: var(--pharos-spacing-4);\n}\n\n.sizeMd {\n width: var(--pharos-spacing-5); /* 20 px */\n height: var(--pharos-spacing-5);\n}\n\n.sizeLg {\n width: var(--pharos-spacing-6); /* 24 px */\n height: var(--pharos-spacing-6);\n}\n\n/* Visually-hidden utility for the screen-reader label. Identical to the\n * Tailwind `sr-only` recipe — preserved as a local utility because Pharos\n * does not ship Tailwind in `dist/` (Decision D9). */\n.srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n","import type { ComponentProps } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Spinner.module.css';\n\n/**\n * Maps the `size` axis to per-class names inside `Spinner.module.css`. Sizes\n * align with the Button / Input / Textarea grid (16 / 20 / 24 px) so a\n * Spinner sits flush inside a control of the same size — Composition cases\n * like `<Button><Spinner /></Button>` work out of the box because the\n * Spinner inherits the Button's `currentColor`.\n *\n * No `variant` / `tone` axis on purpose. State-of-the-art DSes (Radix\n * Themes, Mantine, shadcn) keep the Spinner as a single chrome that uses\n * `currentColor`; the consumer picks the colour by setting `color` on the\n * parent (or via `className`). This keeps the atom small and removes the\n * need for a tone palette mapping.\n */\nconst spinnerVariants = cva(styles.spinner, {\n variants: {\n size: {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n },\n },\n defaultVariants: {\n size: 'md',\n },\n});\n\ntype SpinnerVariantProps = VariantProps<typeof spinnerVariants>;\n\n/**\n * Props accepted by `<Spinner>`. The component renders a `<span role=\"status\">`\n * with an inline SVG that rotates via CSS keyframes (no JS, no animation\n * libraries). Screen readers announce the `srLabel` (default `\"Loading…\"`)\n * via a visually hidden `<span>` inside the status node — the same a11y\n * pattern shadcn / Radix Themes / Material UI use.\n *\n * - `size` aligns with the control grid (Button / Input / Textarea).\n * - `srLabel` is the announced text. Pass an explicit one when the loading\n * action is specific (e.g. `\"Saving template…\"`); the default suits\n * generic \"something is loading\" cases.\n * - Native `<span>` props (e.g. `style`, `aria-*`) pass through. If the\n * consumer sets `aria-label` directly, that wins over `srLabel` for\n * assistive tech (the `srLabel` `<span>` is then redundant but harmless).\n */\nexport interface SpinnerProps extends ComponentProps<'span'>, SpinnerVariantProps {\n /**\n * Visually hidden text announced by assistive tech. Pass an explicit\n * label tied to the action (e.g. `\"Submitting form…\"`) when relevant.\n *\n * @default \"Loading…\"\n */\n srLabel?: string;\n}\n\nexport function Spinner({\n size,\n className,\n srLabel = 'Loading…',\n children,\n ref,\n ...rest\n}: SpinnerProps) {\n const resolvedSize = size ?? 'md';\n return (\n <span\n ref={ref}\n role=\"status\"\n data-pharos-size={resolvedSize}\n className={cn(spinnerVariants({ size: resolvedSize }), className)}\n {...rest}\n >\n <svg\n className={styles.icon}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2.5}\n strokeLinecap=\"round\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n {/*\n * Single arc that completes ~75 % of the circle. The remaining gap\n * gives the visual rotation a clear lead/tail under the CSS spin\n * animation. Same shape Radix Themes / Mantine use; differs from\n * shadcn's lucide-Loader2 only in being inline and dependency-free.\n */}\n <circle cx=\"12\" cy=\"12\" r=\"10\" strokeDasharray=\"50 25\" />\n </svg>\n <span className={styles.srOnly}>{srLabel}</span>\n {children}\n </span>\n );\n}\n\nSpinner.displayName = 'Spinner';\n\nexport { spinnerVariants };\n","/* IconButton.module.css\n *\n * Per-component CSS Modules definition (Decision D9). The build emits hashed\n * class names (`IconButton_button__xxxx`) into `dist/styles.css`; the runtime\n * JS references them through the `styles` object imported by IconButton.tsx.\n *\n * Square dimensions across every size — the wrapper width matches the\n * height, so the rounded-full radius produces a perfect circle (matches\n * Material 3 / MUI / Mantine ActionIcon transparent variant). Icon child\n * SVGs are sized via the `> svg` direct-child selector so a Lucide icon\n * does not need explicit `size` props on the consumer side.\n */\n\n.button {\n /* Strip native button rendering. Same rationale as Button.module.css —\n * platform-specific drawing only adds variance without value. */\n appearance: none;\n -webkit-appearance: none;\n\n /* === Box model ===\n * `padding: 0` plus equal width / height gives the square that the\n * `border-radius: var(--pharos-radius-full)` turns into a circle. Hard\n * `flex-shrink: 0` so the IconButton keeps its dimensions inside a flex\n * row (toolbar, header) where surrounding labels could otherwise force\n * it to compress. */\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid transparent;\n border-radius: var(--pharos-radius-full);\n flex-shrink: 0;\n\n /* === Layout === */\n display: inline-flex;\n align-items: center;\n justify-content: center;\n\n /* === Interactivity === */\n user-select: none;\n cursor: pointer;\n background-color: transparent;\n color: inherit;\n font-family: var(--pharos-font-family-sans);\n\n /* === Transitions === */\n transition-property: color, background-color, border-color, box-shadow;\n transition-duration: var(--pharos-duration-normal);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.button:focus-visible {\n outline: none;\n /* Two-stop box-shadow mirrors the Button focus ring so a row of mixed\n * Buttons + IconButtons share a single focus look. */\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--icon-button-focus-ring, var(--pharos-color-primary-600));\n}\n\n.button:disabled {\n pointer-events: none;\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n/* === Intents ===\n *\n * Same palette as Button (D-cardinal: state-of-the-art coherence first,\n * Alexandria adapts). `ghost` is the most common icon-button case\n * (toolbars, close buttons, action icons), but the four intents stay\n * available for the rare deliberate emphasis (primary \"send\" icon,\n * destructive \"delete\" icon, secondary bordered circle navigation). */\n\n.primary {\n background-color: var(--pharos-color-neutral-900);\n color: var(--pharos-color-base-white);\n --icon-button-focus-ring: var(--pharos-color-primary-600);\n}\n\n.primary:hover {\n background-color: var(--pharos-color-neutral-700);\n}\n\n.primary:active {\n background-color: var(--pharos-color-neutral-950);\n}\n\n.secondary {\n background-color: var(--pharos-color-base-white);\n color: var(--pharos-color-neutral-900);\n /* `neutral-500` clears WCAG 1.4.11 (Non-text Contrast, 3:1 vs surface).\n * Same border tone Button secondary / Input / Badge outline use — the\n * three-tier border hierarchy (D10) treats every interactive control\n * the same way. */\n border: 1px solid var(--pharos-color-neutral-500);\n --icon-button-focus-ring: var(--pharos-color-primary-600);\n}\n\n.secondary:hover {\n background-color: var(--pharos-color-neutral-50);\n}\n\n.secondary:active {\n background-color: var(--pharos-color-neutral-100);\n}\n\n.ghost {\n background-color: transparent;\n color: var(--pharos-color-neutral-900);\n --icon-button-focus-ring: var(--pharos-color-primary-600);\n}\n\n.ghost:hover {\n background-color: var(--pharos-color-neutral-100);\n}\n\n.ghost:active {\n background-color: var(--pharos-color-neutral-200);\n}\n\n.destructive {\n background-color: var(--pharos-color-semantic-error-fg);\n color: var(--pharos-color-semantic-error-on);\n --icon-button-focus-ring: var(--pharos-color-semantic-error-fg);\n}\n\n.destructive:hover {\n background-color: var(--pharos-color-semantic-error-fg-hover);\n}\n\n.destructive:active {\n background-color: var(--pharos-color-semantic-error-fg-active);\n}\n\n/* === Sizes ===\n *\n * Square dimensions match the Button height grid so an IconButton + Button\n * pair on the same row sit flush (32 / 40 / 48 px). Direct-child `> svg`\n * sizing forces a passed Lucide icon (which renders with hardcoded\n * width/height=\"24\") to fill the icon slot proportionally — 16 / 20 / 24\n * matching the Spinner sizes so `<IconButton isLoading>` looks identical\n * in size to the resting icon state.\n *\n * `> svg` (direct child) is deliberate: Spinner renders a `<span>` wrapper\n * around its own SVG and sizes itself via `size={resolvedSize}`, so the\n * rule does not need to (and should not) double-size the spinner SVG.\n * Consumers wrapping an icon in a custom component remain responsible for\n * sizing — same contract shadcn / Mantine document. */\n\n.sizeSm {\n width: var(--pharos-spacing-8); /* 32 px */\n height: var(--pharos-spacing-8);\n}\n\n.sizeSm > svg {\n width: var(--pharos-spacing-4); /* 16 px */\n height: var(--pharos-spacing-4);\n}\n\n.sizeMd {\n width: var(--pharos-spacing-10); /* 40 px */\n height: var(--pharos-spacing-10);\n}\n\n.sizeMd > svg {\n width: var(--pharos-spacing-5); /* 20 px */\n height: var(--pharos-spacing-5);\n}\n\n.sizeLg {\n width: var(--pharos-spacing-12); /* 48 px */\n height: var(--pharos-spacing-12);\n}\n\n.sizeLg > svg {\n width: var(--pharos-spacing-6); /* 24 px */\n height: var(--pharos-spacing-6);\n}\n","import { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport { Spinner } from './Spinner';\nimport styles from './IconButton.module.css';\n\n/**\n * Maps the two public variant axes (`intent`, `size`) to the per-class names\n * inside `IconButton.module.css`. Mirrors the Button atom so an IconButton\n * sits next to a Button of the same size with matching height (32 / 40 /\n * 48 px) and shares the same intent vocabulary — `ghost` is the dominant\n * intent for icon-only controls (close, expand, dismiss…) so it is the\n * default, unlike Button where `primary` defaults.\n */\nconst iconButtonVariants = cva(styles.button, {\n variants: {\n intent: {\n primary: styles.primary,\n secondary: styles.secondary,\n ghost: styles.ghost,\n destructive: styles.destructive,\n },\n size: {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n },\n },\n defaultVariants: {\n intent: 'ghost',\n size: 'md',\n },\n});\n\ntype IconButtonVariantProps = VariantProps<typeof iconButtonVariants>;\n\n/**\n * Either `aria-label` or `aria-labelledby` is required at the type level.\n * An icon-only control with no accessible name is unusable for assistive\n * tech (WCAG 4.1.2). The atom enforces the constraint by construction —\n * the equivalent runtime check Chakra / Mantine emit as a console warning\n * is here a TypeScript error, so a missing label cannot ship.\n */\ntype IconButtonRequiredA11y =\n | { 'aria-label': string; 'aria-labelledby'?: never }\n | { 'aria-label'?: never; 'aria-labelledby': string };\n\n/**\n * Props accepted by `<IconButton>`. Extends Base UI's\n * `useRender.ComponentProps<'button'>` minus the optional `aria-label` /\n * `aria-labelledby` (re-added as required), plus the variant axes and an\n * `isLoading` toggle that swaps the icon for a `<Spinner>` of the same\n * size.\n *\n * The `render` prop composes the IconButton as a different element while\n * keeping its styles and behaviour — same shape as Button, useful for\n * `<IconButton render={<Link to=\"/next\" />} aria-label=\"Next\">…</IconButton>`\n * call-sites (e.g. circle navigation buttons).\n */\nexport type IconButtonProps = Omit<\n useRender.ComponentProps<'button'>,\n 'aria-label' | 'aria-labelledby'\n> &\n IconButtonVariantProps &\n IconButtonRequiredA11y & {\n /**\n * When `true`, replaces the icon with a `<Spinner>` of matching size,\n * sets `disabled` so the action cannot fire mid-flight, and exposes\n * `aria-busy=\"true\"` so screen readers announce the in-progress state.\n */\n isLoading?: boolean;\n };\n\nexport function IconButton({\n render,\n className,\n intent,\n size,\n isLoading,\n disabled,\n type = 'button',\n children,\n ref,\n ...rest\n}: IconButtonProps) {\n const resolvedIntent = intent ?? 'ghost';\n const resolvedSize = size ?? 'md';\n const isDisabled = Boolean(disabled) || Boolean(isLoading);\n return useRender({\n render: render ?? <button />,\n ref,\n defaultTagName: 'button',\n props: {\n type,\n 'data-pharos-intent': resolvedIntent,\n 'data-pharos-size': resolvedSize,\n 'aria-busy': isLoading ? true : undefined,\n disabled: isDisabled,\n className: cn(iconButtonVariants({ intent: resolvedIntent, size: resolvedSize }), className),\n ...rest,\n children: isLoading ? <Spinner size={resolvedSize} /> : children,\n },\n });\n}\n\nIconButton.displayName = 'IconButton';\n\nexport { iconButtonVariants };\n","/* Base layout. Same across every variant — only surface, label and border\n * change. Inline-flex so a child SVG (rendered by the consumer as plain\n * children) sits on the same baseline as the label. The font-relative SVG\n * sizing rule mirrors shadcn's `[&>svg]:size-3` utility. */\n.badge {\n display: inline-flex;\n align-items: center;\n gap: var(--pharos-spacing-1);\n padding: 0 var(--pharos-spacing-2);\n height: var(--pharos-spacing-5);\n border: 1px solid transparent;\n border-radius: var(--pharos-radius-full);\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-xs);\n font-weight: var(--pharos-font-weight-medium);\n line-height: 1;\n white-space: nowrap;\n width: fit-content;\n text-box-trim: trim-both;\n text-box-edge: cap alphabetic;\n}\n\n.badge > svg {\n width: 0.75em;\n height: 0.75em;\n flex: 0 0 auto;\n pointer-events: none;\n}\n\n.default {\n background: var(--pharos-color-neutral-900);\n color: var(--pharos-color-base-white);\n}\n\n.secondary {\n background: var(--pharos-color-neutral-100);\n color: var(--pharos-color-neutral-900);\n}\n\n.destructive {\n background: var(--pharos-color-semantic-error-fg);\n color: var(--pharos-color-semantic-error-on);\n}\n\n.outline {\n background: transparent;\n color: var(--pharos-color-neutral-900);\n /* `neutral-500` clears WCAG 1.4.11 (Non-text Contrast, 3:1 against the\n * surrounding surface) and matches the resting border of Input and\n * Button secondary so the system has one accessible boundary intensity\n * across primitives. See `Input.module.css` for the cross-DS rationale. */\n border-color: var(--pharos-color-neutral-500);\n}\n\n.success {\n background: var(--pharos-color-semantic-success-fg);\n color: var(--pharos-color-semantic-success-on);\n}\n\n.warning {\n background: var(--pharos-color-semantic-warning-fg);\n color: var(--pharos-color-semantic-warning-on);\n}\n\n.info {\n background: var(--pharos-color-semantic-info-fg);\n color: var(--pharos-color-semantic-info-on);\n}\n","import type { ComponentPropsWithoutRef } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Badge.module.css';\n\n/**\n * Maps the `variant` axis to per-class names inside `Badge.module.css`.\n * The variant set follows shadcn/ui — `default`, `secondary`, `destructive`,\n * `outline` — extended with the semantic tones every modern DS exposes\n * (`success`, `warning`, `info`). Naming stays asymmetric on purpose:\n * `destructive` is the canonical shadcn name for \"negative tone\" and we\n * keep it instead of renaming it to `error`, so consumers familiar with\n * shadcn pick the right value without re-learning the API.\n */\nconst badgeVariants = cva(styles.badge, {\n variants: {\n variant: {\n default: styles.default,\n secondary: styles.secondary,\n destructive: styles.destructive,\n outline: styles.outline,\n success: styles.success,\n warning: styles.warning,\n info: styles.info,\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n});\n\ntype BadgeVariantProps = VariantProps<typeof badgeVariants>;\n\nexport interface BadgeProps extends ComponentPropsWithoutRef<'span'>, BadgeVariantProps {}\n\nexport function Badge({ variant, className, ...rest }: BadgeProps) {\n const resolvedVariant = variant ?? 'default';\n return (\n <span\n data-pharos-variant={resolvedVariant}\n className={cn(badgeVariants({ variant: resolvedVariant }), className)}\n {...rest}\n />\n );\n}\n\nBadge.displayName = 'Badge';\n\nexport { badgeVariants };\n","/* Input.module.css\n *\n * Per-component CSS Modules definition (Decision D9). The build emits hashed\n * class names (`Input_input__xxxx`) into `dist/styles.css`; the runtime JS\n * references them through the `styles` object imported by Input.tsx.\n *\n * Every visual value comes from a `--pharos-*` token. No hex, no Tailwind,\n * no preflight. Hover / focus-visible / invalid / disabled states are\n * authored with native CSS pseudo-classes and the `[aria-invalid=\"true\"]`\n * attribute selector — the component does not own any \"error mode\" state\n * itself; it reflects what the consumer puts on the DOM.\n */\n\n.input {\n /* Strip the user-agent input chrome (gradient backgrounds, internal\n * padding, rounded corners on macOS, the right-edge spinner on number).\n * With preflight gone from the consumer, this is the only line that\n * normalizes the rendering across browsers. */\n appearance: none;\n -webkit-appearance: none;\n\n /* === Box model === */\n box-sizing: border-box;\n width: 100%;\n margin: 0;\n /* `neutral-500` lands the resting border at ~5.4:1 contrast against a\n * white surface, clearing WCAG 1.4.11 (Non-text Contrast, 3:1 for UI\n * component boundaries). The shadcn / Radix Themes / Mantine default\n * sits around oklch 92% (~1.3:1) and fails the requirement; Pharos\n * deliberately diverges to comply. Carbon `border-strong-01` (#8d8d8d)\n * and Adobe Spectrum's outline tone are at the same intensity. */\n border: 1px solid var(--pharos-color-neutral-500);\n border-radius: var(--pharos-radius-md);\n background-color: var(--pharos-color-base-white);\n color: var(--pharos-color-neutral-900);\n\n /* === Typography ===\n * `font-family` and weight are explicit so the input does not inherit a\n * system-ui fallback when it lives inside surfaces that haven't set the\n * Pharos sans stack (e.g. inside a `<dialog>` rendered to the top layer).\n * The size variants below override `font-size` and the `padding-inline`. */\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n font-weight: var(--pharos-font-weight-regular);\n line-height: 1.25;\n\n /* === Transitions === */\n transition-property: border-color, box-shadow;\n transition-duration: var(--pharos-duration-normal);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.input::placeholder {\n color: var(--pharos-color-neutral-500);\n /* Some Linux/Chromium builds dim placeholders to ~54% by default; the\n * explicit `opacity: 1` keeps the rendered tone equal to the token. */\n opacity: 1;\n}\n\n.input:focus-visible {\n outline: none;\n border-color: var(--pharos-color-primary-600);\n /* Two-stop box-shadow mirrors the Button focus ring: an inner halo in the\n * surface color separates the ring from the input fill, and the outer\n * stop is the brand accent. Same tokens as Button so the system has a\n * single focus look. */\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-primary-600);\n}\n\n.input:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.input[aria-invalid='true'] {\n border-color: var(--pharos-color-semantic-error-fg);\n}\n\n.input[aria-invalid='true']:focus-visible {\n border-color: var(--pharos-color-semantic-error-fg);\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-semantic-error-fg);\n}\n\n/* === Sizes ===\n *\n * Heights match the Button grid exactly — the system has one vertical\n * rhythm across pressable controls and form fields, so an `<Input size>`\n * sits flush next to a `<Button size>` on the same row. Horizontal padding\n * follows the same scale Button uses; font-size hops to `base` (16px) at\n * `lg` because larger controls usually pair with prominent layouts where\n * the body 14px starts to feel undersized. */\n\n.sizeSm {\n height: var(--pharos-spacing-8);\n padding: 0 var(--pharos-spacing-3);\n font-size: var(--pharos-font-size-sm);\n}\n\n.sizeMd {\n height: var(--pharos-spacing-10);\n padding: 0 var(--pharos-spacing-4);\n font-size: var(--pharos-font-size-sm);\n}\n\n.sizeLg {\n height: var(--pharos-spacing-12);\n padding: 0 var(--pharos-spacing-6);\n font-size: var(--pharos-font-size-base);\n}\n","import type { ComponentProps } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Input.module.css';\n\n/**\n * Maps the `size` axis to per-class names inside `Input.module.css`. Input\n * has no `variant` axis on purpose — shadcn's primitive contract is a single\n * styled `<input>` and the design-system best practice for form controls is\n * one chrome with explicit error / disabled / readonly states. Composition\n * around the input (label, helper, error message) belongs to a future\n * `<Field>` molecule, not to this atom (Escuela 1 from the field-pattern\n * decision, 2026-04-27).\n */\nconst inputVariants = cva(styles.input, {\n variants: {\n size: {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n },\n },\n defaultVariants: {\n size: 'md',\n },\n});\n\ntype InputVariantProps = VariantProps<typeof inputVariants>;\n\n/**\n * Props accepted by `<Input>`. Extends every native `<input>` attribute\n * except `size` — the native `size` attribute is a number (visible width\n * in characters) that is rarely used in modern apps where width comes from\n * CSS, so the slot is reused for the typed Pharos size axis. Consumers that\n * truly need the HTML attribute can set it through `style.width` or via a\n * dedicated `htmlSize` prop if we expose one later.\n *\n * Error state is conveyed through the standard `aria-invalid` attribute,\n * not a custom `error` prop. The CSS reacts to `[aria-invalid=\"true\"]`,\n * matching shadcn / Base UI / Mantine. The actual error message is rendered\n * by the consumer next to the input (or by the future `<Field>` molecule).\n */\nexport interface InputProps extends Omit<ComponentProps<'input'>, 'size'>, InputVariantProps {}\n\nexport function Input({ size, className, type = 'text', ref, ...rest }: InputProps) {\n const resolvedSize = size ?? 'md';\n return (\n <input\n ref={ref}\n type={type}\n data-pharos-size={resolvedSize}\n className={cn(inputVariants({ size: resolvedSize }), className)}\n {...rest}\n />\n );\n}\n\nInput.displayName = 'Input';\n\nexport { inputVariants };\n","/* Textarea.module.css\n *\n * Per-component CSS Modules definition (Decision D9). The build emits hashed\n * class names (`Textarea_textarea__xxxx`) into `dist/styles.css`; the runtime\n * JS references them through the `styles` object imported by Textarea.tsx.\n *\n * Every visual value comes from a `--pharos-*` token. No hex, no Tailwind,\n * no preflight. Hover / focus-visible / invalid / disabled states are\n * authored with native CSS pseudo-classes and the `[aria-invalid=\"true\"]`\n * attribute selector — the component does not own any \"error mode\" state\n * itself; it reflects what the consumer puts on the DOM.\n *\n * The chrome (border, radius, focus ring, invalid ring, disabled opacity)\n * is identical to `Input.module.css` so the two atoms read as siblings of\n * the same form-control family. The two genuine differences are:\n * - `min-height` instead of fixed `height` (textareas grow with content).\n * - `resize: vertical` (state-of-the-art shadcn default; consumer can\n * override via `style.resize` if needed).\n */\n\n.textarea {\n /* Strip the user-agent textarea chrome. With preflight gone from the\n * consumer, this is the only line that normalizes the rendering across\n * browsers. */\n appearance: none;\n -webkit-appearance: none;\n\n /* === Box model === */\n box-sizing: border-box;\n width: 100%;\n margin: 0;\n /* `neutral-500` lands the resting border at ~5.4:1 contrast against a\n * white surface, clearing WCAG 1.4.11 (Non-text Contrast, 3:1 for UI\n * component boundaries). Same tone Input uses; the form-control family\n * shares the resting border. */\n border: 1px solid var(--pharos-color-neutral-500);\n border-radius: var(--pharos-radius-md);\n background-color: var(--pharos-color-base-white);\n color: var(--pharos-color-neutral-900);\n\n /* === Layout ===\n * `resize: vertical` matches shadcn / Mantine / Radix Themes — the user\n * can drag the bottom edge to grow the textarea but cannot stretch it\n * horizontally (which would break form layouts). Consumers can opt out\n * via `style={{ resize: 'none' }}` if needed. */\n resize: vertical;\n\n /* === Typography ===\n * `font-family` and weight are explicit so the textarea does not inherit\n * a system-ui fallback when it lives inside surfaces that haven't set the\n * Pharos sans stack. Line-height 1.5 is more legible than Input's 1.25\n * because users frequently paste multi-line content here. */\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n font-weight: var(--pharos-font-weight-regular);\n line-height: 1.5;\n\n /* === Transitions === */\n transition-property: border-color, box-shadow;\n transition-duration: var(--pharos-duration-normal);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.textarea::placeholder {\n color: var(--pharos-color-neutral-500);\n opacity: 1;\n}\n\n.textarea:focus-visible {\n outline: none;\n border-color: var(--pharos-color-primary-600);\n /* Two-stop box-shadow mirrors Input + Button focus rings: an inner halo\n * in the surface color separates the ring from the textarea fill, and\n * the outer stop is the brand accent. Same tokens as Input so the\n * system has a single focus look across every form control. */\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-primary-600);\n}\n\n.textarea:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n /* Dragging the resize handle on a disabled control is jarring; mirror\n * `cursor: not-allowed` by removing the affordance entirely. */\n resize: none;\n}\n\n.textarea[aria-invalid='true'] {\n border-color: var(--pharos-color-semantic-error-fg);\n}\n\n.textarea[aria-invalid='true']:focus-visible {\n border-color: var(--pharos-color-semantic-error-fg);\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-semantic-error-fg);\n}\n\n/* === Sizes ===\n *\n * Inline padding matches Input exactly so a textarea sits flush next to an\n * input of the same size (no horizontal jog when stacked). Vertical padding\n * is set explicitly because textareas don't have the fixed-height that lets\n * Input use vertical centring. `min-height` is sized to ~3-4 lines of body\n * text at the corresponding font-size; `resize: vertical` lets the user\n * grow it past that.\n *\n * Font-size hops to `base` (16px) at `lg` for the same reason Input does:\n * larger controls usually pair with prominent layouts where 14px feels\n * undersized. */\n\n.sizeSm {\n min-height: var(--pharos-spacing-16);\n padding: var(--pharos-spacing-2) var(--pharos-spacing-3);\n font-size: var(--pharos-font-size-sm);\n}\n\n.sizeMd {\n min-height: var(--pharos-spacing-20);\n padding: var(--pharos-spacing-2-5) var(--pharos-spacing-4);\n font-size: var(--pharos-font-size-sm);\n}\n\n.sizeLg {\n min-height: var(--pharos-spacing-24);\n padding: var(--pharos-spacing-3) var(--pharos-spacing-6);\n font-size: var(--pharos-font-size-base);\n}\n","import type { ComponentProps } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Textarea.module.css';\n\n/**\n * Maps the `size` axis to per-class names inside `Textarea.module.css`. Like\n * `Input`, `Textarea` has no `variant` axis on purpose — shadcn's primitive\n * contract is a single styled `<textarea>` and the design-system best\n * practice for form controls is one chrome with explicit error / disabled /\n * readonly states. Composition around the textarea (label, helper, error\n * message) belongs to the future `<Field>` molecule, not to this atom\n * (Escuela 1 from the field-pattern decision, 2026-04-27).\n *\n * The three sizes share the resting visual contract with `<Input size>` so\n * a textarea sits flush next to inputs of the same size in a form: identical\n * inline padding, identical resting border tone (`neutral-500`), identical\n * font-size hop at `lg`, identical focus ring. The vertical dimension is\n * `min-height` instead of a fixed `height` because textareas grow with\n * content and the user can drag them taller (`resize: vertical`).\n */\nconst textareaVariants = cva(styles.textarea, {\n variants: {\n size: {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n },\n },\n defaultVariants: {\n size: 'md',\n },\n});\n\ntype TextareaVariantProps = VariantProps<typeof textareaVariants>;\n\n/**\n * Props accepted by `<Textarea>`. Extends every native `<textarea>`\n * attribute. Unlike `<Input>`, no native attribute is shadowed — the native\n * `<textarea>` does not have a `size` attribute, so the typed Pharos size\n * axis takes the slot without collision.\n *\n * Error state is conveyed through the standard `aria-invalid` attribute,\n * not a custom `error` prop. The CSS reacts to `[aria-invalid=\"true\"]`,\n * matching shadcn / Base UI / Mantine. The actual error message is rendered\n * by the consumer next to the textarea (or by the future `<Field>` molecule).\n */\nexport interface TextareaProps extends ComponentProps<'textarea'>, TextareaVariantProps {}\n\nexport function Textarea({ size, className, ref, ...rest }: TextareaProps) {\n const resolvedSize = size ?? 'md';\n return (\n <textarea\n ref={ref}\n data-pharos-size={resolvedSize}\n className={cn(textareaVariants({ size: resolvedSize }), className)}\n {...rest}\n />\n );\n}\n\nTextarea.displayName = 'Textarea';\n\nexport { textareaVariants };\n","/* Separator.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css` like the rest of pharos-react. The token of the\n * border colour comes from D12 (subtle borders for non-interactive\n * surfaces): a Separator is decorative or semantic but never an\n * interactive control, so WCAG 1.4.11 (3:1) does not apply and the\n * resting tone stays at `neutral-200` — same intensity Carbon\n * `border-subtle`, Adobe Spectrum gray-200 and Polaris use for\n * dividers.\n */\n\n.separator {\n /* `flex-shrink: 0` keeps a vertical Separator at 1px wide inside flex\n * rows that would otherwise compress it to zero. shadcn does the same\n * via the Tailwind `shrink-0` utility; we hard-code it because every\n * Separator wants this behaviour and there is no axis where dropping\n * it would be useful. */\n flex-shrink: 0;\n background-color: var(--pharos-color-neutral-200);\n /* Reset any user-agent margin (notably the default `<hr>` margin) when\n * the Separator is rendered as a `<div>`. A consumer that wants\n * spacing controls it from the outside via flex / margin / padding,\n * not via implicit user-agent rules. */\n margin: 0;\n border: 0;\n}\n\n.horizontal {\n width: 100%;\n height: 1px;\n}\n\n.vertical {\n height: 100%;\n width: 1px;\n}\n","import type { ComponentPropsWithoutRef } from 'react';\nimport { cn } from '../lib/cn';\nimport styles from './Separator.module.css';\n\n/**\n * Visual or semantic divider between two regions. Renders a thin line in\n * either orientation. Two contracts coexist behind the same component:\n *\n * - **Decorative** (default, `decorative={true}`) — the line is purely\n * visual chrome. Marked `role=\"none\"` so assistive tech ignores it,\n * the same way shadcn / Radix Separator do.\n * - **Semantic** (`decorative={false}`) — the line announces a logical\n * partition (e.g. between groups in a menu). Marked `role=\"separator\"`\n * and, for vertical separators, `aria-orientation=\"vertical\"` per the\n * ARIA APG (the spec defaults `aria-orientation` to \"horizontal\" for\n * role=separator, so we emit the attribute only when it differs).\n *\n * The resting border tone is `--pharos-color-neutral-200` (Decision D12:\n * subtle borders for non-interactive surfaces). WCAG 1.4.11 does not\n * apply to decorative separators or to the boundaries of non-interactive\n * containers; the system reserves the stronger `neutral-500` tone for\n * interactive controls (Input, Button secondary, Badge outline — D10).\n */\nexport interface SeparatorProps extends ComponentPropsWithoutRef<'div'> {\n orientation?: 'horizontal' | 'vertical';\n decorative?: boolean;\n}\n\nexport function Separator({\n orientation = 'horizontal',\n decorative = true,\n className,\n ...rest\n}: SeparatorProps) {\n return (\n <div\n role={decorative ? 'none' : 'separator'}\n aria-orientation={!decorative && orientation === 'vertical' ? 'vertical' : undefined}\n data-pharos-orientation={orientation}\n className={cn(styles.separator, styles[orientation], className)}\n {...rest}\n />\n );\n}\n\nSeparator.displayName = 'Separator';\n","/* Card.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. The variant tones land on the border-intensity\n * hierarchy from D12: `default` and `elevated` carry the subtle tier,\n * `outlined` carries the emphasised tier. None of them reach the\n * strong tier reserved for interactive controls.\n */\n\n.card {\n /* === Surface === */\n background-color: var(--pharos-color-base-white);\n border-radius: var(--pharos-radius-2xl);\n /* Default to no border / no shadow at the base level — variants opt\n * into one or the other. Keeping the base neutral means a consumer\n * that overrides via `className` does not have to fight defaults. */\n border: 0 solid transparent;\n\n /* === Layout ===\n * Cards default to vertical stacking of their slots (Header / Content /\n * Footer). The slots themselves carry their own padding so the Card\n * never imposes a global padding that would conflict with full-bleed\n * media headers. */\n display: flex;\n flex-direction: column;\n gap: var(--pharos-spacing-6);\n\n /* === Typography ===\n * Set the body baseline for the card so `<CardTitle>` and\n * `<CardDescription>` can size relatively if a future caller wants. */\n font-family: var(--pharos-font-family-sans);\n color: var(--pharos-color-neutral-900);\n\n /* === Transitions === */\n transition-property: border-color, box-shadow;\n transition-duration: var(--pharos-duration-normal);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n/* === Variants === */\n\n.default {\n border: 1px solid var(--pharos-color-neutral-200);\n}\n\n.elevated {\n border: 0 solid transparent;\n box-shadow: var(--pharos-shadow-md);\n}\n\n.outlined {\n border: 1px solid var(--pharos-color-neutral-300);\n}\n\n/* === Slots ===\n *\n * Every slot is a styled `<div>` (CardTitle stays a `<div>` like shadcn,\n * so the consumer picks the semantic heading level). Padding is consistent\n * across the three slots — the consumer composes them inside `<Card>`\n * without thinking about layout details. */\n\n.header {\n display: flex;\n flex-direction: column;\n gap: var(--pharos-spacing-1-5);\n padding: var(--pharos-spacing-6) var(--pharos-spacing-6) 0;\n}\n\n.title {\n margin: 0;\n font-size: var(--pharos-font-size-base);\n font-weight: var(--pharos-font-weight-semibold);\n line-height: 1.25;\n color: var(--pharos-color-neutral-900);\n}\n\n.description {\n margin: 0;\n font-size: var(--pharos-font-size-sm);\n font-weight: var(--pharos-font-weight-regular);\n line-height: 1.5;\n color: var(--pharos-color-neutral-500);\n}\n\n.content {\n /* Header padding-top + Footer padding-bottom already provide the\n * vertical breathing room. Content itself only carries inline padding;\n * its vertical rhythm comes from the parent `gap`. When the card has\n * no Header/Footer, Content's lack of vertical padding means the card\n * is content-only with the card-level `gap` collapsing to zero — the\n * consumer applies their own padding via `style` or `className`. */\n padding: 0 var(--pharos-spacing-6);\n}\n\n.footer {\n display: flex;\n align-items: center;\n gap: var(--pharos-spacing-3);\n padding: 0 var(--pharos-spacing-6) var(--pharos-spacing-6);\n}\n","import type { ComponentPropsWithoutRef } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Card.module.css';\n\n/**\n * Container surface for grouped content. Three variants on the\n * border-intensity hierarchy (Decision D12):\n *\n * - `default` (subtle border, no shadow) — the most common card. Sits\n * on the same `neutral-200` tone as `Separator`, so panels and\n * dividers harmonise visually.\n * - `elevated` (no border, soft shadow) — for surfaces that should lift\n * off the canvas (modals' inner cards, important callouts).\n * - `outlined` (deliberately stronger border, no shadow) — emphasised\n * container. Sits on `neutral-300`, between subtle non-interactive\n * tone and the strong interactive tone reserved for controls.\n *\n * Sub-components (`CardHeader`, `CardTitle`, `CardDescription`,\n * `CardContent`, `CardFooter`) are slot primitives, not separate atoms\n * — same pattern shadcn uses. They are exported individually so the\n * consumer composes the structure they need; an empty Card with no\n * slots is equally valid.\n */\nconst cardVariants = cva(styles.card, {\n variants: {\n variant: {\n default: styles.default,\n elevated: styles.elevated,\n outlined: styles.outlined,\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n});\n\ntype CardVariantProps = VariantProps<typeof cardVariants>;\n\nexport interface CardProps extends ComponentPropsWithoutRef<'div'>, CardVariantProps {}\n\nexport function Card({ variant, className, ...rest }: CardProps) {\n const resolvedVariant = variant ?? 'default';\n return (\n <div\n data-pharos-variant={resolvedVariant}\n className={cn(cardVariants({ variant: resolvedVariant }), className)}\n {...rest}\n />\n );\n}\n\nCard.displayName = 'Card';\n\nexport interface CardHeaderProps extends ComponentPropsWithoutRef<'div'> {}\n\nexport function CardHeader({ className, ...rest }: CardHeaderProps) {\n return <div data-pharos-slot=\"card-header\" className={cn(styles.header, className)} {...rest} />;\n}\n\nCardHeader.displayName = 'CardHeader';\n\nexport interface CardTitleProps extends ComponentPropsWithoutRef<'div'> {}\n\nexport function CardTitle({ className, ...rest }: CardTitleProps) {\n // Renders as a `<div>` so the consumer picks the semantic heading\n // level (`<h2>`, `<h3>`, etc.) by wrapping or by using the native\n // `role` and `aria-level` attributes if needed. Same default as\n // shadcn's CardTitle — atoms do not impose document outline.\n return <div data-pharos-slot=\"card-title\" className={cn(styles.title, className)} {...rest} />;\n}\n\nCardTitle.displayName = 'CardTitle';\n\nexport interface CardDescriptionProps extends ComponentPropsWithoutRef<'p'> {}\n\nexport function CardDescription({ className, ...rest }: CardDescriptionProps) {\n return (\n <p\n data-pharos-slot=\"card-description\"\n className={cn(styles.description, className)}\n {...rest}\n />\n );\n}\n\nCardDescription.displayName = 'CardDescription';\n\nexport interface CardContentProps extends ComponentPropsWithoutRef<'div'> {}\n\nexport function CardContent({ className, ...rest }: CardContentProps) {\n return (\n <div data-pharos-slot=\"card-content\" className={cn(styles.content, className)} {...rest} />\n );\n}\n\nCardContent.displayName = 'CardContent';\n\nexport interface CardFooterProps extends ComponentPropsWithoutRef<'div'> {}\n\nexport function CardFooter({ className, ...rest }: CardFooterProps) {\n return <div data-pharos-slot=\"card-footer\" className={cn(styles.footer, className)} {...rest} />;\n}\n\nCardFooter.displayName = 'CardFooter';\n\nexport { cardVariants };\n","/* Avatar.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Compound:\n * `Avatar` (root), `AvatarImage`, `AvatarFallback`, `AvatarGroup`. The\n * build emits hashed class names (`Avatar_root__xxxx`) into\n * `dist/styles.css`; the runtime JS references them through the\n * `styles` object imported by Avatar.tsx.\n *\n * Sizes (D14): 3 named (sm / md / lg = 32 / 40 / 48 px) aligned to the\n * IconButton height grid. Consumers needing a one-off (a 108px profile\n * picture, a 20px stack avatar) pass `size={number}` and the runtime\n * sets `width` / `height` inline — same escape pattern Mantine /\n * Ant Design ship. The `--pharos-avatar-overlap` CSS variable cascades\n * to AvatarGroup so the negative margin scales with the chosen size.\n *\n * Shape: `circle` (default) via `border-radius: full`; `square` via\n * `radius-md`. The atom does not impose a breakpoint-conditional\n * shape — that is the consumer's call.\n */\n\n.root {\n /* === Box model ===\n * Square dimensions with `border-radius` doing the circle/rounded\n * shape. `flex-shrink: 0` prevents the avatar from collapsing inside\n * a flex row (typical in stacks and people lists). `overflow: hidden`\n * clips the inner `<img>` to the shape — the same approach Radix /\n * Base UI document.\n *\n * `position: relative` anchors the absolutely-positioned image so it\n * stacks on top of the fallback. Without this the image and the\n * fallback would compete as siblings of the same flex container, and\n * during the loading window both would render at `width: 100%` —\n * shrinking to 50/50 inside the avatar (visible as a split chrome\n * until the image paints). Layering the image on top keeps the\n * fallback as the single flex item that centres its content, and\n * the image fades in over it once the browser decodes the pixels. */\n position: relative;\n box-sizing: border-box;\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n\n /* === Visual baseline ===\n * Neutral surface behind the fallback so the slot reads as a defined\n * shape even before the image (or text) renders. `color: inherit`\n * lets icons inside Fallback pick the parent's text colour. */\n background-color: var(--pharos-color-neutral-100);\n color: var(--pharos-color-neutral-700);\n font-family: var(--pharos-font-family-sans);\n font-weight: 500;\n line-height: 1;\n user-select: none;\n\n /* Default overlap step for grouped avatars. The size classes below\n * override it so the negative margin scales with the avatar. */\n --pharos-avatar-overlap: calc(var(--pharos-spacing-2) * -1); /* -8px */\n}\n\n/* === Shape === */\n\n.shapeCircle {\n border-radius: var(--pharos-radius-full);\n}\n\n.shapeSquare {\n /* Matches the `Card` outlined corner radius family — squared avatars\n * (org logos, product icons) sit consistently next to cards on the\n * same surface. */\n border-radius: var(--pharos-radius-md);\n}\n\n/* === Sizes ===\n *\n * Named sizes match the IconButton grid (32 / 40 / 48 px). Initials\n * type scale follows the body 14 / 20 rhythm (D3): sm shrinks to 12 px,\n * md / lg sit at 14 / 16 px. The fallback `font-size` is set on\n * `.root` so the text reflows correctly when the consumer passes an\n * inline-style override.\n *\n * Each size also pegs the `--pharos-avatar-overlap` so AvatarGroup\n * pulls the next sibling proportionally — ~20% of the avatar diameter,\n * the same ratio MUI / Chakra / Mantine ship by default. */\n\n.sizeSm {\n width: var(--pharos-spacing-8); /* 32 px */\n height: var(--pharos-spacing-8);\n font-size: 0.75rem; /* 12 px */\n --pharos-avatar-overlap: calc(var(--pharos-spacing-2) * -0.75); /* -6 px */\n}\n\n.sizeMd {\n width: var(--pharos-spacing-10); /* 40 px */\n height: var(--pharos-spacing-10);\n font-size: 0.875rem; /* 14 px */\n --pharos-avatar-overlap: calc(var(--pharos-spacing-2) * -1); /* -8 px */\n}\n\n.sizeLg {\n width: var(--pharos-spacing-12); /* 48 px */\n height: var(--pharos-spacing-12);\n font-size: 1rem; /* 16 px */\n --pharos-avatar-overlap: calc(var(--pharos-spacing-2) * -1.25); /* -10 px */\n}\n\n/* === Image ===\n *\n * The img fills the root completely with `object-fit: cover` so a\n * non-square portrait crops to the avatar shape without distortion.\n * `position: absolute` lifts the image out of the flex flow so it can\n * stack on top of the fallback during the load window — without this\n * the two would split the root's width 50/50 because each carries\n * `width: 100%` (visible as a fractured chrome on size=lg, story\n * grids, and any docs page that renders multiple Avatars before the\n * images decode). */\n\n.image {\n position: absolute;\n inset: 0;\n display: block;\n width: 100%;\n height: 100%;\n object-fit: cover;\n /* Hairline border-radius inheritance to keep clipping crisp under\n * sub-pixel rendering — `overflow: hidden` on the root handles the\n * clip but the inner `<img>` repeating the radius prevents a\n * one-pixel halo on some browsers. */\n border-radius: inherit;\n}\n\n/* === Fallback === */\n\n.fallback {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n text-align: center;\n /* Inherit the typography baked into the root via `.sizeX`. */\n font: inherit;\n color: inherit;\n}\n\n/* The Fallback owns a direct-child `> svg` sizing rule for icon-only\n * fallbacks (e.g. `<UserIcon/>`). Icons render at ~60% of the avatar\n * diameter — matching the Material 3 / MUI default when an Avatar\n * falls back to its generic person silhouette. */\n\n.sizeSm .fallback > svg {\n width: var(--pharos-spacing-5); /* 20 px ≈ 62.5% of 32 */\n height: var(--pharos-spacing-5);\n}\n\n.sizeMd .fallback > svg {\n width: var(--pharos-spacing-6); /* 24 px = 60% of 40 */\n height: var(--pharos-spacing-6);\n}\n\n.sizeLg .fallback > svg {\n width: var(--pharos-spacing-7); /* 28 px ≈ 58% of 48 */\n height: var(--pharos-spacing-7);\n}\n\n/* === Group ===\n *\n * Stack avatars with negative left margin. The first child sits flush;\n * each subsequent child slides under the previous one. The `ring` is\n * applied as a `box-shadow` (not `border`) so it does not shrink the\n * avatar's content box — same trick Tailwind UI / MUI use. The ring\n * colour reads from a CSS var so the consumer can match the\n * surrounding surface (e.g. a card with a tinted background). */\n\n.group {\n display: inline-flex;\n align-items: center;\n /* Direction control: `flex-direction: row-reverse` would reverse\n * stacking order; we keep the default and let the consumer pass\n * children in the order they prefer. The first-rendered child sits\n * on top of the stack because z-index increases via :nth-child\n * below. */\n}\n\n.group > .root {\n /* The ring fills the gap that the negative margin opens between\n * stacked avatars so each silhouette reads as a separate shape.\n * `--pharos-avatar-group-ring` defaults to white (the page surface);\n * a consumer with a tinted backdrop overrides it. */\n box-shadow: 0 0 0 2px var(--pharos-avatar-group-ring, var(--pharos-color-base-white));\n /* Position relative + z-index lets the box-shadow ring sit above\n * the next sibling so the gap stays visible. */\n position: relative;\n}\n\n.group > .root + .root {\n margin-left: var(--pharos-avatar-overlap);\n}\n\n/* === Loading status data attributes ===\n *\n * The Image element exposes `data-pharos-loading` while it transitions\n * through `idle` → `loaded` / `error`. The Fallback reads the same\n * attribute on the root via context (no CSS selector needed, but the\n * data attribute remains queryable from e2e tests). */\n","'use client';\n\n/* The `'use client'` directive at the top of this file is load-bearing.\n * Avatar runs `createContext(...)` three times at module top level (shape /\n * loading / group). Without the directive, Next.js evaluates the module in\n * its build-time RSC analysis pass, where React is wired in a way that\n * makes `createContext` blow up with\n * `TypeError: (0 , c.createContext) is not a function` during page-data\n * collection. The directive marks the module as client-only in the\n * published bundle (esbuild / Vite preserve the banner verbatim) and the\n * RSC analysis stops at the import boundary.\n *\n * The other Pharos atoms (Button / Spinner / etc.) do not need this\n * because their hook calls happen inside the component body, not at module\n * scope — module evaluation is side-effect free for them.\n */\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ComponentPropsWithoutRef,\n type CSSProperties,\n type ReactNode,\n type Ref,\n} from 'react';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cn } from '../lib/cn';\nimport styles from './Avatar.module.css';\n\n/**\n * Compound atom (Decision D14). The state-of-the-art convention across\n * shadcn / Radix / Base UI / Chakra is to expose `Avatar`,\n * `AvatarImage`, and `AvatarFallback` as discrete parts so the consumer\n * composes the fallback content (initials, icon, default image)\n * directly — same Escuela 1 composition principle Pharos applies for\n * Input / Textarea (D11).\n *\n * Two contexts coordinate the parts:\n *\n * - `AvatarShapeContext` carries the resolved `size` / `shape` for\n * nested `> svg` sizing and AvatarGroup cascade.\n * - `AvatarLoadingContext` carries the image load status so\n * `AvatarFallback` only renders when the image is missing or has\n * failed — the same render-gate pattern Radix Primitives document.\n *\n * The group reads `AvatarGroupContext` to cascade `size` / `shape` to\n * every Avatar child and to inject the overflow \"+N\" badge when the\n * children exceed `max`.\n */\n\ntype AvatarSize = 'sm' | 'md' | 'lg' | number;\ntype AvatarShape = 'circle' | 'square';\n\ninterface AvatarShapeContextValue {\n size: AvatarSize;\n shape: AvatarShape;\n}\n\nconst AvatarShapeContext = createContext<AvatarShapeContextValue | null>(null);\n\ntype AvatarLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error';\n\ninterface AvatarLoadingContextValue {\n status: AvatarLoadingStatus;\n setStatus: (next: AvatarLoadingStatus) => void;\n}\n\nconst AvatarLoadingContext = createContext<AvatarLoadingContextValue | null>(null);\n\ninterface AvatarGroupContextValue {\n size: AvatarSize;\n shape: AvatarShape;\n}\n\nconst AvatarGroupContext = createContext<AvatarGroupContextValue | null>(null);\n\nconst SIZE_CLASSNAME: Record<Exclude<AvatarSize, number>, string> = {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n};\n\nconst SHAPE_CLASSNAME: Record<AvatarShape, string> = {\n circle: styles.shapeCircle,\n square: styles.shapeSquare,\n};\n\nfunction resolveSizeStyle(size: AvatarSize): CSSProperties | undefined {\n if (typeof size !== 'number') return undefined;\n // Numeric escape: consumer-supplied one-off dimension. Font-size\n // follows the 0.4 ratio the named sizes (12/14/16 at 32/40/48) imply\n // so an initials fallback still reads correctly inside a non-grid\n // avatar.\n return {\n width: size,\n height: size,\n fontSize: `${Math.round(size * 0.4)}px`,\n };\n}\n\nfunction resolveSizeClassName(size: AvatarSize): string | undefined {\n if (typeof size === 'number') return undefined;\n return SIZE_CLASSNAME[size];\n}\n\n// ---------------------------------------------------------------------------\n// Avatar (Root)\n// ---------------------------------------------------------------------------\n\nexport interface AvatarProps extends Omit<useRender.ComponentProps<'span'>, 'children'> {\n /**\n * Avatar dimensions. Named sizes (`sm` / `md` / `lg` = 32 / 40 / 48 px)\n * align with the IconButton / Button height grid. A numeric value\n * sets one-off `width` / `height` / `font-size` inline — use it for\n * profile-picture sized images (e.g. 108 px) or compact stacks\n * (20 px) outside the canonical grid.\n *\n * @default 'md'\n */\n size?: AvatarSize;\n /**\n * Border-radius family. `circle` (default) clips the avatar to a\n * perfect circle via `border-radius: full`; `square` uses the\n * `radius-md` token so an org or product avatar harmonises with the\n * Card corner family on the same surface.\n *\n * @default 'circle'\n */\n shape?: AvatarShape;\n /**\n * The Image and Fallback parts, plus any content the consumer wishes\n * to layer (status badge, ring overlay, etc.). The atom does not\n * inject a default `<AvatarFallback>` — composition is explicit so\n * the consumer is in control of the fallback content (initials,\n * icon, default PNG).\n */\n children?: ReactNode;\n}\n\nexport function Avatar({\n size,\n shape,\n className,\n style,\n render,\n ref,\n children,\n ...rest\n}: AvatarProps) {\n const group = useContext(AvatarGroupContext);\n const resolvedSize: AvatarSize = size ?? group?.size ?? 'md';\n const resolvedShape: AvatarShape = shape ?? group?.shape ?? 'circle';\n\n const [status, setStatus] = useState<AvatarLoadingStatus>('idle');\n\n const shapeCtx = useMemo<AvatarShapeContextValue>(\n () => ({ size: resolvedSize, shape: resolvedShape }),\n [resolvedSize, resolvedShape],\n );\n const loadingCtx = useMemo<AvatarLoadingContextValue>(() => ({ status, setStatus }), [status]);\n\n // Numeric vs named: the named path applies a size class that owns\n // width / height / font-size / --pharos-avatar-overlap; the numeric\n // path skips the class and writes width / height / font-size inline.\n // The shape class always applies — same border-radius regardless of\n // sizing path.\n const sizeClassName = resolveSizeClassName(resolvedSize);\n const sizeStyle = resolveSizeStyle(resolvedSize);\n\n const resolvedSizeAttr = typeof resolvedSize === 'number' ? String(resolvedSize) : resolvedSize;\n\n return (\n <AvatarShapeContext.Provider value={shapeCtx}>\n <AvatarLoadingContext.Provider value={loadingCtx}>\n {useRender({\n render: render ?? <span />,\n ref,\n defaultTagName: 'span',\n props: {\n 'data-pharos-size': resolvedSizeAttr,\n 'data-pharos-shape': resolvedShape,\n 'data-pharos-loading': status,\n className: cn(styles.root, sizeClassName, SHAPE_CLASSNAME[resolvedShape], className),\n style: sizeStyle ? { ...sizeStyle, ...style } : style,\n ...rest,\n children,\n },\n })}\n </AvatarLoadingContext.Provider>\n </AvatarShapeContext.Provider>\n );\n}\n\nAvatar.displayName = 'Avatar';\n\n// ---------------------------------------------------------------------------\n// AvatarImage\n// ---------------------------------------------------------------------------\n\nexport interface AvatarImageProps extends ComponentPropsWithoutRef<'img'> {\n /**\n * Optional callback invoked when the image transitions between load\n * states. Mirrors Radix / Base UI's `onLoadingStatusChange` so an\n * external consumer can react to the cascade (e.g. emit telemetry\n * when a CDN avatar fails).\n */\n onLoadingStatusChange?: (status: AvatarLoadingStatus) => void;\n ref?: Ref<HTMLImageElement>;\n}\n\nexport function AvatarImage({\n className,\n src,\n onLoad,\n onError,\n onLoadingStatusChange,\n ref,\n ...rest\n}: AvatarImageProps) {\n const loading = useContext(AvatarLoadingContext);\n\n // When no `src` lands, the image stays hidden and the Fallback\n // renders. Same contract Radix and Base UI document. Without this\n // guard the browser would request the current URL relatively\n // (interpreting `undefined` as empty string), polluting the network\n // tab with a useless 404.\n const hasSrc = typeof src === 'string' && src.length > 0;\n\n useEffect(() => {\n if (!loading) return;\n // Whenever `src` changes we re-enter the loading phase. The browser\n // re-fires `onLoad` / `onError` for the new URL; the local state\n // resets here so the Fallback returns until the new load resolves.\n loading.setStatus(hasSrc ? 'loading' : 'idle');\n onLoadingStatusChange?.(hasSrc ? 'loading' : 'idle');\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [src]);\n\n const handleLoad = useCallback(\n (event: React.SyntheticEvent<HTMLImageElement, Event>) => {\n loading?.setStatus('loaded');\n onLoadingStatusChange?.('loaded');\n onLoad?.(event);\n },\n [loading, onLoad, onLoadingStatusChange],\n );\n\n const handleError = useCallback(\n (event: React.SyntheticEvent<HTMLImageElement, Event>) => {\n loading?.setStatus('error');\n onLoadingStatusChange?.('error');\n onError?.(event);\n },\n [loading, onError, onLoadingStatusChange],\n );\n\n if (!hasSrc) return null;\n if (loading && loading.status === 'error') return null;\n\n return (\n <img\n ref={ref}\n src={src}\n onLoad={handleLoad}\n onError={handleError}\n data-pharos-loading={loading?.status}\n className={cn(styles.image, className)}\n {...rest}\n />\n );\n}\n\nAvatarImage.displayName = 'AvatarImage';\n\n// ---------------------------------------------------------------------------\n// AvatarFallback\n// ---------------------------------------------------------------------------\n\nexport interface AvatarFallbackProps extends ComponentPropsWithoutRef<'span'> {\n /**\n * Milliseconds to wait before rendering the fallback. Useful when\n * the image is likely to load quickly — delaying the fallback avoids\n * a flash of placeholder before the real avatar paints. Same\n * `delayMs` contract Radix / Base UI document. Defaults to `0`\n * (render immediately while the image is still loading or after an\n * error).\n */\n delayMs?: number;\n ref?: Ref<HTMLSpanElement>;\n}\n\nexport function AvatarFallback({\n className,\n delayMs = 0,\n children,\n ref,\n ...rest\n}: AvatarFallbackProps) {\n const loading = useContext(AvatarLoadingContext);\n const [canRender, setCanRender] = useState(delayMs === 0);\n\n useEffect(() => {\n if (delayMs === 0) {\n setCanRender(true);\n return;\n }\n const handle = window.setTimeout(() => setCanRender(true), delayMs);\n return () => window.clearTimeout(handle);\n }, [delayMs]);\n\n // The fallback hides as soon as the image reports `loaded`. Until\n // then (idle / loading / error) the slot is on screen. A consumer\n // that prefers to keep the fallback while the image is mid-flight\n // gets the default; a consumer that only wants the fallback after a\n // hard failure can branch on `data-pharos-loading=\"error\"` from the\n // root.\n if (loading && loading.status === 'loaded') return null;\n if (!canRender) return null;\n\n return (\n <span\n ref={ref}\n data-pharos-slot=\"avatar-fallback\"\n data-pharos-loading={loading?.status}\n className={cn(styles.fallback, className)}\n {...rest}\n >\n {children}\n </span>\n );\n}\n\nAvatarFallback.displayName = 'AvatarFallback';\n\n// ---------------------------------------------------------------------------\n// AvatarGroup\n// ---------------------------------------------------------------------------\n\nexport interface AvatarGroupProps extends ComponentPropsWithoutRef<'div'> {\n /**\n * Default `size` cascaded to every Avatar child that does not set\n * its own. Mirrors MUI / Chakra / Mantine Avatar.Group sizing.\n *\n * @default 'md'\n */\n size?: AvatarSize;\n /**\n * Default `shape` cascaded to every Avatar child that does not set\n * its own.\n *\n * @default 'circle'\n */\n shape?: AvatarShape;\n /**\n * Maximum number of Avatar children to render before an overflow\n * indicator takes over the remaining slots. When the children count\n * exceeds `max`, the group renders `max - 1` avatars followed by a\n * compact \"+N\" Avatar that summarises the surplus.\n *\n * Omitting the prop displays every Avatar without an overflow\n * indicator — the contract MUI / Chakra apply.\n */\n max?: number;\n /**\n * Optional renderer for the overflow indicator. Receives the surplus\n * count and returns the React node placed at the end of the stack\n * (wrapped in an Avatar with matching `size` / `shape`). Defaults to\n * a `+N` text fallback.\n */\n renderOverflow?: (surplus: number) => ReactNode;\n ref?: Ref<HTMLDivElement>;\n}\n\nexport function AvatarGroup({\n size,\n shape,\n max,\n renderOverflow,\n className,\n children,\n ref,\n ...rest\n}: AvatarGroupProps) {\n const resolvedSize: AvatarSize = size ?? 'md';\n const resolvedShape: AvatarShape = shape ?? 'circle';\n\n const childArray = useMemo(() => {\n const list: ReactNode[] = [];\n // `React.Children.toArray` would assign sequential keys, but using\n // a `Children.forEach` keeps the consumer-provided keys intact\n // when present. The order Avatar children appear in the DOM is the\n // order the consumer passed them — first child paints on top.\n let index = 0;\n const items: ReactNode[] = [];\n const append = (child: ReactNode) => {\n items.push(child);\n index += 1;\n };\n if (Array.isArray(children)) {\n children.forEach((child) => append(child));\n } else if (children) {\n append(children);\n }\n return items.concat(list).slice(0, index);\n }, [children]);\n\n const visible =\n typeof max === 'number' && childArray.length > max\n ? childArray.slice(0, Math.max(max - 1, 0))\n : childArray;\n const surplus =\n typeof max === 'number' && childArray.length > max ? childArray.length - visible.length : 0;\n\n const groupCtx = useMemo<AvatarGroupContextValue>(\n () => ({ size: resolvedSize, shape: resolvedShape }),\n [resolvedSize, resolvedShape],\n );\n\n return (\n <AvatarGroupContext.Provider value={groupCtx}>\n <div\n ref={ref}\n data-pharos-slot=\"avatar-group\"\n data-pharos-size={typeof resolvedSize === 'number' ? String(resolvedSize) : resolvedSize}\n data-pharos-shape={resolvedShape}\n className={cn(styles.group, className)}\n {...rest}\n >\n {visible}\n {surplus > 0 ? (\n <Avatar aria-label={`${surplus} more`} data-pharos-slot=\"avatar-group-overflow\">\n <AvatarFallback>\n {renderOverflow ? renderOverflow(surplus) : `+${surplus}`}\n </AvatarFallback>\n </Avatar>\n ) : null}\n </div>\n </AvatarGroupContext.Provider>\n );\n}\n\nAvatarGroup.displayName = 'AvatarGroup';\n","/* DropdownMenu.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. Styles the floating surface (`.content`), the\n * action rows (`.item`, with a `destructive` tone), the structural\n * parts (`.separator`, `.label`, `.group`), and the open/close\n * transition driven by Base UI's `data-*` state attributes.\n *\n * z-index lives on `.positioner` (see below). Base UI portals the menu to the\n * end of `<body>`; that floats it above in-flow content, but not above a\n * host-app overlay (drawer/modal) with its own z-index — and a z-index on the\n * Popup is trapped by the Positioner's transform stacking context. The\n * Positioner therefore carries `z-index: var(--pharos-z-index-popover, …)`,\n * the shared scale Combobox / Select / Popover all use now. */\n\n/* === Positioner (Menu.Positioner) ===\n * z-index lives here, not on `.content` (the Popup). Base UI's Positioner\n * anchors with `transform`, which creates a stacking context — so a z-index\n * on the inner Popup is trapped inside it and cannot rise above a host-app\n * overlay (e.g. a drawer with its own z-index). The z-index must sit on the\n * Positioner itself.\n *\n * Sourced from `--pharos-z-index-popover` (no hardcoded value — NON-NEGOTIABLE\n * rule 1). The fallback suits apps with no high z-index scale; an app whose\n * own overlays sit higher (drawers, modals) raises every Pharos popup at once\n * by overriding `--pharos-z-index-popover` in `:root`. */\n.positioner {\n z-index: var(--pharos-z-index-popover, 1000);\n}\n\n/* === Floating surface (Menu.Popup) === */\n.content {\n box-sizing: border-box;\n min-width: 12rem;\n padding: var(--pharos-spacing-1);\n\n background-color: var(--pharos-color-base-white);\n border: 1px solid var(--pharos-color-neutral-200);\n border-radius: var(--pharos-radius-md);\n box-shadow: var(--pharos-shadow-lg);\n\n font-family: var(--pharos-font-family-sans);\n color: var(--pharos-color-neutral-900);\n\n /* Open/close transition keyed off Base UI's `data-starting-style` /\n * `data-ending-style`. The popup fades and lifts a few pixels from\n * the anchored side — the same motion shadcn / Radix ship. */\n transform-origin: var(--transform-origin);\n transition-property: opacity, transform;\n transition-duration: var(--pharos-duration-fast);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.content[data-starting-style],\n.content[data-ending-style] {\n opacity: 0;\n transform: scale(0.96);\n}\n\n/* === Action row (Menu.Item) === */\n.item {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: var(--pharos-spacing-2);\n width: 100%;\n padding: var(--pharos-spacing-1-5) var(--pharos-spacing-2);\n\n border-radius: var(--pharos-radius-sm);\n font-size: var(--pharos-font-size-sm);\n font-weight: var(--pharos-font-weight-medium);\n line-height: 1.25;\n color: var(--pharos-color-neutral-700);\n\n cursor: default;\n user-select: none;\n outline: none;\n scroll-margin: var(--pharos-spacing-1);\n\n transition-property: background-color, color;\n transition-duration: var(--pharos-duration-fast);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n/* Base UI marks the active row (keyboard navigation or pointer, when\n * `highlightItemOnHover` is on) with `data-highlighted`. A single\n * highlight state covers both pointer hover and arrow-key focus, so\n * the menu reads identically whichever input drives it. */\n.item[data-highlighted] {\n background-color: var(--pharos-color-neutral-100);\n color: var(--pharos-color-neutral-900);\n}\n\n.item[data-disabled] {\n color: var(--pharos-color-neutral-500);\n opacity: 0.6;\n pointer-events: none;\n}\n\n/* Icons composed inside the row inherit the row colour and size to the\n * text cap height, matching the IconButton `> svg` convention. */\n.item > svg {\n width: 1rem;\n height: 1rem;\n flex-shrink: 0;\n}\n\n/* === Destructive tone ===\n * shadcn ships a `destructive` variant on DropdownMenuItem; Alexandria\n * has several delete / remove actions (career-ladder delete, lesson\n * delete, RemoveBox) that map onto it. The error foreground darkens on\n * highlight the same way the destructive Button does. */\n.itemDestructive {\n color: var(--pharos-color-semantic-error-fg);\n}\n\n.itemDestructive[data-highlighted] {\n background-color: var(--pharos-color-semantic-error-bg);\n color: var(--pharos-color-semantic-error-fg-hover);\n}\n\n/* === Separator (Menu.Separator) === */\n.separator {\n height: 1px;\n margin: var(--pharos-spacing-1) calc(var(--pharos-spacing-1) * -1);\n background-color: var(--pharos-color-neutral-200);\n}\n\n/* === Group label (Menu.GroupLabel) === */\n.label {\n padding: var(--pharos-spacing-1-5) var(--pharos-spacing-2);\n font-size: var(--pharos-font-size-sm);\n font-weight: var(--pharos-font-weight-semibold);\n color: var(--pharos-color-neutral-500);\n}\n","import type { ComponentProps } from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport { cn } from '../lib/cn';\nimport styles from './DropdownMenu.module.css';\n\n/**\n * Action menu anchored to a trigger (Decision D15). Wraps Base UI's\n * `Menu.*` parts, which implement the ARIA APG **menu button** pattern:\n * `role=\"menu\"` / `menuitem`, roving focus, arrow-key navigation,\n * typeahead, and Escape-to-close with focus return to the trigger.\n *\n * This is deliberately distinct from a future `Popover` atom. A\n * DropdownMenu is for **commands** (the menu-button contract); free-form\n * anchored content (forms, navigation links) belongs in a Popover (the\n * disclosure/dialog contract). 7 of 8 surveyed design systems separate\n * the two for exactly this reason — putting `role=\"menu\"` around\n * arbitrary content breaks screen-reader expectations. See\n * `NAMING-decisions.md` § DropdownMenu.\n *\n * Naming follows shadcn (`DropdownMenu*`, not Base UI's `Menu`). The\n * compound surface collapses Base UI's `Portal` + `Positioner` + `Popup`\n * into a single `DropdownMenuContent` with `side` / `align` /\n * `sideOffset` props — the same ergonomics shadcn exposes over Radix.\n *\n * v1 ships the parts the known consumers need (kebab row menus, the\n * MobileHeader user actions): Trigger, Content, Item, Separator, Label,\n * Group. `CheckboxItem`, `RadioItem`, submenus and `LinkItem` are\n * deliberately deferred — no Alexandria call-site exercises them, and the\n * compound API makes them additive without a breaking change.\n *\n * @example\n * <DropdownMenu>\n * <DropdownMenuTrigger render={<IconButton aria-label=\"Options\"><MoreIcon /></IconButton>} />\n * <DropdownMenuContent>\n * <DropdownMenuItem onClick={onRename}>Rename</DropdownMenuItem>\n * <DropdownMenuSeparator />\n * <DropdownMenuItem variant=\"destructive\" onClick={onDelete}>Delete</DropdownMenuItem>\n * </DropdownMenuContent>\n * </DropdownMenu>\n */\nexport type DropdownMenuProps = ComponentProps<typeof Menu.Root>;\n\nexport function DropdownMenu(props: DropdownMenuProps) {\n return <Menu.Root {...props} />;\n}\n\nDropdownMenu.displayName = 'DropdownMenu';\n\n/**\n * The element that opens the menu. Renders a native `<button>` by\n * default; pass `render` to compose any control while keeping the menu\n * wiring — the dominant case is the kebab `IconButton` (D13):\n * `<DropdownMenuTrigger render={<IconButton aria-label=\"Options\"><MoreIcon /></IconButton>} />`.\n */\nexport type DropdownMenuTriggerProps = ComponentProps<typeof Menu.Trigger>;\n\nexport function DropdownMenuTrigger(props: DropdownMenuTriggerProps) {\n return <Menu.Trigger {...props} />;\n}\n\nDropdownMenuTrigger.displayName = 'DropdownMenuTrigger';\n\n/**\n * The floating surface. Collapses Base UI's `Portal` + `Positioner` +\n * `Popup` so the consumer positions the menu with the three props that\n * actually matter at the call site. Defaults match the common dropdown:\n * opens below the trigger, aligned to its start edge, 8px away.\n */\nexport interface DropdownMenuContentProps extends ComponentProps<typeof Menu.Popup> {\n /**\n * Which side of the trigger the menu opens on.\n * @default 'bottom'\n */\n side?: ComponentProps<typeof Menu.Positioner>['side'];\n /**\n * Alignment of the menu relative to the trigger along the chosen side.\n * @default 'start'\n */\n align?: ComponentProps<typeof Menu.Positioner>['align'];\n /**\n * Gap in pixels between the trigger and the menu.\n * @default 8\n */\n sideOffset?: ComponentProps<typeof Menu.Positioner>['sideOffset'];\n}\n\nexport function DropdownMenuContent({\n side = 'bottom',\n align = 'start',\n sideOffset = 8,\n className,\n children,\n ...rest\n}: DropdownMenuContentProps) {\n return (\n <Menu.Portal>\n <Menu.Positioner\n className={styles.positioner}\n side={side}\n align={align}\n sideOffset={sideOffset}\n >\n <Menu.Popup\n data-pharos-slot=\"dropdown-menu-content\"\n className={cn(styles.content, className)}\n {...rest}\n >\n {children}\n </Menu.Popup>\n </Menu.Positioner>\n </Menu.Portal>\n );\n}\n\nDropdownMenuContent.displayName = 'DropdownMenuContent';\n\n/**\n * A single action row. `variant=\"destructive\"` tints the row with the\n * error foreground for delete / remove actions — the same tone the\n * destructive Button uses, and the equivalent of shadcn's destructive\n * item variant. By default a click closes the menu (`closeOnClick`);\n * pass `closeOnClick={false}` for rows that should keep it open.\n */\nexport interface DropdownMenuItemProps extends ComponentProps<typeof Menu.Item> {\n /**\n * Visual tone of the row.\n * @default 'default'\n */\n variant?: 'default' | 'destructive';\n}\n\nexport function DropdownMenuItem({\n variant = 'default',\n className,\n ...rest\n}: DropdownMenuItemProps) {\n return (\n <Menu.Item\n data-pharos-variant={variant}\n className={cn(styles.item, variant === 'destructive' && styles.itemDestructive, className)}\n {...rest}\n />\n );\n}\n\nDropdownMenuItem.displayName = 'DropdownMenuItem';\n\n/** A thin divider between groups of items. */\nexport type DropdownMenuSeparatorProps = ComponentProps<typeof Menu.Separator>;\n\nexport function DropdownMenuSeparator({ className, ...rest }: DropdownMenuSeparatorProps) {\n return (\n <Menu.Separator\n data-pharos-slot=\"dropdown-menu-separator\"\n className={cn(styles.separator, className)}\n {...rest}\n />\n );\n}\n\nDropdownMenuSeparator.displayName = 'DropdownMenuSeparator';\n\n/**\n * A non-interactive heading for a group of items. Renders Base UI's\n * `Menu.GroupLabel`, which wires `aria-labelledby` on the surrounding\n * `DropdownMenuGroup` — so use the two together when a section needs an\n * accessible name.\n */\nexport type DropdownMenuLabelProps = ComponentProps<typeof Menu.GroupLabel>;\n\nexport function DropdownMenuLabel({ className, ...rest }: DropdownMenuLabelProps) {\n return (\n <Menu.GroupLabel\n data-pharos-slot=\"dropdown-menu-label\"\n className={cn(styles.label, className)}\n {...rest}\n />\n );\n}\n\nDropdownMenuLabel.displayName = 'DropdownMenuLabel';\n\n/**\n * Groups related items under an optional `DropdownMenuLabel` for\n * accessible sectioning (`role=\"group\"`).\n */\nexport type DropdownMenuGroupProps = ComponentProps<typeof Menu.Group>;\n\nexport function DropdownMenuGroup(props: DropdownMenuGroupProps) {\n return <Menu.Group data-pharos-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nDropdownMenuGroup.displayName = 'DropdownMenuGroup';\n","/* Popover.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. Styles the floating surface (`.content`), the dialog\n * labelling parts (`.title`, `.description`), and the open/close transition\n * driven by Base UI's `data-*` state attributes.\n *\n * Unlike DropdownMenu, a Popover carries arbitrary content rather than a\n * list of rows, so the surface uses a more generous block padding and a\n * `max-width` to keep prose readable. The `.content` chrome (white surface,\n * subtle border, large shadow, fade-and-scale motion) is intentionally\n * identical to DropdownMenu — both are anchored overlays and should read as\n * the same surface family.\n *\n * z-index lives on `.positioner` (see below). Base UI portals the popover to\n * the end of `<body>`; that floats it above in-flow content, but not above a\n * host-app overlay (drawer/modal) with its own z-index — and a z-index on the\n * Popup is trapped by the Positioner's transform stacking context. The\n * Positioner therefore carries `z-index: var(--pharos-z-index-popover, …)`,\n * the shared scale Combobox / Select / DropdownMenu all use now. */\n\n/* === Positioner (Popover.Positioner) ===\n * z-index lives here, not on `.content` (the Popup). Base UI's Positioner\n * anchors with `transform`, which creates a stacking context — so a z-index\n * on the inner Popup is trapped inside it and cannot rise above a host-app\n * overlay (e.g. a drawer with its own z-index). The z-index must sit on the\n * Positioner itself.\n *\n * Sourced from `--pharos-z-index-popover` (no hardcoded value — NON-NEGOTIABLE\n * rule 1). The fallback suits apps with no high z-index scale; an app whose\n * own overlays sit higher (drawers, modals) raises every Pharos popup at once\n * by overriding `--pharos-z-index-popover` in `:root`. */\n.positioner {\n z-index: var(--pharos-z-index-popover, 1000);\n}\n\n/* === Floating surface (Popover.Popup) === */\n.content {\n box-sizing: border-box;\n max-width: 20rem;\n padding: var(--pharos-spacing-4);\n\n background-color: var(--pharos-color-base-white);\n border: 1px solid var(--pharos-color-neutral-200);\n border-radius: var(--pharos-radius-md);\n box-shadow: var(--pharos-shadow-lg);\n\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n color: var(--pharos-color-neutral-900);\n\n outline: none;\n\n /* Open/close transition keyed off Base UI's `data-starting-style` /\n * `data-ending-style`. The popup fades and lifts a few pixels from the\n * anchored side — the same motion DropdownMenu / shadcn / Radix ship. */\n transform-origin: var(--transform-origin);\n transition-property: opacity, transform;\n transition-duration: var(--pharos-duration-fast);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.content[data-starting-style],\n.content[data-ending-style] {\n opacity: 0;\n transform: scale(0.96);\n}\n\n/* === Title (Popover.Title) ===\n * The accessible name. Renders as <h2> but is sized as body-strong, not as\n * a document heading — a popover title is a label, not page structure. */\n.title {\n margin: 0;\n font-size: var(--pharos-font-size-base);\n font-weight: var(--pharos-font-weight-semibold);\n line-height: 1.4;\n color: var(--pharos-color-neutral-900);\n}\n\n/* === Description (Popover.Description) ===\n * Supporting copy under the title. Sits a hair below the title with the\n * muted foreground used for secondary text across the system. */\n.description {\n margin: var(--pharos-spacing-1) 0 0;\n font-size: var(--pharos-font-size-sm);\n line-height: 1.5;\n color: var(--pharos-color-neutral-600);\n}\n","import type { ComponentProps } from 'react';\nimport { Popover as BasePopover } from '@base-ui/react/popover';\nimport { cn } from '../lib/cn';\nimport styles from './Popover.module.css';\n\n/**\n * Free-form content anchored to a trigger (Decision D16). Wraps Base UI's\n * `Popover.*` parts, which implement the ARIA **dialog** pattern in a\n * **non-modal** disclosure: `role=\"dialog\"` on the popup, focus moves into\n * it on open and returns to the trigger on close, Escape and outside-click\n * dismiss it — but focus is NOT trapped and the rest of the page stays\n * interactive.\n *\n * This is deliberately distinct from the `DropdownMenu` atom (D15). A\n * DropdownMenu is for **commands** (the menu-button contract: `role=\"menu\"`\n * / `menuitem`, roving focus, typeahead); a Popover holds **arbitrary\n * content** — forms, navigation, descriptive panels. 7 of 8 surveyed design\n * systems separate the two for exactly this reason: wrapping `role=\"menu\"`\n * around free-form content breaks screen-reader expectations. The two atoms\n * wrap different Base UI primitives (`Menu` vs `Popover`) — they do not\n * share machinery. See `NAMING-decisions.md` § Popover.\n *\n * Naming follows shadcn (`Popover*`). The compound surface collapses Base\n * UI's `Portal` + `Positioner` + `Popup` into a single `PopoverContent`\n * with `side` / `align` / `sideOffset` / `alignOffset` props — the same\n * ergonomics shadcn exposes over Radix, mirroring the Pharos DropdownMenu.\n *\n * v1 ships the parts the known consumers need (the MobileHeader user panel,\n * the \"add users\" / \"add skills\" panels): Trigger, Content, Close, Title,\n * Description. `Arrow`, an `Anchor` (anchoring to an element other than the\n * trigger — the future Combobox will want it) and a modal `Backdrop` are\n * deliberately deferred; the compound API makes them additive without a\n * breaking change.\n *\n * @example\n * <Popover>\n * <PopoverTrigger render={<IconButton aria-label=\"Account\"><Avatar … /></IconButton>} />\n * <PopoverContent>\n * <PopoverTitle>Signed in as Ada</PopoverTitle>\n * <PopoverDescription>ada@example.com</PopoverDescription>\n * <button onClick={onSignOut}>Sign out</button>\n * </PopoverContent>\n * </Popover>\n */\nexport type PopoverProps = ComponentProps<typeof BasePopover.Root>;\n\nexport function Popover(props: PopoverProps) {\n return <BasePopover.Root {...props} />;\n}\n\nPopover.displayName = 'Popover';\n\n/**\n * The element that opens the popover. Renders a native `<button>` by\n * default; pass `render` to compose any control while keeping the popover\n * wiring — e.g. the MobileHeader avatar:\n * `<PopoverTrigger render={<IconButton aria-label=\"Account\">…</IconButton>} />`.\n */\nexport type PopoverTriggerProps = ComponentProps<typeof BasePopover.Trigger>;\n\nexport function PopoverTrigger(props: PopoverTriggerProps) {\n return <BasePopover.Trigger {...props} />;\n}\n\nPopoverTrigger.displayName = 'PopoverTrigger';\n\n/**\n * The floating surface. Collapses Base UI's `Portal` + `Positioner` +\n * `Popup` so the consumer positions the popover with the four props that\n * actually matter at the call site. Defaults match the common popover:\n * opens below the trigger, centred on it, 8px away.\n *\n * `align` defaults to `center` (not `start` like DropdownMenu): a menu opens\n * flush to the trigger's start edge, but a popover carries arbitrary content\n * and reads better centred — the shadcn / Radix / Base UI default.\n */\nexport interface PopoverContentProps extends ComponentProps<typeof BasePopover.Popup> {\n /**\n * Which side of the trigger the popover opens on.\n * @default 'bottom'\n */\n side?: ComponentProps<typeof BasePopover.Positioner>['side'];\n /**\n * Alignment of the popover relative to the trigger along the chosen side.\n * @default 'center'\n */\n align?: ComponentProps<typeof BasePopover.Positioner>['align'];\n /**\n * Gap in pixels between the trigger and the popover.\n * @default 8\n */\n sideOffset?: ComponentProps<typeof BasePopover.Positioner>['sideOffset'];\n /**\n * Offset in pixels along the alignment axis.\n * @default 0\n */\n alignOffset?: ComponentProps<typeof BasePopover.Positioner>['alignOffset'];\n}\n\nexport function PopoverContent({\n side = 'bottom',\n align = 'center',\n sideOffset = 8,\n alignOffset = 0,\n className,\n children,\n ...rest\n}: PopoverContentProps) {\n return (\n <BasePopover.Portal>\n <BasePopover.Positioner\n className={styles.positioner}\n side={side}\n align={align}\n sideOffset={sideOffset}\n alignOffset={alignOffset}\n >\n <BasePopover.Popup\n data-pharos-slot=\"popover-content\"\n className={cn(styles.content, className)}\n {...rest}\n >\n {children}\n </BasePopover.Popup>\n </BasePopover.Positioner>\n </BasePopover.Portal>\n );\n}\n\nPopoverContent.displayName = 'PopoverContent';\n\n/**\n * An accessible name for the popover. Renders Base UI's `Popover.Title`,\n * which wires `aria-labelledby` on the popup — the dialog labelling that\n * distinguishes a Popover from a Menu. Renders as a `<h2>` by default; pass\n * `render` to change the element.\n */\nexport type PopoverTitleProps = ComponentProps<typeof BasePopover.Title>;\n\nexport function PopoverTitle({ className, ...rest }: PopoverTitleProps) {\n return (\n <BasePopover.Title\n data-pharos-slot=\"popover-title\"\n className={cn(styles.title, className)}\n {...rest}\n />\n );\n}\n\nPopoverTitle.displayName = 'PopoverTitle';\n\n/**\n * A supporting description for the popover. Renders Base UI's\n * `Popover.Description`, which wires `aria-describedby` on the popup.\n * Renders as a `<p>` by default.\n */\nexport type PopoverDescriptionProps = ComponentProps<typeof BasePopover.Description>;\n\nexport function PopoverDescription({ className, ...rest }: PopoverDescriptionProps) {\n return (\n <BasePopover.Description\n data-pharos-slot=\"popover-description\"\n className={cn(styles.description, className)}\n {...rest}\n />\n );\n}\n\nPopoverDescription.displayName = 'PopoverDescription';\n\n/**\n * A button that closes the popover. Renders Base UI's `Popover.Close` (a\n * native `<button>`); compose any control via `render`. Unlike a menu — whose\n * items close it on selection — a popover holds content that stays open until\n * explicitly dismissed, so an in-popup close affordance is the common case.\n */\nexport type PopoverCloseProps = ComponentProps<typeof BasePopover.Close>;\n\nexport function PopoverClose(props: PopoverCloseProps) {\n return <BasePopover.Close data-pharos-slot=\"popover-close\" {...props} />;\n}\n\nPopoverClose.displayName = 'PopoverClose';\n","/* Select.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. Styles the trigger (which shares the Input chrome so a\n * Select sits flush next to an Input on the same form row), the floating\n * listbox surface, and the option rows.\n *\n * The trigger border tone is `neutral-500` for the same WCAG 1.4.11 reason\n * Input uses it (D10): a Select trigger is an interactive control boundary,\n * so it must clear the 3:1 non-text contrast bar. The popup surface chrome\n * (white, subtle `neutral-200` border, large shadow, fade-and-scale motion)\n * is deliberately identical to DropdownMenu and Popover — every anchored\n * overlay reads as the same surface family.\n *\n * Anchor-width: the popup takes `width: var(--anchor-width)` so the listbox\n * matches the trigger that opened it exactly — long options truncate (see\n * `.itemText`) rather than ballooning the panel wider than the field, the\n * Ant / Mantine / MUI / shadcn-combobox default. Base UI sets `--anchor-width`\n * on the Positioner; it cascades to the Popup. This is the affordance that\n * was missing from Popover v1 and forced the picker call-sites to be deferred\n * to this atom. Consumers needing a wider panel override `width` via\n * `className` per call-site.\n *\n * z-index lives on `.positioner` (see below). Base UI portals the popup to\n * the end of `<body>`; that floats it above in-flow content, but not above a\n * host-app overlay (drawer/modal) with its own z-index — and a z-index on the\n * Popup is trapped by the Positioner's transform stacking context. The\n * Positioner therefore carries `z-index: var(--pharos-z-index-popover, …)`,\n * the shared scale Combobox / DropdownMenu / Popover all use now. */\n\n/* === Trigger (Select.Trigger) ===\n * Same chrome as Input: white fill, `neutral-500` resting border, brand\n * focus ring. Laid out as a row so the value grows and the chevron pins to\n * the trailing edge. */\n.trigger {\n appearance: none;\n -webkit-appearance: none;\n\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--pharos-spacing-2);\n width: 100%;\n margin: 0;\n\n border: 1px solid var(--pharos-color-neutral-500);\n border-radius: var(--pharos-radius-md);\n background-color: var(--pharos-color-base-white);\n color: var(--pharos-color-neutral-900);\n\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n font-weight: var(--pharos-font-weight-regular);\n line-height: 1.25;\n text-align: start;\n cursor: default;\n\n transition-property: border-color, box-shadow;\n transition-duration: var(--pharos-duration-normal);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.trigger:focus-visible {\n outline: none;\n border-color: var(--pharos-color-primary-600);\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-primary-600);\n}\n\n.trigger:disabled,\n.trigger[data-disabled] {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.trigger[aria-invalid='true'] {\n border-color: var(--pharos-color-semantic-error-fg);\n}\n\n.trigger[aria-invalid='true']:focus-visible {\n border-color: var(--pharos-color-semantic-error-fg);\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-semantic-error-fg);\n}\n\n/* === Sizes ===\n * Heights match the Input/Button grid exactly so a Select aligns with the\n * other controls on a row. */\n.sizeSm {\n height: var(--pharos-spacing-8);\n padding: 0 var(--pharos-spacing-3);\n font-size: var(--pharos-font-size-sm);\n}\n\n.sizeMd {\n height: var(--pharos-spacing-10);\n padding: 0 var(--pharos-spacing-4);\n font-size: var(--pharos-font-size-sm);\n}\n\n.sizeLg {\n height: var(--pharos-spacing-12);\n padding: 0 var(--pharos-spacing-6);\n font-size: var(--pharos-font-size-base);\n}\n\n/* === Value (Select.Value) ===\n * Grows to fill the trigger and truncates rather than wrapping. The\n * placeholder state is tinted with the same muted tone as Input's. */\n.value {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.value[data-placeholder] {\n color: var(--pharos-color-neutral-500);\n}\n\n/* === Chevron (Select.Icon) ===\n * Inline SVG sized to the control. Rotates when the popup is open. */\n.icon {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n color: var(--pharos-color-neutral-500);\n transition: transform var(--pharos-duration-fast) var(--pharos-easing-out);\n}\n\n.icon svg {\n width: 1rem;\n height: 1rem;\n}\n\n.icon[data-popup-open] {\n transform: rotate(180deg);\n}\n\n/* === Positioner (Select.Positioner) ===\n * z-index lives here, not on `.content` (the Popup). Base UI's Positioner\n * anchors with `transform`, which creates a stacking context — so a z-index\n * on the inner Popup is trapped inside it and cannot rise above a host-app\n * overlay (e.g. a drawer with its own z-index). The z-index must sit on the\n * Positioner itself.\n *\n * Sourced from `--pharos-z-index-popover` (no hardcoded value — NON-NEGOTIABLE\n * rule 1). The fallback suits apps with no high z-index scale; an app whose\n * own overlays sit higher (drawers, modals) raises every Pharos popup at once\n * by overriding `--pharos-z-index-popover` in `:root`. */\n.positioner {\n z-index: var(--pharos-z-index-popover, 1000);\n}\n\n/* === Floating surface (Select.Popup) === */\n.content {\n box-sizing: border-box;\n width: var(--anchor-width);\n max-height: var(--available-height);\n overflow-y: auto;\n padding: var(--pharos-spacing-1);\n\n background-color: var(--pharos-color-base-white);\n border: 1px solid var(--pharos-color-neutral-200);\n border-radius: var(--pharos-radius-md);\n box-shadow: var(--pharos-shadow-lg);\n\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n color: var(--pharos-color-neutral-900);\n\n outline: none;\n\n transform-origin: var(--transform-origin);\n transition-property: opacity, transform;\n transition-duration: var(--pharos-duration-fast);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.content[data-starting-style],\n.content[data-ending-style] {\n opacity: 0;\n transform: scale(0.96);\n}\n\n/* === Option (Select.Item) ===\n * Row layout with the label growing and the selected-state check pinned to\n * the trailing edge. Highlight tracks Base UI's `data-highlighted` (keyboard\n * / pointer) the same way DropdownMenu items do. */\n.item {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--pharos-spacing-2);\n\n padding: var(--pharos-spacing-2) var(--pharos-spacing-3);\n border-radius: var(--pharos-radius-sm);\n\n color: var(--pharos-color-neutral-900);\n cursor: default;\n user-select: none;\n outline: none;\n scroll-margin: var(--pharos-spacing-1);\n}\n\n.item[data-highlighted] {\n background-color: var(--pharos-color-neutral-100);\n}\n\n.item[data-disabled] {\n color: var(--pharos-color-neutral-400);\n pointer-events: none;\n}\n\n.itemText {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.itemIndicator {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n color: var(--pharos-color-primary-600);\n}\n\n.itemIndicator svg {\n width: 1rem;\n height: 1rem;\n}\n\n/* === Group + label (Select.Group / Select.GroupLabel) === */\n.label {\n padding: var(--pharos-spacing-2) var(--pharos-spacing-3) var(--pharos-spacing-1);\n font-size: var(--pharos-font-size-xs);\n font-weight: var(--pharos-font-weight-semibold);\n color: var(--pharos-color-neutral-600);\n}\n\n/* === Separator (Select.Separator) === */\n.separator {\n height: 1px;\n margin: var(--pharos-spacing-1) 0;\n background-color: var(--pharos-color-neutral-200);\n}\n","import type { ComponentProps } from 'react';\nimport { Select as BaseSelect } from '@base-ui/react/select';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Select.module.css';\n\n/**\n * Single- or multi-select from a **known set of options** (Decision D17).\n * Wraps Base UI's `Select.*` parts, which implement the ARIA **listbox**\n * pattern: a trigger button opens a popup with `role=\"listbox\"` whose rows\n * are `role=\"option\"`, with roving focus, typeahead and Escape-to-close.\n *\n * This is deliberately distinct from the `Combobox` atom (also D17). A\n * Select has **no text input** — the user picks from a closed list. When the\n * option set is large enough to need filtering, or the value can be typed,\n * that is a Combobox (the APG combobox pattern). shadcn, Base UI and ARIA APG\n * all separate the two under exactly these names, which is why Pharos ships\n * two atoms rather than one component with a `searchable` axis. See\n * `NAMING-decisions.md` § Select.\n *\n * Multi-select is the `multiple` axis on the root (not a separate atom),\n * matching Base UI / shadcn / React Aria: pass `multiple` and `value`\n * becomes an array. Single-select closes on pick; multi-select keeps the\n * popup open so selections accumulate — Base UI derives this from `multiple`\n * and Pharos does not override it.\n *\n * Naming follows shadcn (`Select*`). `SelectTrigger` folds Base UI's\n * `Trigger` + the chevron `Icon`; `SelectContent` collapses `Portal` +\n * `Positioner` + `Popup` + `List` with `side` / `align` / `sideOffset`\n * props; `SelectItem` folds `Item` + `ItemText` + the selected-state\n * `ItemIndicator` — the same ergonomics shadcn exposes over Radix. The popup\n * takes `width: var(--anchor-width)` so it matches the trigger width exactly\n * (long options truncate; override `width` via `className` for a wider panel).\n *\n * Following the form-control composition rule (Escuela 1, D11), the atom owns\n * no label / helper / error message — the consumer composes those, and the\n * error state is conveyed through the standard `aria-invalid` attribute on\n * the trigger (the CSS reacts to `[aria-invalid=\"true\"]`).\n *\n * **Always pass `items` (a value→label map) when the value differs from its\n * label.** Base UI's `SelectValue` resolves the trigger label from the root's\n * `items` prop, NOT from the `SelectItem` children — those are the listbox\n * label + typeahead text. This holds for interactive selection too: without\n * `items` the trigger shows the serialized raw value (the enum\n * `\"organization\"`, not `\"Organization\"`). The idiomatic, drift-free pattern\n * is a single options source that feeds both `items` and the rendered items.\n *\n * @example\n * const VISIBILITY = { private: 'Private', organization: 'Organization', public: 'Public' };\n *\n * <Select value={value} onValueChange={setValue} items={VISIBILITY}>\n * <SelectTrigger aria-label=\"Visibility\">\n * <SelectValue placeholder=\"Select visibility\" />\n * </SelectTrigger>\n * <SelectContent>\n * {Object.entries(VISIBILITY).map(([v, label]) => (\n * <SelectItem key={v} value={v}>\n * {label}\n * </SelectItem>\n * ))}\n * </SelectContent>\n * </Select>\n */\nexport type SelectProps<\n Value,\n Multiple extends boolean | undefined = false,\n> = BaseSelect.Root.Props<Value, Multiple>;\n\nexport function Select<Value, Multiple extends boolean | undefined = false>(\n props: SelectProps<Value, Multiple>,\n) {\n return <BaseSelect.Root {...props} />;\n}\n\nSelect.displayName = 'Select';\n\n/**\n * A downward chevron, inlined as an SVG so the published bundle pulls no icon\n * dependency (Pharos does not bundle `lucide-react`; D4 keeps Lucide as the\n * canonical source for the path data). Rotates 180° when the popup opens via\n * the `[data-popup-open]` selector on the wrapping `SelectIcon`.\n */\nfunction ChevronDownIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden=\"true\">\n <path d=\"m6 9 6 6 6-6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n\n/** A check mark for the selected option, inlined for the same reason. */\nfunction CheckIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden=\"true\">\n <path d=\"M20 6 9 17l-5-5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n\nconst triggerVariants = cva(styles.trigger, {\n variants: {\n size: {\n sm: styles.sizeSm,\n md: styles.sizeMd,\n lg: styles.sizeLg,\n },\n },\n defaultVariants: {\n size: 'md',\n },\n});\n\ntype TriggerVariantProps = VariantProps<typeof triggerVariants>;\n\n/**\n * The button that opens the listbox. Shares the Input chrome (border tone,\n * focus ring, size grid) so a Select sits flush next to an Input on a form\n * row. Folds the trailing chevron, and lays children out before it — the\n * common case is a single `<SelectValue />`. The `size` axis (sm / md / lg)\n * matches the Input and Button heights.\n *\n * Give it an accessible name with `aria-label` (or wire a `<label>` /\n * `aria-labelledby`) — the trigger only shows the chosen value, not a label.\n */\nexport interface SelectTriggerProps\n extends ComponentProps<typeof BaseSelect.Trigger>, TriggerVariantProps {}\n\nexport function SelectTrigger({ size, className, children, ...rest }: SelectTriggerProps) {\n const resolvedSize = size ?? 'md';\n return (\n <BaseSelect.Trigger\n data-pharos-slot=\"select-trigger\"\n data-pharos-size={resolvedSize}\n className={cn(triggerVariants({ size: resolvedSize }), className)}\n {...rest}\n >\n {children}\n <BaseSelect.Icon className={styles.icon}>\n <ChevronDownIcon />\n </BaseSelect.Icon>\n </BaseSelect.Trigger>\n );\n}\n\nSelectTrigger.displayName = 'SelectTrigger';\n\n/**\n * Renders the selected option's label inside the trigger, or the\n * `placeholder` when nothing is selected. Wraps Base UI's `Select.Value`;\n * pass a render function as `children` to format the value yourself.\n */\nexport type SelectValueProps = ComponentProps<typeof BaseSelect.Value>;\n\nexport function SelectValue({ className, ...rest }: SelectValueProps) {\n return (\n <BaseSelect.Value\n data-pharos-slot=\"select-value\"\n className={cn(styles.value, className)}\n {...rest}\n />\n );\n}\n\nSelectValue.displayName = 'SelectValue';\n\n/**\n * The floating listbox. Collapses Base UI's `Portal` + `Positioner` +\n * `Popup` + `List` so the consumer positions it with the props that matter\n * at the call site. Defaults match the common select: opens below the\n * trigger, aligned to its start edge, 8px away, and matches the trigger width\n * (`width: var(--anchor-width)`).\n *\n * `alignItemWithTrigger` is forced to `false`. Base UI's `Select.Positioner`\n * defaults it to `true` — the native-`<select>` / macOS behaviour that floats\n * the popup *over* the trigger to put the selected item under the pointer,\n * and which silently overrides `side` / `align` / `sideOffset`\n * (`renderedSide` becomes `'none'`). Pharos anchors the listbox **below the\n * trigger** instead (D17 refinement): it is the dominant web convention\n * (shadcn `position=\"popper\"`, Ant, Mantine), it keeps the trigger box\n * visible, and — decisively — it makes `Select` consistent with `Combobox`,\n * whose Base UI primitive has no item-alignment mode and always anchors\n * adjacent. With it off, the `side` / `align` / `sideOffset` props below\n * actually take effect.\n */\nexport interface SelectContentProps extends ComponentProps<typeof BaseSelect.Popup> {\n /**\n * Which side of the trigger the listbox opens on.\n * @default 'bottom'\n */\n side?: ComponentProps<typeof BaseSelect.Positioner>['side'];\n /**\n * Alignment of the listbox relative to the trigger along the chosen side.\n * @default 'start'\n */\n align?: ComponentProps<typeof BaseSelect.Positioner>['align'];\n /**\n * Gap in pixels between the trigger and the listbox.\n * @default 8\n */\n sideOffset?: ComponentProps<typeof BaseSelect.Positioner>['sideOffset'];\n /**\n * Offset in pixels along the alignment axis.\n * @default 0\n */\n alignOffset?: ComponentProps<typeof BaseSelect.Positioner>['alignOffset'];\n}\n\nexport function SelectContent({\n side = 'bottom',\n align = 'start',\n sideOffset = 8,\n alignOffset = 0,\n className,\n children,\n ...rest\n}: SelectContentProps) {\n return (\n <BaseSelect.Portal>\n <BaseSelect.Positioner\n className={styles.positioner}\n alignItemWithTrigger={false}\n side={side}\n align={align}\n sideOffset={sideOffset}\n alignOffset={alignOffset}\n >\n <BaseSelect.Popup\n data-pharos-slot=\"select-content\"\n className={cn(styles.content, className)}\n {...rest}\n >\n <BaseSelect.List>{children}</BaseSelect.List>\n </BaseSelect.Popup>\n </BaseSelect.Positioner>\n </BaseSelect.Portal>\n );\n}\n\nSelectContent.displayName = 'SelectContent';\n\n/**\n * A single option. Folds Base UI's `Item` + `ItemText` (the label, also used\n * as the value shown in the trigger) + `ItemIndicator` (the check shown when\n * the option is selected). The `value` prop is required by Base UI and is\n * what `onValueChange` reports.\n */\nexport interface SelectItemProps extends ComponentProps<typeof BaseSelect.Item> {}\n\nexport function SelectItem({ className, children, ...rest }: SelectItemProps) {\n return (\n <BaseSelect.Item\n data-pharos-slot=\"select-item\"\n className={cn(styles.item, className)}\n {...rest}\n >\n <BaseSelect.ItemText className={styles.itemText}>{children}</BaseSelect.ItemText>\n <BaseSelect.ItemIndicator className={styles.itemIndicator}>\n <CheckIcon />\n </BaseSelect.ItemIndicator>\n </BaseSelect.Item>\n );\n}\n\nSelectItem.displayName = 'SelectItem';\n\n/**\n * Groups related options under an optional `SelectLabel` for accessible\n * sectioning (`role=\"group\"`).\n */\nexport type SelectGroupProps = ComponentProps<typeof BaseSelect.Group>;\n\nexport function SelectGroup(props: SelectGroupProps) {\n return <BaseSelect.Group data-pharos-slot=\"select-group\" {...props} />;\n}\n\nSelectGroup.displayName = 'SelectGroup';\n\n/**\n * A non-interactive heading for a `SelectGroup`. Renders Base UI's\n * `Select.GroupLabel`, which wires `aria-labelledby` on the surrounding\n * group — use the two together when a section needs an accessible name.\n */\nexport type SelectLabelProps = ComponentProps<typeof BaseSelect.GroupLabel>;\n\nexport function SelectLabel({ className, ...rest }: SelectLabelProps) {\n return (\n <BaseSelect.GroupLabel\n data-pharos-slot=\"select-label\"\n className={cn(styles.label, className)}\n {...rest}\n />\n );\n}\n\nSelectLabel.displayName = 'SelectLabel';\n\n/**\n * A thin **decorative** divider between groups of options.\n *\n * Rendered `aria-hidden` with `role=\"none\"`. Base UI's `Select.Separator`\n * re-exports the generic `Separator`, which emits `role=\"separator\"`. A\n * `separator` is **not** a permitted child of the `listbox` role (its content\n * model allows only `option` and `group`), so leaving it in the accessibility\n * tree fails axe's `aria-required-children` (critical) on a grouped Select.\n * The grouping is already conveyed semantically by `SelectGroup` (`role=\n * \"group\"`) + `SelectLabel` (`aria-labelledby`), so the divider is purely\n * visual — hiding it from the tree is the correct, drift-free fix (same as\n * Radix / shadcn, whose select separators are `aria-hidden`).\n */\nexport type SelectSeparatorProps = ComponentProps<typeof BaseSelect.Separator>;\n\nexport function SelectSeparator({ className, ...rest }: SelectSeparatorProps) {\n return (\n <BaseSelect.Separator\n data-pharos-slot=\"select-separator\"\n role=\"none\"\n aria-hidden\n className={cn(styles.separator, className)}\n {...rest}\n />\n );\n}\n\nSelectSeparator.displayName = 'SelectSeparator';\n","/* Combobox.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. Styles the bordered control (single-select input group\n * and multi-select chips container share the `.control` chrome), the chips,\n * the floating listbox surface, and the option rows.\n *\n * The control border tone is `neutral-500` for the same WCAG 1.4.11 reason\n * Input and Select use it (D10). The popup surface chrome and the option /\n * group / separator styling mirror Select exactly — Select and Combobox are\n * the two halves of the selection family and read as one system.\n *\n * Anchor-width: the popup takes `width: var(--anchor-width)` so the listbox\n * matches the control exactly — long options truncate (see `.itemText`)\n * rather than ballooning wider than the field (the Ant / Mantine / MUI /\n * shadcn-combobox default). `max-height: var(--available-height)` clamps it\n * to the viewport. Both vars come from Base UI's Positioner. Consumers\n * needing a wider panel override `width` via `className`. */\n\n/* === Control (Combobox.InputGroup, single) and chips box (Combobox.Chips,\n * multiple) — same bordered chrome === */\n.control {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: var(--pharos-spacing-2);\n width: 100%;\n\n border: 1px solid var(--pharos-color-neutral-500);\n border-radius: var(--pharos-radius-md);\n background-color: var(--pharos-color-base-white);\n\n /* Set the Pharos sans stack on the box so everything inside it — chips,\n * the input — shares one typography. Without this the chips fall back to\n * the user-agent serif/system font (the bundle ships no global font), so\n * they read in a different typeface than the input. Same reason Input sets\n * font-family explicitly. */\n font-family: var(--pharos-font-family-sans);\n\n transition-property: border-color, box-shadow;\n transition-duration: var(--pharos-duration-normal);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n/* Lift the focus ring to the control when the inner input is focused, so the\n * whole bordered box reads as the focused control (the input itself carries\n * no border). */\n.control:focus-within {\n border-color: var(--pharos-color-primary-600);\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-primary-600);\n}\n\n.control[aria-invalid='true'],\n.control:has([aria-invalid='true']) {\n border-color: var(--pharos-color-semantic-error-fg);\n}\n\n/* The multi-select chips box wraps its chips and grows vertically. */\n.chips {\n flex-wrap: wrap;\n gap: var(--pharos-spacing-1);\n padding: var(--pharos-spacing-1) var(--pharos-spacing-2);\n}\n\n/* === Bare input (Combobox.Input) ===\n * Carries no border of its own — the surrounding `.control` / `.chips` box\n * owns the chrome. Grows to fill the row. */\n.input {\n appearance: none;\n -webkit-appearance: none;\n\n flex: 1 1 auto;\n min-width: 0;\n margin: 0;\n border: none;\n outline: none;\n background: transparent;\n color: var(--pharos-color-neutral-900);\n\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n font-weight: var(--pharos-font-weight-regular);\n line-height: 1.25;\n}\n\n.input::placeholder {\n color: var(--pharos-color-neutral-500);\n opacity: 1;\n}\n\n.input:disabled {\n cursor: not-allowed;\n}\n\n/* Standalone single-select input gets the same padding/height grid as Input\n * so it sits flush with other fields. */\n.inputSizeSm {\n height: var(--pharos-spacing-8);\n padding: 0 var(--pharos-spacing-3);\n}\n\n.inputSizeMd {\n height: var(--pharos-spacing-10);\n padding: 0 var(--pharos-spacing-4);\n}\n\n.inputSizeLg {\n height: var(--pharos-spacing-12);\n padding: 0 var(--pharos-spacing-6);\n font-size: var(--pharos-font-size-base);\n}\n\n/* Inside the chips box the input is a flexible filler — no fixed height, it\n * shares the row with the chips. */\n.chipsInput {\n height: var(--pharos-spacing-7);\n padding: 0 var(--pharos-spacing-1);\n flex: 1 1 4rem;\n}\n\n/* === Trigger (chevron) and clear button === */\n.trigger,\n.clear {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: var(--pharos-spacing-6);\n height: var(--pharos-spacing-6);\n margin-inline-end: var(--pharos-spacing-1);\n padding: 0;\n border: none;\n background: transparent;\n color: var(--pharos-color-neutral-500);\n cursor: default;\n}\n\n.trigger svg,\n.clear svg {\n width: 1rem;\n height: 1rem;\n}\n\n.trigger[data-popup-open] svg {\n transform: rotate(180deg);\n transition: transform var(--pharos-duration-fast) var(--pharos-easing-out);\n}\n\n/* === Chip (Combobox.Chip) === */\n.chip {\n display: inline-flex;\n align-items: center;\n gap: var(--pharos-spacing-1);\n padding: 0 var(--pharos-spacing-1) 0 var(--pharos-spacing-2);\n height: var(--pharos-spacing-6);\n\n border-radius: var(--pharos-radius-sm);\n background-color: var(--pharos-color-neutral-100);\n color: var(--pharos-color-neutral-900);\n font-size: var(--pharos-font-size-xs);\n}\n\n.chip[data-highlighted] {\n background-color: var(--pharos-color-neutral-200);\n}\n\n.chipRemove {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--pharos-spacing-4);\n height: var(--pharos-spacing-4);\n padding: 0;\n border: none;\n border-radius: var(--pharos-radius-sm);\n background: transparent;\n color: var(--pharos-color-neutral-500);\n cursor: default;\n}\n\n.chipRemove:hover {\n color: var(--pharos-color-neutral-900);\n}\n\n.chipRemove svg {\n width: 0.875rem;\n height: 0.875rem;\n}\n\n/* === Positioner (Combobox.Positioner) ===\n * z-index lives here, not on `.content` (the Popup). Base UI's Positioner\n * anchors with `transform`, which creates a stacking context — so a z-index\n * on the inner Popup is trapped inside it and cannot rise above a host-app\n * overlay (e.g. a drawer with its own z-index). The z-index must sit on the\n * Positioner itself.\n *\n * Sourced from `--pharos-z-index-popover` (no hardcoded value — NON-NEGOTIABLE\n * rule 1). The fallback suits apps with no high z-index scale; an app whose\n * own overlays sit higher (drawers, modals) raises every Pharos popup at once\n * by overriding `--pharos-z-index-popover` in `:root`. */\n.positioner {\n z-index: var(--pharos-z-index-popover, 1000);\n}\n\n/* === Floating surface (Combobox.Popup) — mirrors Select === */\n.content {\n box-sizing: border-box;\n width: var(--anchor-width);\n max-height: var(--available-height);\n overflow-y: auto;\n padding: var(--pharos-spacing-1);\n\n background-color: var(--pharos-color-base-white);\n border: 1px solid var(--pharos-color-neutral-200);\n border-radius: var(--pharos-radius-md);\n box-shadow: var(--pharos-shadow-lg);\n\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n color: var(--pharos-color-neutral-900);\n\n outline: none;\n\n transform-origin: var(--transform-origin);\n transition-property: opacity, transform;\n transition-duration: var(--pharos-duration-fast);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.content[data-starting-style],\n.content[data-ending-style] {\n opacity: 0;\n transform: scale(0.96);\n}\n\n/* === Option (Combobox.Item) === */\n.item {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--pharos-spacing-2);\n\n padding: var(--pharos-spacing-2) var(--pharos-spacing-3);\n border-radius: var(--pharos-radius-sm);\n\n color: var(--pharos-color-neutral-900);\n cursor: default;\n user-select: none;\n outline: none;\n scroll-margin: var(--pharos-spacing-1);\n}\n\n.item[data-highlighted] {\n background-color: var(--pharos-color-neutral-100);\n}\n\n.item[data-disabled] {\n color: var(--pharos-color-neutral-400);\n pointer-events: none;\n}\n\n.itemText {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.itemIndicator {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n color: var(--pharos-color-primary-600);\n}\n\n.itemIndicator svg {\n width: 1rem;\n height: 1rem;\n}\n\n/* === Empty state (Combobox.Empty) ===\n * Base UI keeps this element mounted even when there ARE results (it is a\n * `role=\"status\"` live region and must not be removed / `display:none`-d, or\n * screen readers miss the \"no results\" announcement) — it only nulls its\n * children. So when results exist the element is present but childless;\n * `:not(:empty)` applies the padding only when a message is actually shown,\n * collapsing the box to zero height otherwise. Without this the empty live\n * region adds a block of padding above the list and the popup looks\n * top-heavy / asymmetric against its bottom edge. */\n.empty {\n color: var(--pharos-color-neutral-500);\n text-align: center;\n}\n\n.empty:not(:empty) {\n padding: var(--pharos-spacing-3);\n}\n\n/* === Group + label + separator (mirror Select) === */\n.label {\n padding: var(--pharos-spacing-2) var(--pharos-spacing-3) var(--pharos-spacing-1);\n font-size: var(--pharos-font-size-xs);\n font-weight: var(--pharos-font-weight-semibold);\n color: var(--pharos-color-neutral-600);\n}\n\n.separator {\n height: 1px;\n margin: var(--pharos-spacing-1) 0;\n background-color: var(--pharos-color-neutral-200);\n}\n","import {\n type ComponentProps,\n type Ref,\n createContext,\n useCallback,\n useContext,\n useMemo,\n useState,\n} from 'react';\nimport { Combobox as BaseCombobox } from '@base-ui/react/combobox';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Combobox.module.css';\n\n/**\n * Single- or multi-select with a **text input that filters the options**\n * (Decision D17). Wraps Base UI's `Combobox.*` parts, which implement the\n * ARIA APG **combobox** pattern: an `<input role=\"combobox\">` with\n * `aria-expanded` / `aria-controls` pointing at a popup listbox, typeahead\n * filtering, and arrow-key navigation of the results.\n *\n * This is deliberately distinct from the `Select` atom (also D17). A Select\n * has no input — the user picks from a closed list. A Combobox is for sets\n * large enough to need filtering, or where the user expects to type. shadcn,\n * Base UI and ARIA APG separate the two; Pharos ships both rather than a\n * single component with a `searchable` axis. (Free-text entry — typing a\n * value that is not in the list — is Base UI's separate `Autocomplete`\n * primitive and is deferred; every known Alexandria call-site filters a known\n * set.) See `NAMING-decisions.md` § Combobox.\n *\n * Multi-select is the `multiple` axis on the root (not a separate atom),\n * matching Base UI / shadcn / React Aria. With `multiple`, selections render\n * as removable chips inside a `ComboboxChips` box and the popup stays open so\n * they accumulate; single-select uses a `ComboboxControl` input box and\n * closes on pick.\n *\n * Naming follows Base UI (`Combobox*`; shadcn has no canonical primitive — it\n * composes a recipe). The control chrome is split into composable parts\n * because a combobox is inherently more compositional than a select:\n * `ComboboxControl` / `ComboboxChips` own the bordered box, `ComboboxInput`\n * is the bare field, `ComboboxTrigger` the chevron. `ComboboxContent`\n * collapses `Portal` + `Positioner` + `Popup`; `ComboboxList` and\n * `ComboboxEmpty` sit inside it. The popup takes `width:\n * var(--anchor-width)` so it matches the control width exactly (long options\n * truncate) — the anchor-width affordance the picker call-sites needed.\n *\n * Per the form-control composition rule (Escuela 1, D11) the atom owns no\n * label / helper / error message; the consumer composes those and conveys\n * error via `aria-invalid`.\n *\n * @example single-select\n * <Combobox items={fruits} onValueChange={setValue}>\n * <ComboboxControl>\n * <ComboboxInput placeholder=\"Search fruit…\" />\n * <ComboboxTrigger />\n * </ComboboxControl>\n * <ComboboxContent>\n * <ComboboxEmpty>No fruit found</ComboboxEmpty>\n * <ComboboxList>\n * {(item) => (\n * <ComboboxItem key={item.value} value={item}>\n * {item.label}\n * </ComboboxItem>\n * )}\n * </ComboboxList>\n * </ComboboxContent>\n * </Combobox>\n */\nexport type ComboboxProps<\n Value,\n Multiple extends boolean | undefined = false,\n> = BaseCombobox.Root.Props<Value, Multiple>;\n\n/**\n * Lets `ComboboxChips` publish its DOM node so `ComboboxContent` can anchor\n * the floating listbox to the control box instead of the bare input.\n *\n * Why this exists: Base UI resolves the popup anchor as\n * `inputGroupElement ?? inputElement`. In single-select Pharos wraps Base UI's\n * `InputGroup` (`ComboboxControl`), so the popup anchors to the full-width\n * control. In multi-select Pharos uses `Combobox.Chips` (`ComboboxChips`) as\n * the bordered box — and `Chips` does NOT register an `inputGroupElement`, so\n * the anchor falls back to the `Input`, which floats at the end of the chip\n * row. The popup then opens narrow and shifted right. Capturing the chips box\n * here and passing it as the Positioner's `anchor` keeps the panel the full\n * control width, left-aligned, regardless of where the input sits.\n */\ninterface ComboboxAnchorContextValue {\n anchorElement: HTMLElement | null;\n setAnchorElement: (element: HTMLElement | null) => void;\n}\n\nconst ComboboxAnchorContext = createContext<ComboboxAnchorContextValue | null>(null);\n\nexport function Combobox<Value, Multiple extends boolean | undefined = false>(\n props: ComboboxProps<Value, Multiple>,\n) {\n const [anchorElement, setAnchorElement] = useState<HTMLElement | null>(null);\n const anchorContext = useMemo<ComboboxAnchorContextValue>(\n () => ({ anchorElement, setAnchorElement }),\n [anchorElement],\n );\n return (\n <ComboboxAnchorContext.Provider value={anchorContext}>\n <BaseCombobox.Root {...props} />\n </ComboboxAnchorContext.Provider>\n );\n}\n\nCombobox.displayName = 'Combobox';\n\n/** A downward chevron, inlined as an SVG (Pharos bundles no icon dependency; D4). */\nfunction ChevronDownIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden=\"true\">\n <path d=\"m6 9 6 6 6-6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n\n/** A check mark for the selected option, inlined for the same reason. */\nfunction CheckIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden=\"true\">\n <path d=\"M20 6 9 17l-5-5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n\n/** A small ✕ for clearing / removing, inlined for the same reason. */\nfunction XIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden=\"true\">\n <path d=\"M18 6 6 18M6 6l12 12\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n\n/**\n * The bordered control box for **single-select**. Wraps Base UI's\n * `Combobox.InputGroup` and carries the Input chrome (border tone, focus\n * ring). Put a `ComboboxInput` and optionally a `ComboboxTrigger` /\n * `ComboboxClear` inside it.\n */\nexport type ComboboxControlProps = ComponentProps<typeof BaseCombobox.InputGroup>;\n\nexport function ComboboxControl({ className, ...rest }: ComboboxControlProps) {\n return (\n <BaseCombobox.InputGroup\n data-pharos-slot=\"combobox-control\"\n className={cn(styles.control, className)}\n {...rest}\n />\n );\n}\n\nComboboxControl.displayName = 'ComboboxControl';\n\n/**\n * The bordered control box for **multi-select**. Wraps Base UI's\n * `Combobox.Chips`; render `ComboboxChip`s for the current selections\n * followed by a `ComboboxInput` to keep typing. Shares the `ComboboxControl`\n * chrome.\n */\nexport type ComboboxChipsProps = ComponentProps<typeof BaseCombobox.Chips>;\n\nexport function ComboboxChips({ className, ref, ...rest }: ComboboxChipsProps) {\n const anchorContext = useContext(ComboboxAnchorContext);\n // Publish the chips box as the popup anchor (see ComboboxAnchorContext)\n // while still honouring any ref the consumer forwards.\n const setRef = useCallback(\n (element: HTMLDivElement | null) => {\n anchorContext?.setAnchorElement(element);\n if (typeof ref === 'function') {\n ref(element);\n } else if (ref) {\n (ref as { current: HTMLDivElement | null }).current = element;\n }\n },\n [anchorContext, ref],\n );\n return (\n <BaseCombobox.Chips\n data-pharos-slot=\"combobox-chips\"\n className={cn(styles.control, styles.chips, className)}\n ref={setRef as Ref<HTMLDivElement>}\n {...rest}\n />\n );\n}\n\nComboboxChips.displayName = 'ComboboxChips';\n\nconst inputVariants = cva(styles.input, {\n variants: {\n size: {\n sm: styles.inputSizeSm,\n md: styles.inputSizeMd,\n lg: styles.inputSizeLg,\n },\n },\n defaultVariants: {\n size: 'md',\n },\n});\n\ntype InputVariantProps = VariantProps<typeof inputVariants>;\n\n/**\n * The text field. Wraps Base UI's `Combobox.Input` (`role=\"combobox\"`),\n * which owns the filtering. Renders bare — the surrounding `ComboboxControl`\n * / `ComboboxChips` box owns the border. The `size` axis (sm / md / lg)\n * matches Input/Select and applies when the input is used standalone in a\n * `ComboboxControl`; inside a `ComboboxChips` box pass `inset` so it fills\n * the chip row instead of imposing its own height.\n */\nexport interface ComboboxInputProps\n extends Omit<ComponentProps<typeof BaseCombobox.Input>, 'size'>, InputVariantProps {\n /**\n * Render as a flexible filler that shares the row with chips, instead of a\n * fixed-height field. Use inside `ComboboxChips`.\n * @default false\n */\n inset?: boolean;\n}\n\nexport function ComboboxInput({ size, inset = false, className, ...rest }: ComboboxInputProps) {\n const resolvedSize = size ?? 'md';\n return (\n <BaseCombobox.Input\n data-pharos-slot=\"combobox-input\"\n data-pharos-size={resolvedSize}\n className={cn(\n inset ? cn(styles.input, styles.chipsInput) : inputVariants({ size: resolvedSize }),\n className,\n )}\n {...rest}\n />\n );\n}\n\nComboboxInput.displayName = 'ComboboxInput';\n\n/**\n * The chevron button that toggles the popup. Wraps Base UI's\n * `Combobox.Trigger`; the chevron flips when the popup is open.\n */\nexport type ComboboxTriggerProps = ComponentProps<typeof BaseCombobox.Trigger>;\n\nexport function ComboboxTrigger({ className, children, ...rest }: ComboboxTriggerProps) {\n return (\n <BaseCombobox.Trigger\n data-pharos-slot=\"combobox-trigger\"\n className={cn(styles.trigger, className)}\n {...rest}\n >\n {children ?? <ChevronDownIcon />}\n </BaseCombobox.Trigger>\n );\n}\n\nComboboxTrigger.displayName = 'ComboboxTrigger';\n\n/** A button that clears the current value(s). Wraps Base UI's `Combobox.Clear`. */\nexport type ComboboxClearProps = ComponentProps<typeof BaseCombobox.Clear>;\n\nexport function ComboboxClear({ className, children, ...rest }: ComboboxClearProps) {\n return (\n <BaseCombobox.Clear\n data-pharos-slot=\"combobox-clear\"\n className={cn(styles.clear, className)}\n {...rest}\n >\n {children ?? <XIcon />}\n </BaseCombobox.Clear>\n );\n}\n\nComboboxClear.displayName = 'ComboboxClear';\n\n/** A selected-value chip for multi-select. Wraps Base UI's `Combobox.Chip`. */\nexport type ComboboxChipProps = ComponentProps<typeof BaseCombobox.Chip>;\n\nexport function ComboboxChip({ className, ...rest }: ComboboxChipProps) {\n return (\n <BaseCombobox.Chip\n data-pharos-slot=\"combobox-chip\"\n className={cn(styles.chip, className)}\n {...rest}\n />\n );\n}\n\nComboboxChip.displayName = 'ComboboxChip';\n\n/** The ✕ that removes a chip's value. Wraps Base UI's `Combobox.ChipRemove`. */\nexport type ComboboxChipRemoveProps = ComponentProps<typeof BaseCombobox.ChipRemove>;\n\nexport function ComboboxChipRemove({\n className,\n children,\n 'aria-label': ariaLabel = 'Remove',\n ...rest\n}: ComboboxChipRemoveProps) {\n return (\n <BaseCombobox.ChipRemove\n data-pharos-slot=\"combobox-chip-remove\"\n aria-label={ariaLabel}\n className={cn(styles.chipRemove, className)}\n {...rest}\n >\n {children ?? <XIcon />}\n </BaseCombobox.ChipRemove>\n );\n}\n\nComboboxChipRemove.displayName = 'ComboboxChipRemove';\n\n/**\n * The floating listbox surface. Collapses Base UI's `Portal` + `Positioner` +\n * `Popup`. Put `ComboboxEmpty` and `ComboboxList` inside it. Defaults: opens\n * below the control, aligned to its start edge, 8px away, and matches the\n * control width (`width: var(--anchor-width)`).\n */\nexport interface ComboboxContentProps extends ComponentProps<typeof BaseCombobox.Popup> {\n /**\n * Which side of the control the listbox opens on.\n * @default 'bottom'\n */\n side?: ComponentProps<typeof BaseCombobox.Positioner>['side'];\n /**\n * Alignment of the listbox relative to the control along the chosen side.\n * @default 'start'\n */\n align?: ComponentProps<typeof BaseCombobox.Positioner>['align'];\n /**\n * Gap in pixels between the control and the listbox.\n * @default 8\n */\n sideOffset?: ComponentProps<typeof BaseCombobox.Positioner>['sideOffset'];\n /**\n * Offset in pixels along the alignment axis.\n * @default 0\n */\n alignOffset?: ComponentProps<typeof BaseCombobox.Positioner>['alignOffset'];\n}\n\nexport function ComboboxContent({\n side = 'bottom',\n align = 'start',\n sideOffset = 8,\n alignOffset = 0,\n className,\n children,\n ...rest\n}: ComboboxContentProps) {\n const anchorContext = useContext(ComboboxAnchorContext);\n // Anchor to the chips control box in multi-select (see\n // ComboboxAnchorContext); single-select leaves this undefined so Base UI\n // anchors to the `ComboboxControl` (InputGroup) as before.\n const anchor = anchorContext?.anchorElement ?? undefined;\n return (\n <BaseCombobox.Portal>\n <BaseCombobox.Positioner\n className={styles.positioner}\n anchor={anchor}\n side={side}\n align={align}\n sideOffset={sideOffset}\n alignOffset={alignOffset}\n >\n <BaseCombobox.Popup\n data-pharos-slot=\"combobox-content\"\n className={cn(styles.content, className)}\n {...rest}\n >\n {children}\n </BaseCombobox.Popup>\n </BaseCombobox.Positioner>\n </BaseCombobox.Portal>\n );\n}\n\nComboboxContent.displayName = 'ComboboxContent';\n\n/**\n * The list of results. Wraps Base UI's `Combobox.List`, which accepts either\n * static children or a render function `(item, index) => ReactNode` that maps\n * over the (already filtered) `items` passed to the root.\n */\nexport type ComboboxListProps = ComponentProps<typeof BaseCombobox.List>;\n\nexport function ComboboxList({ className, ...rest }: ComboboxListProps) {\n return <BaseCombobox.List data-pharos-slot=\"combobox-list\" className={className} {...rest} />;\n}\n\nComboboxList.displayName = 'ComboboxList';\n\n/**\n * A single result row. Folds Base UI's `Item` + the selected-state\n * `ItemIndicator` check. The `value` prop is what `onValueChange` reports.\n */\nexport interface ComboboxItemProps extends ComponentProps<typeof BaseCombobox.Item> {}\n\nexport function ComboboxItem({ className, children, ...rest }: ComboboxItemProps) {\n return (\n <BaseCombobox.Item\n data-pharos-slot=\"combobox-item\"\n className={cn(styles.item, className)}\n {...rest}\n >\n <span className={styles.itemText}>{children}</span>\n <BaseCombobox.ItemIndicator className={styles.itemIndicator}>\n <CheckIcon />\n </BaseCombobox.ItemIndicator>\n </BaseCombobox.Item>\n );\n}\n\nComboboxItem.displayName = 'ComboboxItem';\n\n/**\n * Shown in place of the list when the filter matches nothing. Wraps Base UI's\n * `Combobox.Empty`, which is only rendered when there are no results.\n */\nexport type ComboboxEmptyProps = ComponentProps<typeof BaseCombobox.Empty>;\n\nexport function ComboboxEmpty({ className, ...rest }: ComboboxEmptyProps) {\n return (\n <BaseCombobox.Empty\n data-pharos-slot=\"combobox-empty\"\n className={cn(styles.empty, className)}\n {...rest}\n />\n );\n}\n\nComboboxEmpty.displayName = 'ComboboxEmpty';\n\n/** Groups related results under an optional `ComboboxGroupLabel` (`role=\"group\"`). */\nexport type ComboboxGroupProps = ComponentProps<typeof BaseCombobox.Group>;\n\nexport function ComboboxGroup(props: ComboboxGroupProps) {\n return <BaseCombobox.Group data-pharos-slot=\"combobox-group\" {...props} />;\n}\n\nComboboxGroup.displayName = 'ComboboxGroup';\n\n/** A non-interactive heading for a `ComboboxGroup`. Wraps `Combobox.GroupLabel`. */\nexport type ComboboxGroupLabelProps = ComponentProps<typeof BaseCombobox.GroupLabel>;\n\nexport function ComboboxGroupLabel({ className, ...rest }: ComboboxGroupLabelProps) {\n return (\n <BaseCombobox.GroupLabel\n data-pharos-slot=\"combobox-group-label\"\n className={cn(styles.label, className)}\n {...rest}\n />\n );\n}\n\nComboboxGroupLabel.displayName = 'ComboboxGroupLabel';\n\n/** A thin divider between groups of results. Wraps `Combobox.Separator`. */\nexport type ComboboxSeparatorProps = ComponentProps<typeof BaseCombobox.Separator>;\n\nexport function ComboboxSeparator({ className, ...rest }: ComboboxSeparatorProps) {\n return (\n <BaseCombobox.Separator\n data-pharos-slot=\"combobox-separator\"\n className={cn(styles.separator, className)}\n {...rest}\n />\n );\n}\n\nComboboxSeparator.displayName = 'ComboboxSeparator';\n","/* Sheet.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. Styles a modal panel that docks to a viewport edge and\n * slides in — the shadcn \"Sheet\" contract over Base UI's `Dialog` (modal:\n * focus trap, scroll lock, backdrop). Motion is driven by Base UI's\n * `data-starting-style` / `data-ending-style` state attributes.\n *\n * z-index: the backdrop and the panel share `--pharos-z-index-popover` — the\n * SAME overlay layer DropdownMenu / Popover / Select / Combobox use. Nesting\n * resolves by portal open order (DOM order), NOT by a higher modal z-index:\n * a Select or Popover opened from INSIDE the Sheet portals to <body> AFTER the\n * Sheet, so at equal z-index it paints above the panel. That is the primary\n * use case (a LanguageSelector inside a mobile drawer), so a dedicated higher\n * modal z-index would break it. See NAMING-decisions.md § Sheet. The token is\n * still consumer-overridable (fallback 1000). Defining a real z-index scale in\n * pharos-tokens is a tracked follow-up.\n */\n\n/* === Backdrop (Dialog.Backdrop) === */\n.backdrop {\n position: fixed;\n inset: 0;\n z-index: var(--pharos-z-index-popover, 1000);\n /* Scrim from the base-black token at 50% via color-mix — no hardcoded\n * rgba (NON-NEGOTIABLE rule 1). */\n background-color: color-mix(in srgb, var(--pharos-color-base-black) 50%, transparent);\n transition: opacity var(--pharos-duration-normal) var(--pharos-easing-out);\n}\n\n.backdrop[data-starting-style],\n.backdrop[data-ending-style] {\n opacity: 0;\n}\n\n/* === Panel (Dialog.Popup) === */\n.content {\n position: fixed;\n z-index: var(--pharos-z-index-popover, 1000);\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: var(--pharos-spacing-4);\n padding: var(--pharos-spacing-6);\n\n background-color: var(--pharos-color-base-white);\n box-shadow: var(--pharos-shadow-xl);\n\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n color: var(--pharos-color-neutral-900);\n\n outline: none;\n overflow-y: auto;\n\n transition: transform var(--pharos-duration-normal) var(--pharos-easing-out);\n}\n\n/* === Sides === */\n.sideRight {\n top: 0;\n bottom: 0;\n right: 0;\n height: 100dvh;\n width: min(24rem, 100vw);\n border-left: 1px solid var(--pharos-color-neutral-200);\n}\n.sideRight[data-starting-style],\n.sideRight[data-ending-style] {\n transform: translateX(100%);\n}\n\n.sideLeft {\n top: 0;\n bottom: 0;\n left: 0;\n height: 100dvh;\n width: min(24rem, 100vw);\n border-right: 1px solid var(--pharos-color-neutral-200);\n}\n.sideLeft[data-starting-style],\n.sideLeft[data-ending-style] {\n transform: translateX(-100%);\n}\n\n.sideTop {\n top: 0;\n left: 0;\n right: 0;\n width: 100vw;\n max-height: 100dvh;\n border-bottom: 1px solid var(--pharos-color-neutral-200);\n}\n.sideTop[data-starting-style],\n.sideTop[data-ending-style] {\n transform: translateY(-100%);\n}\n\n.sideBottom {\n bottom: 0;\n left: 0;\n right: 0;\n width: 100vw;\n max-height: 100dvh;\n border-top: 1px solid var(--pharos-color-neutral-200);\n}\n.sideBottom[data-starting-style],\n.sideBottom[data-ending-style] {\n transform: translateY(100%);\n}\n\n/* === Slots === */\n.header {\n display: flex;\n flex-direction: column;\n gap: var(--pharos-spacing-1);\n}\n\n.title {\n margin: 0;\n font-size: var(--pharos-font-size-lg);\n font-weight: var(--pharos-font-weight-semibold);\n line-height: 1.4;\n color: var(--pharos-color-neutral-900);\n}\n\n.description {\n margin: 0;\n font-size: var(--pharos-font-size-sm);\n line-height: 1.5;\n color: var(--pharos-color-neutral-600);\n}\n\n.footer {\n display: flex;\n gap: var(--pharos-spacing-2);\n justify-content: flex-end;\n margin-top: auto;\n}\n","import type { ComponentProps, ComponentPropsWithoutRef } from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/cn';\nimport styles from './Sheet.module.css';\n\n/**\n * Modal panel that docks to a viewport edge and slides in (the shadcn\n * **Sheet** contract). Wraps Base UI's `Dialog.*`, which implements the ARIA\n * **dialog** pattern as a **modal** disclosure: `role=\"dialog\"` +\n * `aria-modal`, focus trapped inside the panel, scroll locked, Escape and\n * backdrop-click dismiss, focus returns to the trigger on close.\n *\n * Distinct from `Popover` (D16, non-modal, anchored to its trigger) and\n * `DropdownMenu` (D15, menu-button). A Sheet is for a task surface that\n * should command the screen — mobile navigation, a filters panel, a\n * create/edit form. Naming follows shadcn (`Sheet*`); the compound surface\n * collapses Base UI's `Portal` + `Backdrop` + `Popup` into a single\n * `SheetContent` with a `side` prop, mirroring the ergonomics of the other\n * Pharos overlays.\n *\n * v1 ships the parts the known consumers need (the LanguageSelector /\n * MobileDrawer): Trigger, Content, Header, Footer, Title, Description, Close.\n *\n * @example\n * <Sheet>\n * <SheetTrigger render={<Button intent=\"secondary\">Menu</Button>} />\n * <SheetContent side=\"left\">\n * <SheetHeader>\n * <SheetTitle>Navigation</SheetTitle>\n * <SheetDescription>Jump to a section.</SheetDescription>\n * </SheetHeader>\n * <nav>…</nav>\n * <SheetFooter>\n * <SheetClose render={<Button intent=\"ghost\">Close</Button>} />\n * </SheetFooter>\n * </SheetContent>\n * </Sheet>\n */\nexport type SheetProps = ComponentProps<typeof Dialog.Root>;\n\nexport function Sheet(props: SheetProps) {\n return <Dialog.Root {...props} />;\n}\n\nSheet.displayName = 'Sheet';\n\n/**\n * The element that opens the sheet. Renders a native `<button>` by default;\n * pass `render` to compose any control (`render={<Button…/>}`,\n * `render={<IconButton…/>}`).\n */\nexport type SheetTriggerProps = ComponentProps<typeof Dialog.Trigger>;\n\nexport function SheetTrigger(props: SheetTriggerProps) {\n return <Dialog.Trigger {...props} />;\n}\n\nSheetTrigger.displayName = 'SheetTrigger';\n\nconst sheetVariants = cva(styles.content, {\n variants: {\n side: {\n top: styles.sideTop,\n right: styles.sideRight,\n bottom: styles.sideBottom,\n left: styles.sideLeft,\n },\n },\n defaultVariants: {\n side: 'right',\n },\n});\n\ntype SheetVariantProps = VariantProps<typeof sheetVariants>;\n\n/**\n * The panel surface. Collapses Base UI's `Portal` + `Backdrop` + `Popup`. The\n * `side` prop docks it to one of the four viewport edges (default `right`)\n * and picks the matching slide-in transition. The backdrop and panel share\n * the `--pharos-z-index-popover` overlay layer so a Select / Popover opened\n * from inside the sheet still stacks above it (portal open order).\n */\nexport interface SheetContentProps extends ComponentProps<typeof Dialog.Popup>, SheetVariantProps {}\n\nexport function SheetContent({ side = 'right', className, children, ...rest }: SheetContentProps) {\n return (\n <Dialog.Portal>\n <Dialog.Backdrop className={styles.backdrop} />\n <Dialog.Popup\n data-pharos-slot=\"sheet-content\"\n data-pharos-side={side ?? 'right'}\n className={cn(sheetVariants({ side }), className)}\n {...rest}\n >\n {children}\n </Dialog.Popup>\n </Dialog.Portal>\n );\n}\n\nSheetContent.displayName = 'SheetContent';\n\n/** Groups the title + description at the top of the panel. */\nexport interface SheetHeaderProps extends ComponentPropsWithoutRef<'div'> {}\n\nexport function SheetHeader({ className, ...rest }: SheetHeaderProps) {\n return <div data-pharos-slot=\"sheet-header\" className={cn(styles.header, className)} {...rest} />;\n}\n\nSheetHeader.displayName = 'SheetHeader';\n\n/**\n * The panel's accessible name. Renders Base UI's `Dialog.Title`, which wires\n * `aria-labelledby` on the popup — required for the dialog to be announced.\n * Renders as an `<h2>` by default.\n */\nexport type SheetTitleProps = ComponentProps<typeof Dialog.Title>;\n\nexport function SheetTitle({ className, ...rest }: SheetTitleProps) {\n return (\n <Dialog.Title\n data-pharos-slot=\"sheet-title\"\n className={cn(styles.title, className)}\n {...rest}\n />\n );\n}\n\nSheetTitle.displayName = 'SheetTitle';\n\n/**\n * Supporting copy under the title. Renders Base UI's `Dialog.Description`,\n * which wires `aria-describedby` on the popup. Renders as a `<p>` by default.\n */\nexport type SheetDescriptionProps = ComponentProps<typeof Dialog.Description>;\n\nexport function SheetDescription({ className, ...rest }: SheetDescriptionProps) {\n return (\n <Dialog.Description\n data-pharos-slot=\"sheet-description\"\n className={cn(styles.description, className)}\n {...rest}\n />\n );\n}\n\nSheetDescription.displayName = 'SheetDescription';\n\n/** Bottom action row, pushed to the panel's end. */\nexport interface SheetFooterProps extends ComponentPropsWithoutRef<'div'> {}\n\nexport function SheetFooter({ className, ...rest }: SheetFooterProps) {\n return <div data-pharos-slot=\"sheet-footer\" className={cn(styles.footer, className)} {...rest} />;\n}\n\nSheetFooter.displayName = 'SheetFooter';\n\n/**\n * A button that closes the sheet. Renders Base UI's `Dialog.Close` (a native\n * `<button>`); compose any control via `render`.\n */\nexport type SheetCloseProps = ComponentProps<typeof Dialog.Close>;\n\nexport function SheetClose(props: SheetCloseProps) {\n return <Dialog.Close data-pharos-slot=\"sheet-close\" {...props} />;\n}\n\nSheetClose.displayName = 'SheetClose';\n\nexport { sheetVariants };\n","/* Dialog.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. Styles a centered modal panel — the shadcn \"Dialog\"\n * contract over Base UI's `Dialog` (modal: focus trap, scroll lock, backdrop).\n * Sibling of Sheet (D18): same Base UI primitive, but centered with a\n * fade-and-scale transition instead of an edge slide. Motion is driven by Base\n * UI's `data-starting-style` / `data-ending-style` state attributes.\n *\n * z-index: backdrop and panel share `--pharos-z-index-popover` — the SAME\n * overlay layer DropdownMenu / Popover / Select / Combobox / Sheet use. Nesting\n * resolves by portal open order (DOM order), so a Select or Popover opened from\n * INSIDE the dialog still stacks above it. Consumer-overridable (fallback 1000).\n * See NAMING-decisions.md § Sheet for the full rationale.\n */\n\n/* === Backdrop (Dialog.Backdrop) === */\n.backdrop {\n position: fixed;\n inset: 0;\n z-index: var(--pharos-z-index-popover, 1000);\n background-color: color-mix(in srgb, var(--pharos-color-base-black) 50%, transparent);\n transition: opacity var(--pharos-duration-normal) var(--pharos-easing-out);\n}\n\n.backdrop[data-starting-style],\n.backdrop[data-ending-style] {\n opacity: 0;\n}\n\n/* === Panel (Dialog.Popup) === */\n.content {\n position: fixed;\n top: 50%;\n left: 50%;\n /* Centering lives in the transform; the scale for the open/close motion is\n * composed on top of the same translate (see starting/ending styles). */\n transform: translate(-50%, -50%);\n transform-origin: center;\n z-index: var(--pharos-z-index-popover, 1000);\n\n box-sizing: border-box;\n width: calc(100% - 2 * var(--pharos-spacing-4));\n max-width: 32rem;\n max-height: calc(100dvh - 2 * var(--pharos-spacing-6));\n overflow-y: auto;\n\n display: flex;\n flex-direction: column;\n gap: var(--pharos-spacing-4);\n padding: var(--pharos-spacing-6);\n\n background-color: var(--pharos-color-base-white);\n border-radius: var(--pharos-radius-lg);\n box-shadow: var(--pharos-shadow-xl);\n\n font-family: var(--pharos-font-family-sans);\n font-size: var(--pharos-font-size-sm);\n color: var(--pharos-color-neutral-900);\n\n outline: none;\n\n transition-property: opacity, transform;\n transition-duration: var(--pharos-duration-fast);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.content[data-starting-style],\n.content[data-ending-style] {\n opacity: 0;\n transform: translate(-50%, -50%) scale(0.96);\n}\n\n/* === Slots === */\n.header {\n display: flex;\n flex-direction: column;\n gap: var(--pharos-spacing-1);\n}\n\n.title {\n margin: 0;\n font-size: var(--pharos-font-size-lg);\n font-weight: var(--pharos-font-weight-semibold);\n line-height: 1.4;\n color: var(--pharos-color-neutral-900);\n}\n\n.description {\n margin: 0;\n font-size: var(--pharos-font-size-sm);\n line-height: 1.5;\n color: var(--pharos-color-neutral-600);\n}\n\n.footer {\n display: flex;\n gap: var(--pharos-spacing-2);\n justify-content: flex-end;\n margin-top: auto;\n}\n","import type { ComponentProps, ComponentPropsWithoutRef } from 'react';\nimport { Dialog as BaseDialog } from '@base-ui/react/dialog';\nimport { cn } from '../lib/cn';\nimport styles from './Dialog.module.css';\n\n/**\n * Centered modal panel (the shadcn **Dialog** contract). Wraps Base UI's\n * `Dialog.*`, which implements the ARIA **dialog** pattern as a **modal**\n * disclosure: `role=\"dialog\"` + `aria-modal`, focus trapped inside the panel,\n * scroll locked, Escape and backdrop-click dismiss, focus returns to the\n * trigger on close.\n *\n * Sibling of `Sheet` (D18): both wrap the same Base UI `Dialog` primitive.\n * Dialog is the **centered** modal (confirmations, short forms, focused\n * tasks); Sheet is the **edge-docked** panel. Distinct from `Popover` (D16,\n * non-modal, anchored) and `DropdownMenu` (D15, menu-button). Naming follows\n * shadcn (`Dialog*`); `DialogContent` collapses Base UI's `Portal` +\n * `Backdrop` + `Popup` into one part, mirroring the other Pharos overlays.\n *\n * @example\n * <Dialog>\n * <DialogTrigger render={<Button intent=\"destructive\">Delete</Button>} />\n * <DialogContent>\n * <DialogHeader>\n * <DialogTitle>Delete project?</DialogTitle>\n * <DialogDescription>This cannot be undone.</DialogDescription>\n * </DialogHeader>\n * <DialogFooter>\n * <DialogClose render={<Button intent=\"ghost\">Cancel</Button>} />\n * <Button intent=\"destructive\" onClick={onDelete}>Delete</Button>\n * </DialogFooter>\n * </DialogContent>\n * </Dialog>\n */\nexport type DialogProps = ComponentProps<typeof BaseDialog.Root>;\n\nexport function Dialog(props: DialogProps) {\n return <BaseDialog.Root {...props} />;\n}\n\nDialog.displayName = 'Dialog';\n\n/**\n * The element that opens the dialog. Renders a native `<button>` by default;\n * pass `render` to compose any control (`render={<Button…/>}`).\n */\nexport type DialogTriggerProps = ComponentProps<typeof BaseDialog.Trigger>;\n\nexport function DialogTrigger(props: DialogTriggerProps) {\n return <BaseDialog.Trigger {...props} />;\n}\n\nDialogTrigger.displayName = 'DialogTrigger';\n\n/**\n * The centered panel. Collapses Base UI's `Portal` + `Backdrop` + `Popup`. The\n * backdrop and panel share the `--pharos-z-index-popover` overlay layer so a\n * Select / Popover opened from inside the dialog still stacks above it (portal\n * open order).\n */\nexport interface DialogContentProps extends ComponentProps<typeof BaseDialog.Popup> {}\n\nexport function DialogContent({ className, children, ...rest }: DialogContentProps) {\n return (\n <BaseDialog.Portal>\n <BaseDialog.Backdrop className={styles.backdrop} />\n <BaseDialog.Popup\n data-pharos-slot=\"dialog-content\"\n className={cn(styles.content, className)}\n {...rest}\n >\n {children}\n </BaseDialog.Popup>\n </BaseDialog.Portal>\n );\n}\n\nDialogContent.displayName = 'DialogContent';\n\n/** Groups the title + description at the top of the panel. */\nexport interface DialogHeaderProps extends ComponentPropsWithoutRef<'div'> {}\n\nexport function DialogHeader({ className, ...rest }: DialogHeaderProps) {\n return (\n <div data-pharos-slot=\"dialog-header\" className={cn(styles.header, className)} {...rest} />\n );\n}\n\nDialogHeader.displayName = 'DialogHeader';\n\n/**\n * The dialog's accessible name. Renders Base UI's `Dialog.Title`, which wires\n * `aria-labelledby` on the popup — required for the dialog to be announced.\n * Renders as an `<h2>` by default.\n */\nexport type DialogTitleProps = ComponentProps<typeof BaseDialog.Title>;\n\nexport function DialogTitle({ className, ...rest }: DialogTitleProps) {\n return (\n <BaseDialog.Title\n data-pharos-slot=\"dialog-title\"\n className={cn(styles.title, className)}\n {...rest}\n />\n );\n}\n\nDialogTitle.displayName = 'DialogTitle';\n\n/**\n * Supporting copy under the title. Renders Base UI's `Dialog.Description`,\n * which wires `aria-describedby` on the popup. Renders as a `<p>` by default.\n */\nexport type DialogDescriptionProps = ComponentProps<typeof BaseDialog.Description>;\n\nexport function DialogDescription({ className, ...rest }: DialogDescriptionProps) {\n return (\n <BaseDialog.Description\n data-pharos-slot=\"dialog-description\"\n className={cn(styles.description, className)}\n {...rest}\n />\n );\n}\n\nDialogDescription.displayName = 'DialogDescription';\n\n/** Bottom action row, pushed to the panel's end. */\nexport interface DialogFooterProps extends ComponentPropsWithoutRef<'div'> {}\n\nexport function DialogFooter({ className, ...rest }: DialogFooterProps) {\n return (\n <div data-pharos-slot=\"dialog-footer\" className={cn(styles.footer, className)} {...rest} />\n );\n}\n\nDialogFooter.displayName = 'DialogFooter';\n\n/**\n * A button that closes the dialog. Renders Base UI's `Dialog.Close` (a native\n * `<button>`); compose any control via `render`.\n */\nexport type DialogCloseProps = ComponentProps<typeof BaseDialog.Close>;\n\nexport function DialogClose(props: DialogCloseProps) {\n return <BaseDialog.Close data-pharos-slot=\"dialog-close\" {...props} />;\n}\n\nDialogClose.displayName = 'DialogClose';\n","/* Checkbox.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. Styles a single square control (the shadcn \"Checkbox\"\n * contract) over Base UI's `Checkbox`. State is driven by Base UI's\n * `data-checked` / `data-indeterminate` / `data-disabled` attributes.\n *\n * The atom owns no label (Escuela 1, D11): the consumer pairs it with a\n * `<label htmlFor>`. Error state is conveyed with `aria-invalid`, like Input /\n * Textarea. The check / dash marks are inline SVG — the library ships no icon\n * dependency (same convention as Spinner / Select).\n */\n\n.root {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: var(--pharos-spacing-5);\n height: var(--pharos-spacing-5);\n padding: 0;\n\n border: 1px solid var(--pharos-color-neutral-500);\n border-radius: var(--pharos-radius-sm);\n background-color: var(--pharos-color-base-white);\n /* The indicator inherits this via `currentColor`. */\n color: var(--pharos-color-base-white);\n\n cursor: pointer;\n transition-property: background-color, border-color, box-shadow;\n transition-duration: var(--pharos-duration-fast);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.root:hover:not([data-disabled]):not([data-checked]):not([data-indeterminate]) {\n border-color: var(--pharos-color-neutral-700);\n}\n\n/* Checked and indeterminate share the filled look — the mark differs. */\n.root[data-checked],\n.root[data-indeterminate] {\n background-color: var(--pharos-color-neutral-900);\n border-color: var(--pharos-color-neutral-900);\n}\n\n/* Error state via the standard aria-invalid attribute (D11). */\n.root[aria-invalid='true'] {\n border-color: var(--pharos-color-semantic-error-fg);\n}\n\n.root:focus-visible {\n outline: none;\n /* Same two-stop ring as Button / Input so the system has one focus look. */\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-primary-600);\n}\n\n.root[data-disabled] {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.indicator {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--pharos-color-base-white);\n}\n\n.icon {\n width: var(--pharos-spacing-3);\n height: var(--pharos-spacing-3);\n}\n\n/* The Indicator renders only when checked or indeterminate; inside it the\n * check shows by default and the dash takes over for the indeterminate state. */\n.iconIndeterminate {\n display: none;\n}\n\n.indicator[data-indeterminate] .iconCheck {\n display: none;\n}\n\n.indicator[data-indeterminate] .iconIndeterminate {\n display: block;\n}\n","import type { ComponentProps } from 'react';\nimport { Checkbox as BaseCheckbox } from '@base-ui/react/checkbox';\nimport { cn } from '../lib/cn';\nimport styles from './Checkbox.module.css';\n\n/**\n * A single square checkbox control (the shadcn **Checkbox** contract). Wraps\n * Base UI's `Checkbox`, which renders a `role=\"checkbox\"` button plus a hidden\n * form input, and carries the ARIA state (`aria-checked`, including the\n * `mixed` value for the indeterminate state), keyboard toggle (Space) and\n * focus handling.\n *\n * Like `Input` / `Textarea` (Escuela 1, D11), the atom owns no label: pair it\n * with a `<label htmlFor>`. Error state is conveyed via `aria-invalid`, not a\n * custom prop.\n *\n * Indeterminate uses Base UI's boolean `indeterminate` prop (not shadcn's\n * `checked=\"indeterminate\"` string union — a Radix quirk). Base UI's split is\n * cleaner and better typed; per Rule #0 the DS follows the primitive here.\n *\n * @example\n * <label htmlFor=\"terms\" className=\"flex items-center gap-2\">\n * <Checkbox id=\"terms\" checked={agreed} onCheckedChange={setAgreed} />\n * I accept the terms\n * </label>\n */\nexport interface CheckboxProps extends ComponentProps<typeof BaseCheckbox.Root> {}\n\nfunction CheckIcon() {\n return (\n <svg\n className={cn(styles.icon, styles.iconCheck)}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n );\n}\n\nfunction IndeterminateIcon() {\n return (\n <svg\n className={cn(styles.icon, styles.iconIndeterminate)}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M5 12h14\" />\n </svg>\n );\n}\n\nexport function Checkbox({ className, ...rest }: CheckboxProps) {\n return (\n <BaseCheckbox.Root data-pharos-slot=\"checkbox\" className={cn(styles.root, className)} {...rest}>\n <BaseCheckbox.Indicator className={styles.indicator}>\n <CheckIcon />\n <IndeterminateIcon />\n </BaseCheckbox.Indicator>\n </BaseCheckbox.Root>\n );\n}\n\nCheckbox.displayName = 'Checkbox';\n","/* Switch.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. Styles a single on/off toggle (the shadcn \"Switch\"\n * contract) over Base UI's `Switch`. State is driven by Base UI's\n * `data-checked` / `data-disabled` attributes.\n *\n * The atom owns no label (Escuela 1, D11): the consumer pairs it with a\n * `<label htmlFor>`. Error state is conveyed with `aria-invalid`, like Input /\n * Textarea / Checkbox. No icon dependency — the thumb is a plain `<span>`.\n *\n * Geometry is fully token-derived: a 40×24 track (spacing-10 × spacing-6) with\n * spacing-1 (4px) padding houses a 16px (spacing-4) thumb, so the thumb travels\n * exactly spacing-4 (32px inner − 16px thumb) with no hardcoded pixel value.\n */\n\n.root {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n width: var(--pharos-spacing-10);\n height: var(--pharos-spacing-6);\n padding: var(--pharos-spacing-1);\n\n border: none;\n border-radius: var(--pharos-radius-full);\n /* Off track. The perceivable state indicator is the thumb (white with a\n * neutral-500 hairline, ≈5.4:1) — it stays distinguishable on both the light\n * off track and the dark on track, satisfying WCAG 1.4.11. */\n background-color: var(--pharos-color-neutral-300);\n\n cursor: pointer;\n transition-property: background-color, box-shadow;\n transition-duration: var(--pharos-duration-fast);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.root:hover:not([data-disabled]):not([data-checked]) {\n background-color: var(--pharos-color-neutral-400);\n}\n\n/* On track — the neutral-900 fill, matching the Checkbox's checked look. */\n.root[data-checked] {\n background-color: var(--pharos-color-neutral-900);\n}\n\n.root[data-checked]:hover:not([data-disabled]) {\n background-color: var(--pharos-color-neutral-700);\n}\n\n/* Error state via the standard aria-invalid attribute (D11). An inset ring so\n * the track keeps its geometry (no border-driven layout shift on the thumb). */\n.root[aria-invalid='true'] {\n box-shadow: inset 0 0 0 1px var(--pharos-color-semantic-error-fg);\n}\n\n.root:focus-visible {\n outline: none;\n /* Same two-stop ring as Button / Input / Checkbox so the system has one\n * focus look. */\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-primary-600);\n}\n\n.root[data-disabled] {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.thumb {\n box-sizing: border-box;\n width: var(--pharos-spacing-4);\n height: var(--pharos-spacing-4);\n border: 1px solid var(--pharos-color-neutral-500);\n border-radius: var(--pharos-radius-full);\n background-color: var(--pharos-color-base-white);\n\n transition-property: transform;\n transition-duration: var(--pharos-duration-fast);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n/* Slide to the far edge: inner width (32px) − thumb (16px) = spacing-4. */\n.root[data-checked] .thumb {\n transform: translateX(var(--pharos-spacing-4));\n}\n","import type { ComponentProps } from 'react';\nimport { Switch as BaseSwitch } from '@base-ui/react/switch';\nimport { cn } from '../lib/cn';\nimport styles from './Switch.module.css';\n\n/**\n * A single on/off toggle control (the shadcn **Switch** contract). Wraps Base\n * UI's `Switch`, which renders a `role=\"switch\"` button plus a hidden form\n * input, and carries the ARIA state (`aria-checked`), keyboard toggle (Space)\n * and focus handling.\n *\n * Sibling to `Checkbox` (D20) in the boolean form-control family: same\n * `checked` / `onCheckedChange` / `disabled` API, same shared focus ring. The\n * difference is semantic — a Switch takes effect immediately (a setting you\n * flip), a Checkbox is a selection that is usually submitted with a form. It\n * has no third `indeterminate` state (a toggle is binary).\n *\n * Like `Input` / `Textarea` / `Checkbox` (Escuela 1, D11), the atom owns no\n * label: pair it with a `<label htmlFor>`. Error state is conveyed via\n * `aria-invalid`, not a custom prop.\n *\n * @example\n * <label htmlFor=\"notify\" className=\"flex items-center gap-2\">\n * <Switch id=\"notify\" checked={enabled} onCheckedChange={setEnabled} />\n * Enable notifications\n * </label>\n */\nexport interface SwitchProps extends ComponentProps<typeof BaseSwitch.Root> {}\n\nexport function Switch({ className, ...rest }: SwitchProps) {\n return (\n <BaseSwitch.Root data-pharos-slot=\"switch\" className={cn(styles.root, className)} {...rest}>\n <BaseSwitch.Thumb className={styles.thumb} />\n </BaseSwitch.Root>\n );\n}\n\nSwitch.displayName = 'Switch';\n","/* RadioGroup.module.css\n *\n * Per-component CSS Modules definition (Decision D9). Hashed into\n * `dist/styles.css`. Styles the shadcn \"RadioGroup\" contract over Base UI's\n * `RadioGroup` + `Radio`. Item state is driven by Base UI's `data-checked` /\n * `data-disabled` attributes.\n *\n * The atom owns no labels (Escuela 1, D11): the consumer pairs the group with a\n * `<label>` / `aria-labelledby` and each item with a `<label htmlFor>`. Error\n * state is conveyed with `aria-invalid` on the group. The selected dot is a\n * plain `<span>` — no icon dependency (Base UI mounts it only when checked).\n *\n * The item mirrors Checkbox's 20px (spacing-5) control on the token grid, but\n * circular: a ring that stays visible with an inner dot on selection, the\n * state-of-the-art radio look (distinct from Checkbox's filled square).\n */\n\n.group {\n display: flex;\n flex-direction: column;\n gap: var(--pharos-spacing-2);\n}\n\n.item {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: var(--pharos-spacing-5);\n height: var(--pharos-spacing-5);\n padding: 0;\n\n border: 1px solid var(--pharos-color-neutral-500);\n border-radius: var(--pharos-radius-full);\n background-color: var(--pharos-color-base-white);\n\n cursor: pointer;\n transition-property: background-color, border-color, box-shadow;\n transition-duration: var(--pharos-duration-fast);\n transition-timing-function: var(--pharos-easing-out);\n}\n\n.item:hover:not([data-disabled]):not([data-checked]) {\n border-color: var(--pharos-color-neutral-700);\n}\n\n/* Selected — the ring darkens to match the Checkbox / Switch \"active\" color and\n * the inner dot appears. */\n.item[data-checked] {\n border-color: var(--pharos-color-neutral-900);\n}\n\n/* Error state via aria-invalid on the group (radio error is a group concern,\n * D11) — every item shows the error ring. */\n.group[aria-invalid='true'] .item {\n border-color: var(--pharos-color-semantic-error-fg);\n}\n\n.item:focus-visible {\n outline: none;\n /* Same two-stop ring as Button / Input / Checkbox / Switch so the system has\n * one focus look. */\n box-shadow:\n 0 0 0 2px var(--pharos-color-base-white),\n 0 0 0 4px var(--pharos-color-primary-600);\n}\n\n.item[data-disabled] {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n/* The inner dot — Base UI mounts the Indicator only while selected. */\n.indicator {\n display: inline-flex;\n width: var(--pharos-spacing-2);\n height: var(--pharos-spacing-2);\n border-radius: var(--pharos-radius-full);\n background-color: var(--pharos-color-neutral-900);\n}\n","import type { ComponentProps } from 'react';\nimport { RadioGroup as BaseRadioGroup } from '@base-ui/react/radio-group';\nimport { Radio as BaseRadio } from '@base-ui/react/radio';\nimport { cn } from '../lib/cn';\nimport styles from './RadioGroup.module.css';\n\n/**\n * A set of mutually exclusive options (the shadcn **RadioGroup** contract).\n * Wraps Base UI's `RadioGroup`, which renders a `role=\"radiogroup\"` container\n * carrying the shared value, roving-tabindex arrow-key navigation and the\n * hidden form input.\n *\n * Third of the boolean/choice form-control family (Checkbox → Switch →\n * **Radio**). Unlike Checkbox / Switch — single self-contained atoms — Radio is\n * **compound** (`RadioGroup` + `RadioGroupItem`), because the selection\n * semantics (single choice, arrow navigation, the submitted value) live on the\n * group, not the individual control. This matches shadcn and ARIA APG.\n *\n * Like the other form atoms (Escuela 1, D11) it owns no labels: pair the group\n * with a `<label>` / `aria-labelledby`, and each item with a `<label htmlFor>`.\n * Error state is conveyed via `aria-invalid` on the group, not a custom prop.\n *\n * @example\n * <RadioGroup defaultValue=\"card\" aria-labelledby=\"pay-label\">\n * <span id=\"pay-label\">Payment method</span>\n * <label htmlFor=\"pay-card\">\n * <RadioGroupItem id=\"pay-card\" value=\"card\" /> Card\n * </label>\n * <label htmlFor=\"pay-cash\">\n * <RadioGroupItem id=\"pay-cash\" value=\"cash\" /> Cash\n * </label>\n * </RadioGroup>\n */\nexport interface RadioGroupProps extends ComponentProps<typeof BaseRadioGroup> {}\n\nexport function RadioGroup({ className, ...rest }: RadioGroupProps) {\n return (\n <BaseRadioGroup\n data-pharos-slot=\"radio-group\"\n className={cn(styles.group, className)}\n {...rest}\n />\n );\n}\n\nRadioGroup.displayName = 'RadioGroup';\n\n/**\n * A single radio button within a `RadioGroup`. Wraps Base UI's `Radio` — a\n * `role=\"radio\"` button plus a hidden form input — and requires a unique\n * `value` identifying it within the group. The selected dot is a `<span>`\n * styled with CSS (no icon dependency).\n */\nexport interface RadioGroupItemProps extends ComponentProps<typeof BaseRadio.Root> {}\n\nexport function RadioGroupItem({ className, ...rest }: RadioGroupItemProps) {\n return (\n <BaseRadio.Root\n data-pharos-slot=\"radio-group-item\"\n className={cn(styles.item, className)}\n {...rest}\n >\n <BaseRadio.Indicator className={styles.indicator} />\n </BaseRadio.Root>\n );\n}\n\nRadioGroupItem.displayName = 'RadioGroupItem';\n"],"x_google_ignoreList":[0,1],"mappings":"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"}