@code-sherpas/pharos-react 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -46,7 +46,7 @@ export function Demo() {
46
46
 
47
47
  ## Rules for consumers
48
48
 
49
- See [RULES.md](./RULES.md) — published with the package.
49
+ See [the rules for consumers](https://app.notion.com/p/4071751eee1081239c1efa6a8fdde525) in Notion (Programa Pharos), where Pharos keeps its documentation (decision D27).
50
50
 
51
51
  ## Storybook
52
52
 
@@ -15,7 +15,8 @@ import { VariantProps } from 'class-variance-authority';
15
15
  * single component with a `searchable` axis. (Free-text entry — typing a
16
16
  * value that is not in the list — is Base UI's separate `Autocomplete`
17
17
  * primitive and is deferred; every known Alexandria call-site filters a known
18
- * set.) See `NAMING-decisions.md` § Combobox.
18
+ * set.) See the Select + Combobox page of the naming decisions:
19
+ * https://app.notion.com/p/4071751eee108172bf64fdbe0fe8a94b
19
20
  *
20
21
  * Multi-select is the `multiple` axis on the root (not a separate atom),
21
22
  * matching Base UI / shadcn / React Aria. With `multiple`, selections render
@@ -1 +1 @@
1
- {"version":3,"file":"Combobox.d.ts","sourceRoot":"","sources":["../../src/components/Combobox.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Combobox.d.ts","sourceRoot":"","sources":["../../src/components/Combobox.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Combobox.js","names":[],"sources":["../../src/components/Combobox.tsx"],"sourcesContent":["'use client';\n\nimport {\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"],"mappings":"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"}
1
+ {"version":3,"file":"Combobox.js","names":[],"sources":["../../src/components/Combobox.tsx"],"sourcesContent":["'use client';\n\nimport {\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 the Select + Combobox page of the naming decisions:\n * https://app.notion.com/p/4071751eee108172bf64fdbe0fe8a94b\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"],"mappings":"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"}
@@ -12,7 +12,7 @@ import { Menu } from '@base-ui/react/menu';
12
12
  * disclosure/dialog contract). 7 of 8 surveyed design systems separate
13
13
  * the two for exactly this reason — putting `role="menu"` around
14
14
  * arbitrary content breaks screen-reader expectations. See
15
- * `NAMING-decisions.md` § DropdownMenu.
15
+ * the DropdownMenu page of the naming decisions: https://app.notion.com/p/4071751eee10815ab61dc5e63758da12
16
16
  *
17
17
  * Naming follows shadcn (`DropdownMenu*`, not Base UI's `Menu`). The
18
18
  * compound surface collapses Base UI's `Portal` + `Positioner` + `Popup`
@@ -1 +1 @@
1
- {"version":3,"file":"DropdownMenu.js","names":[],"sources":["../../src/components/DropdownMenu.tsx"],"sourcesContent":["'use client';\n\nimport 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"],"mappings":";;;;;;AA4CA,SAAgB,EAAa,GAA0B;CACrD,OAAO,kBAAC,EAAK,MAAN,EAAW,GAAI,EAAQ,CAAA;AAChC;AAEA,EAAa,cAAc;AAU3B,SAAgB,EAAoB,GAAiC;CACnE,OAAO,kBAAC,EAAK,SAAN,EAAc,GAAI,EAAQ,CAAA;AACnC;AAEA,EAAoB,cAAc;AA0BlC,SAAgB,EAAoB,EAClC,UAAO,UACP,WAAQ,SACR,gBAAa,GACb,cACA,aACA,GAAG,KACwB;CAC3B,OACE,kBAAC,EAAK,QAAN,EAAA,UACE,kBAAC,EAAK,YAAN;EACE,WAAW,EAAO;EACZ;EACC;EACK;YAEZ,kBAAC,EAAK,OAAN;GACE,oBAAiB;GACjB,WAAW,EAAG,EAAO,SAAS,CAAS;GACvC,GAAI;GAEH;EACS,CAAA;CACG,CAAA,EACN,CAAA;AAEjB;AAEA,EAAoB,cAAc;AAiBlC,SAAgB,EAAiB,EAC/B,aAAU,WACV,cACA,GAAG,KACqB;CACxB,OACE,kBAAC,EAAK,MAAN;EACE,uBAAqB;EACrB,WAAW,EAAG,EAAO,MAAM,MAAY,iBAAiB,EAAO,iBAAiB,CAAS;EACzF,GAAI;CACL,CAAA;AAEL;AAEA,EAAiB,cAAc;AAK/B,SAAgB,EAAsB,EAAE,cAAW,GAAG,KAAoC;CACxF,OACE,kBAAC,EAAK,WAAN;EACE,oBAAiB;EACjB,WAAW,EAAG,EAAO,WAAW,CAAS;EACzC,GAAI;CACL,CAAA;AAEL;AAEA,EAAsB,cAAc;AAUpC,SAAgB,EAAkB,EAAE,cAAW,GAAG,KAAgC;CAChF,OACE,kBAAC,EAAK,YAAN;EACE,oBAAiB;EACjB,WAAW,EAAG,EAAO,OAAO,CAAS;EACrC,GAAI;CACL,CAAA;AAEL;AAEA,EAAkB,cAAc;AAQhC,SAAgB,EAAkB,GAA+B;CAC/D,OAAO,kBAAC,EAAK,OAAN;EAAY,oBAAiB;EAAsB,GAAI;CAAQ,CAAA;AACxE;AAEA,EAAkB,cAAc"}
1
+ {"version":3,"file":"DropdownMenu.js","names":[],"sources":["../../src/components/DropdownMenu.tsx"],"sourcesContent":["'use client';\n\nimport 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 * the DropdownMenu page of the naming decisions: https://app.notion.com/p/4071751eee10815ab61dc5e63758da12\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"],"mappings":";;;;;;AA4CA,SAAgB,EAAa,GAA0B;CACrD,OAAO,kBAAC,EAAK,MAAN,EAAW,GAAI,EAAQ,CAAA;AAChC;AAEA,EAAa,cAAc;AAU3B,SAAgB,EAAoB,GAAiC;CACnE,OAAO,kBAAC,EAAK,SAAN,EAAc,GAAI,EAAQ,CAAA;AACnC;AAEA,EAAoB,cAAc;AA0BlC,SAAgB,EAAoB,EAClC,UAAO,UACP,WAAQ,SACR,gBAAa,GACb,cACA,aACA,GAAG,KACwB;CAC3B,OACE,kBAAC,EAAK,QAAN,EAAA,UACE,kBAAC,EAAK,YAAN;EACE,WAAW,EAAO;EACZ;EACC;EACK;YAEZ,kBAAC,EAAK,OAAN;GACE,oBAAiB;GACjB,WAAW,EAAG,EAAO,SAAS,CAAS;GACvC,GAAI;GAEH;EACS,CAAA;CACG,CAAA,EACN,CAAA;AAEjB;AAEA,EAAoB,cAAc;AAiBlC,SAAgB,EAAiB,EAC/B,aAAU,WACV,cACA,GAAG,KACqB;CACxB,OACE,kBAAC,EAAK,MAAN;EACE,uBAAqB;EACrB,WAAW,EAAG,EAAO,MAAM,MAAY,iBAAiB,EAAO,iBAAiB,CAAS;EACzF,GAAI;CACL,CAAA;AAEL;AAEA,EAAiB,cAAc;AAK/B,SAAgB,EAAsB,EAAE,cAAW,GAAG,KAAoC;CACxF,OACE,kBAAC,EAAK,WAAN;EACE,oBAAiB;EACjB,WAAW,EAAG,EAAO,WAAW,CAAS;EACzC,GAAI;CACL,CAAA;AAEL;AAEA,EAAsB,cAAc;AAUpC,SAAgB,EAAkB,EAAE,cAAW,GAAG,KAAgC;CAChF,OACE,kBAAC,EAAK,YAAN;EACE,oBAAiB;EACjB,WAAW,EAAG,EAAO,OAAO,CAAS;EACrC,GAAI;CACL,CAAA;AAEL;AAEA,EAAkB,cAAc;AAQhC,SAAgB,EAAkB,GAA+B;CAC/D,OAAO,kBAAC,EAAK,OAAN;EAAY,oBAAiB;EAAsB,GAAI;CAAQ,CAAA;AACxE;AAEA,EAAkB,cAAc"}
@@ -15,7 +15,8 @@ import { Popover as BasePopover } from '@base-ui/react/popover';
15
15
  * systems separate the two for exactly this reason: wrapping `role="menu"`
16
16
  * around free-form content breaks screen-reader expectations. The two atoms
17
17
  * wrap different Base UI primitives (`Menu` vs `Popover`) — they do not
18
- * share machinery. See `NAMING-decisions.md` § Popover.
18
+ * share machinery. See the Popover page of the naming decisions:
19
+ * https://app.notion.com/p/4071751eee1081239536dc0517fce057
19
20
  *
20
21
  * Naming follows shadcn (`Popover*`). The compound surface collapses Base
21
22
  * UI's `Portal` + `Positioner` + `Popup` into a single `PopoverContent`
@@ -1 +1 @@
1
- {"version":3,"file":"Popover.d.ts","sourceRoot":"","sources":["../../src/components/Popover.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAIhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,MAAM,YAAY,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;AAEnE,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,+BAE1C;yBAFe,OAAO;;;AAMvB;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC;AAE7E,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,+BAExD;yBAFe,cAAc;;;AAM9B;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAoB,SAAQ,cAAc,CAAC,OAAO,WAAW,CAAC,KAAK,CAAC;IACnF;;;OAGG;IACH,IAAI,CAAC,EAAE,cAAc,CAAC,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;IAC7D;;;OAGG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC;IAC/D;;;OAGG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC,YAAY,CAAC,CAAC;IACzE;;;OAGG;IACH,WAAW,CAAC,EAAE,cAAc,CAAC,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC,aAAa,CAAC,CAAC;CAC5E;AAED,wBAAgB,cAAc,CAAC,EAC7B,IAAe,EACf,KAAgB,EAChB,UAAc,EACd,WAAe,EACf,SAAS,EACT,QAAQ,EACR,GAAG,IAAI,EACR,EAAE,mBAAmB,+BAoBrB;yBA5Be,cAAc;;;AAgC9B;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;AAEzE,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,iBAAiB,+BAQrE;yBARe,YAAY;;;AAY5B;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,WAAW,CAAC,CAAC;AAErF,wBAAgB,kBAAkB,CAAC,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,uBAAuB,+BAQjF;yBARe,kBAAkB;;;AAYlC;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;AAEzE,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,+BAEpD;yBAFe,YAAY"}
1
+ {"version":3,"file":"Popover.d.ts","sourceRoot":"","sources":["../../src/components/Popover.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAIhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,MAAM,YAAY,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;AAEnE,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,+BAE1C;yBAFe,OAAO;;;AAMvB;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC;AAE7E,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,+BAExD;yBAFe,cAAc;;;AAM9B;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAoB,SAAQ,cAAc,CAAC,OAAO,WAAW,CAAC,KAAK,CAAC;IACnF;;;OAGG;IACH,IAAI,CAAC,EAAE,cAAc,CAAC,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;IAC7D;;;OAGG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC;IAC/D;;;OAGG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC,YAAY,CAAC,CAAC;IACzE;;;OAGG;IACH,WAAW,CAAC,EAAE,cAAc,CAAC,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC,aAAa,CAAC,CAAC;CAC5E;AAED,wBAAgB,cAAc,CAAC,EAC7B,IAAe,EACf,KAAgB,EAChB,UAAc,EACd,WAAe,EACf,SAAS,EACT,QAAQ,EACR,GAAG,IAAI,EACR,EAAE,mBAAmB,+BAoBrB;yBA5Be,cAAc;;;AAgC9B;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;AAEzE,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,iBAAiB,+BAQrE;yBARe,YAAY;;;AAY5B;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,WAAW,CAAC,CAAC;AAErF,wBAAgB,kBAAkB,CAAC,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,uBAAuB,+BAQjF;yBARe,kBAAkB;;;AAYlC;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;AAEzE,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,+BAEpD;yBAFe,YAAY"}
@@ -1 +1 @@
1
- {"version":3,"file":"Popover.js","names":[],"sources":["../../src/components/Popover.tsx"],"sourcesContent":["'use client';\n\nimport 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"],"mappings":";;;;;;AAgDA,SAAgB,EAAQ,GAAqB;CAC3C,OAAO,kBAAC,EAAY,MAAb,EAAkB,GAAI,EAAQ,CAAA;AACvC;AAEA,EAAQ,cAAc;AAUtB,SAAgB,EAAe,GAA4B;CACzD,OAAO,kBAAC,EAAY,SAAb,EAAqB,GAAI,EAAQ,CAAA;AAC1C;AAEA,EAAe,cAAc;AAmC7B,SAAgB,EAAe,EAC7B,UAAO,UACP,WAAQ,UACR,gBAAa,GACb,iBAAc,GACd,cACA,aACA,GAAG,KACmB;CACtB,OACE,kBAAC,EAAY,QAAb,EAAA,UACE,kBAAC,EAAY,YAAb;EACE,WAAW,EAAO;EACZ;EACC;EACK;EACC;YAEb,kBAAC,EAAY,OAAb;GACE,oBAAiB;GACjB,WAAW,EAAG,EAAO,SAAS,CAAS;GACvC,GAAI;GAEH;EACgB,CAAA;CACG,CAAA,EACN,CAAA;AAExB;AAEA,EAAe,cAAc;AAU7B,SAAgB,EAAa,EAAE,cAAW,GAAG,KAA2B;CACtE,OACE,kBAAC,EAAY,OAAb;EACE,oBAAiB;EACjB,WAAW,EAAG,EAAO,OAAO,CAAS;EACrC,GAAI;CACL,CAAA;AAEL;AAEA,EAAa,cAAc;AAS3B,SAAgB,EAAmB,EAAE,cAAW,GAAG,KAAiC;CAClF,OACE,kBAAC,EAAY,aAAb;EACE,oBAAiB;EACjB,WAAW,EAAG,EAAO,aAAa,CAAS;EAC3C,GAAI;CACL,CAAA;AAEL;AAEA,EAAmB,cAAc;AAUjC,SAAgB,EAAa,GAA0B;CACrD,OAAO,kBAAC,EAAY,OAAb;EAAmB,oBAAiB;EAAgB,GAAI;CAAQ,CAAA;AACzE;AAEA,EAAa,cAAc"}
1
+ {"version":3,"file":"Popover.js","names":[],"sources":["../../src/components/Popover.tsx"],"sourcesContent":["'use client';\n\nimport 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 the Popover page of the naming decisions:\n * https://app.notion.com/p/4071751eee1081239536dc0517fce057\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"],"mappings":";;;;;;AAiDA,SAAgB,EAAQ,GAAqB;CAC3C,OAAO,kBAAC,EAAY,MAAb,EAAkB,GAAI,EAAQ,CAAA;AACvC;AAEA,EAAQ,cAAc;AAUtB,SAAgB,EAAe,GAA4B;CACzD,OAAO,kBAAC,EAAY,SAAb,EAAqB,GAAI,EAAQ,CAAA;AAC1C;AAEA,EAAe,cAAc;AAmC7B,SAAgB,EAAe,EAC7B,UAAO,UACP,WAAQ,UACR,gBAAa,GACb,iBAAc,GACd,cACA,aACA,GAAG,KACmB;CACtB,OACE,kBAAC,EAAY,QAAb,EAAA,UACE,kBAAC,EAAY,YAAb;EACE,WAAW,EAAO;EACZ;EACC;EACK;EACC;YAEb,kBAAC,EAAY,OAAb;GACE,oBAAiB;GACjB,WAAW,EAAG,EAAO,SAAS,CAAS;GACvC,GAAI;GAEH;EACgB,CAAA;CACG,CAAA,EACN,CAAA;AAExB;AAEA,EAAe,cAAc;AAU7B,SAAgB,EAAa,EAAE,cAAW,GAAG,KAA2B;CACtE,OACE,kBAAC,EAAY,OAAb;EACE,oBAAiB;EACjB,WAAW,EAAG,EAAO,OAAO,CAAS;EACrC,GAAI;CACL,CAAA;AAEL;AAEA,EAAa,cAAc;AAS3B,SAAgB,EAAmB,EAAE,cAAW,GAAG,KAAiC;CAClF,OACE,kBAAC,EAAY,aAAb;EACE,oBAAiB;EACjB,WAAW,EAAG,EAAO,aAAa,CAAS;EAC3C,GAAI;CACL,CAAA;AAEL;AAEA,EAAmB,cAAc;AAUjC,SAAgB,EAAa,GAA0B;CACrD,OAAO,kBAAC,EAAY,OAAb;EAAmB,oBAAiB;EAAgB,GAAI;CAAQ,CAAA;AACzE;AAEA,EAAa,cAAc"}
@@ -13,7 +13,7 @@ import { VariantProps } from 'class-variance-authority';
13
13
  * that is a Combobox (the APG combobox pattern). shadcn, Base UI and ARIA APG
14
14
  * all separate the two under exactly these names, which is why Pharos ships
15
15
  * two atoms rather than one component with a `searchable` axis. See
16
- * `NAMING-decisions.md` § Select.
16
+ * the Select + Combobox page of the naming decisions: https://app.notion.com/p/4071751eee108172bf64fdbe0fe8a94b
17
17
  *
18
18
  * Multi-select is the `multiple` axis on the root (not a separate atom),
19
19
  * matching Base UI / shadcn / React Aria: pass `multiple` and `value`
@@ -1 +1 @@
1
- {"version":3,"file":"Select.js","names":[],"sources":["../../src/components/Select.tsx"],"sourcesContent":["'use client';\n\nimport 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"],"mappings":"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"}
1
+ {"version":3,"file":"Select.js","names":[],"sources":["../../src/components/Select.tsx"],"sourcesContent":["'use client';\n\nimport 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 * the Select + Combobox page of the naming decisions: https://app.notion.com/p/4071751eee108172bf64fdbe0fe8a94b\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"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-sherpas/pharos-react",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "React component library for Code Sherpas. Consumes @code-sherpas/pharos-tokens. Vite library mode + CSS Modules — no Tailwind, no CSS framework required at the consumer.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -17,8 +17,7 @@
17
17
  },
18
18
  "type": "module",
19
19
  "files": [
20
- "dist",
21
- "RULES.md"
20
+ "dist"
22
21
  ],
23
22
  "exports": {
24
23
  ".": {
package/RULES.md DELETED
@@ -1,98 +0,0 @@
1
- # Rules for consumers of `@code-sherpas/pharos-react`
2
-
3
- This file ships with the package (`files` includes `RULES.md`). Every
4
- consumer — `alexandria-web-application` and any future
5
- app — must follow these rules.
6
-
7
- ## 1. Only Pharos components for UI
8
-
9
- Forbidden:
10
-
11
- - Hand-rolled `<button>` / `<input>` when `Button` / `Input` exists in
12
- `@code-sherpas/pharos-react`.
13
- - Importing primitives (`@radix-ui/*`, `@base-ui/*`, `antd`,
14
- `@heroicons/*`, `@headlessui/*`) directly. Pharos wraps what it needs.
15
- - Copy-pasting a Pharos component into the consumer and modifying it. If
16
- Pharos is missing a variant, open an issue in this repo.
17
-
18
- ## 2. Use the canonical names
19
-
20
- Pharos names follow shadcn/ui > Base UI > ARIA APG (tiebreaker: shorter
21
- name). If you are migrating Alexandria, translate the old name to the Pharos
22
- canonical one — do not alias.
23
-
24
- | Alexandria-ish | Pharos canonical |
25
- | -------------------------- | ------------------------ |
26
- | `PrimaryButton` / `CtaBtn` | `<Button variant="…" />` |
27
- | `Modal` / `Popup` | `Dialog` |
28
- | `Drawer` / `SidePanel` | `Sheet` |
29
- | `Toast` / `Notification` | `Toast` |
30
- | `Chip` / `Pill` / `Tag` | `Badge` |
31
-
32
- (Full list lives in `NAMING-decisions.md` once Phase 2 starts.)
33
-
34
- ## 3. Import the stylesheet once
35
-
36
- ```ts
37
- // in your app's root entry file
38
- import '@code-sherpas/pharos-react/styles.css';
39
- ```
40
-
41
- This loads the compiled CSS Modules output of every component (hashed class
42
- names that the React components reference at runtime). Without it,
43
- components render unstyled.
44
-
45
- **You do not need Tailwind, PostCSS, or any other CSS framework to consume
46
- pharos-react** (Decision D9). The published bundle is pure CSS — no
47
- `@layer` directives, no preflight, no global resets, no class-name
48
- collisions with your own utilities. It works the same in Next.js + Tailwind
49
- v3, Next.js + Tailwind v4, Vite + plain CSS, Astro, Remix, or any other
50
- React stack.
51
-
52
- ## 4. `pharos-tokens` is a peer dependency
53
-
54
- Consumers must install it explicitly. It is not transitively bundled to
55
- avoid duplicate CSS var declarations:
56
-
57
- ```bash
58
- pnpm add @code-sherpas/pharos-react @code-sherpas/pharos-tokens
59
- ```
60
-
61
- Version compatibility is declared via `peerDependencies` in
62
- `pharos-react/package.json`. Upgrade both together.
63
-
64
- ## 5. Respect the variants API
65
-
66
- Pharos components expose their full variant axis (size, tone, intent, etc.)
67
- via props. Forbidden:
68
-
69
- - Overriding internal class names to "fake" a variant that does not exist.
70
- - Passing `className` strings that conflict with the component's theme
71
- (e.g. `className="bg-red-500"` on a `<Button>` to make it red — use
72
- `intent="destructive"` or open an issue).
73
-
74
- `className` is allowed for layout tweaks (margins, widths), not visual
75
- identity.
76
-
77
- ## 6. Respect semver
78
-
79
- - Breaking change in this repo → major bump → consumers upgrade explicitly.
80
- - New component or variant → minor bump → consumers may adopt freely.
81
- - Bugfix, visual tweak, or internal refactor → patch → consumers get it for
82
- free.
83
-
84
- Every PR that affects the public API includes a changeset. If your PR does
85
- not, CI will fail.
86
-
87
- ## 7. Icons
88
-
89
- Pharos uses **Lucide** (decision D4). When a component needs an icon you do
90
- not pass, Pharos renders the Lucide default. If you need a custom icon,
91
- open a PR to add it under `src/icons/` — do not ship icons from the
92
- consumer that duplicate Lucide.
93
-
94
- ## 8. Extensions and forks
95
-
96
- Do not publish `@yourorg/pharos-react-extended`. If a product needs its own
97
- components, build them in the consumer under your own naming namespace and
98
- document it. If the need is general, propose it in this repo.