@photon-ai/pho-ui 2.7.1 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"select.js","names":[],"sources":["../../src/components/select/select.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n type ComponentProps,\n type ElementType,\n type ReactNode,\n} from \"react\";\nimport { Select as BaseSelect } from \"@base-ui/react/select\";\nimport { SPRING_PRESS_INLINE } from \"../../motion\";\nimport { motion } from \"motion/react\";\nimport { IconCheck } from \"@tabler/icons-react\";\nimport { cn } from \"../../utils/cn\";\nimport { usePressPulse } from \"../../utils/use-press-pulse\";\n\n/** Override Base UI's `string | (state) => string` className with plain string. */\ntype StyledProps<T extends ElementType> = Omit<\n ComponentProps<T>,\n \"className\"\n> & {\n className?: string;\n};\n\n/** Lets `ItemIndicator` know whether the Root is in `multiple` mode. */\nconst SelectMultipleContext = createContext(false);\n\nexport type SelectTriggerVariant = \"default\" | \"inline\";\n\n// Inline rides someone else's surface, so its pulse is shallower and snappier\n// than the default field's (0.98 / stiffness 700) — felt, not seen moving.\nconst INLINE_PRESS_SCALE = 0.99;\nconst INLINE_PRESS_SPRING = SPRING_PRESS_INLINE;\n\nfunction Root({\n multiple = false,\n ...props\n}: ComponentProps<typeof BaseSelect.Root>) {\n return (\n <SelectMultipleContext.Provider value={Boolean(multiple)}>\n <BaseSelect.Root multiple={multiple} {...props} />\n </SelectMultipleContext.Provider>\n );\n}\n\nconst Value = BaseSelect.Value;\nconst Portal = BaseSelect.Portal;\nconst List = BaseSelect.List;\nconst Group = BaseSelect.Group;\n\n// Shared by both Trigger variants — layout, easing, disabled, icon tone.\nconst TRIGGER_BASE = cn(\n \"flex cursor-pointer items-center justify-between gap-2 text-base select-none\",\n // Colors/stroke ease briefly; transform stays out (Motion owns it).\n \"transition-[color,background-color,box-shadow,outline-color] duration-150 ease-out\",\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50\",\n \"[&_svg]:text-pho-secondary [&_svg]:size-4\",\n);\n\nfunction Trigger({\n className,\n disabled,\n variant = \"default\",\n onPointerDown,\n ...props\n}: StyledProps<typeof BaseSelect.Trigger> & {\n /**\n * `inline` drops the field chrome (ring, background, padding, min-width)\n * to ride a host surface — an Input Group addon, a toolbar — which then\n * carries the focus/open treatment. The press pulse moves to the arrow.\n * @default \"default\"\n */\n variant?: SelectTriggerVariant;\n}) {\n // One-shot press pulse (fires on pointer-down, always completes) — the\n // Effect Button gesture family adapted for a field-sized control. Both\n // variants pulse the whole trigger; inline is shallower and quicker.\n const inline = variant === \"inline\";\n const { motionProps } = usePressPulse({\n scale: inline ? INLINE_PRESS_SCALE : 0.98,\n transition: inline ? INLINE_PRESS_SPRING : undefined,\n disabled: Boolean(disabled),\n });\n\n return (\n <BaseSelect.Trigger\n disabled={disabled}\n onPointerDown={onPointerDown}\n // Motion owns transform; a caller-supplied `render` in props still wins.\n render={<motion.button {...motionProps} />}\n className={cn(\n TRIGGER_BASE,\n inline\n ? // Chrome-free: no surface of its own, text inherits the host\n // tone, and the host shows focus (e.g. Input Group's shared ring).\n \"rounded-base min-w-0 bg-transparent focus-visible:outline-none\"\n : cn(\n // Flat field surface — shadows are reserved for floating\n // layers; the 15% ring-pho-primary hairline is the input edge.\n \"bg-pho-primary ring-pho-primary text-pho-primary outline-pho-brand rounded-base min-w-44 px-3 py-2 ring-1 ring-inset\",\n \"data-[popup-open]:ring-pho-brand focus-visible:outline-2 focus-visible:-outline-offset-1\",\n ),\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Icon({ className, ...props }: StyledProps<typeof BaseSelect.Icon>) {\n return (\n <BaseSelect.Icon\n className={cn(\"flex shrink-0 items-center\", className)}\n {...props}\n />\n );\n}\n\nfunction Positioner({\n className,\n sideOffset = 6,\n align = \"start\",\n alignItemWithTrigger = false,\n ...props\n}: StyledProps<typeof BaseSelect.Positioner>) {\n return (\n <BaseSelect.Positioner\n sideOffset={sideOffset}\n align={align}\n // Base UI defaults to the macOS-style overlap (popup covers the trigger\n // so the selected item sits on the value text) — disorienting in a page\n // flow, so Pho drops the list below the field like a menu instead.\n alignItemWithTrigger={alignItemWithTrigger}\n className={cn(\"z-50 outline-none\", className)}\n {...props}\n />\n );\n}\n\nfunction Popup({ className, ...props }: StyledProps<typeof BaseSelect.Popup>) {\n return (\n <BaseSelect.Popup\n className={cn(\n // Floating layer: keeps its elevation shadow, edged with the site\n // hairline (ring-pho-secondary, #eee/#202020).\n \"bg-pho-primary text-pho-primary shadow-pho-lg ring-pho-secondary rounded-base max-h-[var(--available-height)] min-w-[var(--anchor-width)] origin-[var(--transform-origin)] overflow-y-auto p-1 ring-1 outline-none ring-inset\",\n // Effect-Button animation family, faster: the open springs from the\n // trigger edge with a slight overshoot; close is a quick ease-out.\n \"transition-[transform,opacity] duration-[140ms] ease-[cubic-bezier(0.34,1.3,0.64,1)]\",\n \"data-[starting-style]:scale-95 data-[starting-style]:opacity-0\",\n \"data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:duration-100 data-[ending-style]:ease-out\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Item({ className, ...props }: StyledProps<typeof BaseSelect.Item>) {\n return (\n <BaseSelect.Item\n className={cn(\n // Fixed 1rem leading column: the selection mark lives there, so every\n // label starts at the same x whether or not it is selected.\n // `group/pho-item` feeds the multiple-mode checkbox indicator.\n \"group/pho-item text-pho-secondary grid cursor-pointer grid-cols-[1rem_1fr] items-center gap-2 rounded-[calc(var(--radius-base)-4px)] py-1.5 pr-2.5 pl-2 text-base outline-none select-none\",\n // Secondary at rest; highlight lifts to primary + ghost wash (Menu idiom).\n \"data-[highlighted]:bg-pho-ghost-hover data-[highlighted]:text-pho-primary data-[disabled]:opacity-50\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction ItemText({\n className,\n ...props\n}: StyledProps<typeof BaseSelect.ItemText>) {\n return (\n <BaseSelect.ItemText\n // Pin to column 2: when the single-select indicator unmounts, grid\n // auto-placement would otherwise pull the label into the mark column.\n className={cn(\"col-start-2 min-w-0\", className)}\n {...props}\n />\n );\n}\n\n/**\n * Leading selection mark, mode-aware. Single: a brand check that appears in\n * the reserved column. Multiple: an always-visible checkbox box that fills\n * with the same quiet fill/stroke ease as the standalone Checkbox.\n */\nfunction ItemIndicator({\n className,\n children,\n ...props\n}: StyledProps<typeof BaseSelect.ItemIndicator> & { children?: ReactNode }) {\n const multiple = useContext(SelectMultipleContext);\n const mark = children ?? <IconCheck />;\n\n if (multiple) {\n return (\n <span\n aria-hidden\n className={cn(\n \"col-start-1 flex size-4 shrink-0 items-center justify-center rounded-sm ring-1 ring-inset\",\n \"bg-pho-primary ring-pho-primary text-pho-on-brand\",\n // Fill + stroke ease together on toggle — Checkbox's transition.\n \"transition-[background-color,box-shadow,color] duration-150 ease-out\",\n \"group-data-[selected]/pho-item:bg-pho-brand-solid group-data-[selected]/pho-item:ring-transparent\",\n )}\n >\n <BaseSelect.ItemIndicator\n className={cn(\n \"flex items-center justify-center [&>svg]:size-3\",\n // Glyph pops with the fill; exits on the same path.\n \"transition-[transform,opacity] duration-150 ease-out\",\n \"data-[starting-style]:scale-50 data-[starting-style]:opacity-0\",\n \"data-[ending-style]:scale-50 data-[ending-style]:opacity-0\",\n className,\n )}\n {...props}\n >\n {mark}\n </BaseSelect.ItemIndicator>\n </span>\n );\n }\n\n return (\n <BaseSelect.ItemIndicator\n className={cn(\n \"text-pho-brand col-start-1 flex size-4 items-center justify-center [&>svg]:size-4\",\n className,\n )}\n {...props}\n >\n {mark}\n </BaseSelect.ItemIndicator>\n );\n}\n\nfunction GroupLabel({\n className,\n ...props\n}: StyledProps<typeof BaseSelect.GroupLabel>) {\n return (\n <BaseSelect.GroupLabel\n className={cn(\n \"text-pho-secondary px-2.5 py-1.5 text-xs font-medium\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * Select built on Base UI `Select` — pick one option (or several with\n * `multiple`) from a list that opens below the trigger on click; the pattern\n * other design systems call a picker. Pass `items` to `Select.Root`, render an\n * `Item` per option, and show the selection with `Select.Value`. For a short,\n * always-visible set use `RadioGroup`; to filter a long list by typing use\n * `Combobox`.\n */\nexport const Select = {\n Root,\n Trigger,\n Value,\n Icon,\n Portal,\n Positioner,\n Popup,\n List,\n Item,\n ItemText,\n ItemIndicator,\n Group,\n GroupLabel,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"select.js","names":[],"sources":["../../src/components/select/select.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n type ComponentProps,\n type ElementType,\n type ReactNode,\n} from \"react\";\nimport { Select as BaseSelect } from \"@base-ui/react/select\";\nimport { SPRING_PRESS_INLINE } from \"../../motion\";\nimport { motion } from \"motion/react\";\nimport { IconCheck } from \"@tabler/icons-react\";\nimport { cn } from \"../../utils/cn\";\nimport { usePressPulse } from \"../../utils/use-press-pulse\";\n\n/** Override Base UI's `string | (state) => string` className with plain string. */\ntype StyledProps<T extends ElementType> = Omit<\n ComponentProps<T>,\n \"className\"\n> & {\n className?: string;\n};\n\n/** Lets `ItemIndicator` know whether the Root is in `multiple` mode. */\nconst SelectMultipleContext = createContext(false);\n\nexport type SelectTriggerVariant = \"default\" | \"inline\";\n\n// Inline rides someone else's surface, so its pulse is shallower and snappier\n// than the default field's (0.98 / stiffness 700) — felt, not seen moving.\nconst INLINE_PRESS_SCALE = 0.99;\nconst INLINE_PRESS_SPRING = SPRING_PRESS_INLINE;\n\nfunction Root({\n multiple = false,\n ...props\n}: ComponentProps<typeof BaseSelect.Root>) {\n return (\n <SelectMultipleContext.Provider value={Boolean(multiple)}>\n <BaseSelect.Root multiple={multiple} {...props} />\n </SelectMultipleContext.Provider>\n );\n}\n\nconst Value = BaseSelect.Value;\nconst Portal = BaseSelect.Portal;\nconst List = BaseSelect.List;\nconst Group = BaseSelect.Group;\n\n// Shared by both Trigger variants — layout, easing, disabled, icon tone.\nconst TRIGGER_BASE = cn(\n \"flex cursor-pointer items-center justify-between gap-2 text-base select-none\",\n // Colors/stroke ease briefly; transform stays out (Motion owns it).\n \"transition-[color,background-color,box-shadow,outline-color] duration-150 ease-out\",\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50\",\n \"[&_svg]:text-pho-secondary [&_svg]:size-4\",\n);\n\nfunction Trigger({\n className,\n disabled,\n variant = \"default\",\n onPointerDown,\n ...props\n}: StyledProps<typeof BaseSelect.Trigger> & {\n /**\n * `inline` drops the field chrome (ring, background, padding, min-width)\n * to ride a host surface — an Input Group addon, a toolbar — which then\n * carries the focus/open treatment. The press pulse moves to the arrow.\n * @default \"default\"\n */\n variant?: SelectTriggerVariant;\n}) {\n // One-shot press pulse (fires on pointer-down, always completes) — the\n // Effect Button gesture family adapted for a field-sized control. Both\n // variants pulse the whole trigger; inline is shallower and quicker.\n const inline = variant === \"inline\";\n const { motionProps } = usePressPulse({\n scale: inline ? INLINE_PRESS_SCALE : 0.98,\n transition: inline ? INLINE_PRESS_SPRING : undefined,\n disabled: Boolean(disabled),\n });\n\n return (\n <BaseSelect.Trigger\n disabled={disabled}\n onPointerDown={onPointerDown}\n // Motion owns transform; a caller-supplied `render` in props still wins.\n render={<motion.button {...motionProps} />}\n className={cn(\n TRIGGER_BASE,\n inline\n ? // Chrome-free: no surface of its own, text inherits the host\n // tone, and the host shows focus (e.g. Input Group's shared ring).\n \"rounded-base min-w-0 bg-transparent focus-visible:outline-none\"\n : cn(\n // Flat field surface — shadows are reserved for floating\n // layers; the 15% ring-pho-primary hairline is the input edge.\n \"bg-pho-primary ring-pho-primary text-pho-primary rounded-base min-w-44 px-3 py-2 ring-1 ring-inset focus-visible:outline-none\",\n // Open or focused, the edge is Input's: the brand ring at 2px,\n // inset, in place of the hairline.\n \"data-[popup-open]:ring-pho-brand focus-visible:ring-pho-brand focus-visible:ring-2 data-[popup-open]:ring-2\",\n ),\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Icon({ className, ...props }: StyledProps<typeof BaseSelect.Icon>) {\n return (\n <BaseSelect.Icon\n className={cn(\"flex shrink-0 items-center\", className)}\n {...props}\n />\n );\n}\n\nfunction Positioner({\n className,\n sideOffset = 6,\n align = \"start\",\n alignItemWithTrigger = false,\n ...props\n}: StyledProps<typeof BaseSelect.Positioner>) {\n return (\n <BaseSelect.Positioner\n sideOffset={sideOffset}\n align={align}\n // Base UI defaults to the macOS-style overlap (popup covers the trigger\n // so the selected item sits on the value text) — disorienting in a page\n // flow, so Pho drops the list below the field like a menu instead.\n alignItemWithTrigger={alignItemWithTrigger}\n className={cn(\"z-50 outline-none\", className)}\n {...props}\n />\n );\n}\n\nfunction Popup({ className, ...props }: StyledProps<typeof BaseSelect.Popup>) {\n return (\n <BaseSelect.Popup\n className={cn(\n // Floating layer: keeps its elevation shadow, edged with the site\n // hairline (ring-pho-secondary, #eee/#202020).\n \"bg-pho-primary text-pho-primary shadow-pho-lg ring-pho-secondary rounded-base max-h-[var(--available-height)] min-w-[var(--anchor-width)] origin-[var(--transform-origin)] overflow-y-auto p-1 ring-1 outline-none ring-inset\",\n // Effect-Button animation family, faster: the open springs from the\n // trigger edge with a slight overshoot; close is a quick ease-out.\n \"transition-[transform,opacity] duration-[140ms] ease-[cubic-bezier(0.34,1.3,0.64,1)]\",\n \"data-[starting-style]:scale-95 data-[starting-style]:opacity-0\",\n \"data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:duration-100 data-[ending-style]:ease-out\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Item({ className, ...props }: StyledProps<typeof BaseSelect.Item>) {\n return (\n <BaseSelect.Item\n className={cn(\n // Fixed 1rem leading column: the selection mark lives there, so every\n // label starts at the same x whether or not it is selected.\n // `group/pho-item` feeds the multiple-mode checkbox indicator.\n \"group/pho-item text-pho-secondary grid cursor-pointer grid-cols-[1rem_1fr] items-center gap-2 rounded-[calc(var(--radius-base)-4px)] py-1.5 pr-2.5 pl-2 text-base outline-none select-none\",\n // Secondary at rest; highlight lifts to primary + ghost wash (Menu idiom).\n \"data-[highlighted]:bg-pho-ghost-hover data-[highlighted]:text-pho-primary data-[disabled]:opacity-50\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction ItemText({\n className,\n ...props\n}: StyledProps<typeof BaseSelect.ItemText>) {\n return (\n <BaseSelect.ItemText\n // Pin to column 2: when the single-select indicator unmounts, grid\n // auto-placement would otherwise pull the label into the mark column.\n className={cn(\"col-start-2 min-w-0\", className)}\n {...props}\n />\n );\n}\n\n/**\n * Leading selection mark, mode-aware. Single: a brand check that appears in\n * the reserved column. Multiple: an always-visible checkbox box that fills\n * with the same quiet fill/stroke ease as the standalone Checkbox.\n */\nfunction ItemIndicator({\n className,\n children,\n ...props\n}: StyledProps<typeof BaseSelect.ItemIndicator> & { children?: ReactNode }) {\n const multiple = useContext(SelectMultipleContext);\n const mark = children ?? <IconCheck />;\n\n if (multiple) {\n return (\n <span\n aria-hidden\n className={cn(\n \"col-start-1 flex size-4 shrink-0 items-center justify-center rounded-sm ring-1 ring-inset\",\n \"bg-pho-primary ring-pho-primary text-pho-on-brand\",\n // Fill + stroke ease together on toggle — Checkbox's transition.\n \"transition-[background-color,box-shadow,color] duration-150 ease-out\",\n \"group-data-[selected]/pho-item:bg-pho-brand-solid group-data-[selected]/pho-item:ring-transparent\",\n )}\n >\n <BaseSelect.ItemIndicator\n className={cn(\n \"flex items-center justify-center [&>svg]:size-3\",\n // Glyph pops with the fill; exits on the same path.\n \"transition-[transform,opacity] duration-150 ease-out\",\n \"data-[starting-style]:scale-50 data-[starting-style]:opacity-0\",\n \"data-[ending-style]:scale-50 data-[ending-style]:opacity-0\",\n className,\n )}\n {...props}\n >\n {mark}\n </BaseSelect.ItemIndicator>\n </span>\n );\n }\n\n return (\n <BaseSelect.ItemIndicator\n className={cn(\n \"text-pho-brand col-start-1 flex size-4 items-center justify-center [&>svg]:size-4\",\n className,\n )}\n {...props}\n >\n {mark}\n </BaseSelect.ItemIndicator>\n );\n}\n\nfunction GroupLabel({\n className,\n ...props\n}: StyledProps<typeof BaseSelect.GroupLabel>) {\n return (\n <BaseSelect.GroupLabel\n className={cn(\n \"text-pho-secondary px-2.5 py-1.5 text-xs font-medium\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * Select built on Base UI `Select` — pick one option (or several with\n * `multiple`) from a list that opens below the trigger on click; the pattern\n * other design systems call a picker. Pass `items` to `Select.Root`, render an\n * `Item` per option, and show the selection with `Select.Value`. For a short,\n * always-visible set use `RadioGroup`; to filter a long list by typing use\n * `Combobox`.\n */\nexport const Select = {\n Root,\n Trigger,\n Value,\n Icon,\n Portal,\n Positioner,\n Popup,\n List,\n Item,\n ItemText,\n ItemIndicator,\n Group,\n GroupLabel,\n};\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -8,8 +8,8 @@ import { n as c, r as G, t as U } from "./chunks/use-press-pulse-IbNstUT0.js";
8
8
  import "./utils.js";
9
9
  import { a as F, c as W, d as b, i as g, l as h, n as k, o as X, r as v, s as Z, t as w, u as x } from "./chunks/button-CdqS_t5a.js";
10
10
  import { a as K, c as Y, i as z, l as Q, n as j, o as q, r as y, s as J, t as $ } from "./chunks/error-D0yoFxCv.js";
11
- import { a as as, c as Es, d as ts, f as _s, i as rs, l as Ss, m as os, n as As, o as Rs, p as Is, r as is, s as Ts, t as ms, u as es } from "./chunks/basic-page-BF4jnCa1.js";
12
- import { o as Ps } from "./chunks/data-table--f6am1O3.js";
11
+ import { a as as, c as Es, d as ts, f as _s, i as rs, l as Ss, m as os, n as As, o as Rs, p as Is, r as is, s as Ts, t as ms, u as es } from "./chunks/basic-page-B3iBvhbz.js";
12
+ import { o as Ps } from "./chunks/data-table-Cmz0p-jt.js";
13
13
  import { a as Ns, c as Bs, i as ps, n as Os, o as fs, r as us, s as Ls, t as Ds } from "./chunks/sidebar-DVzZsCts.js";
14
14
  import { t as Vs } from "./chunks/input-DjvZoF8v.js";
15
15
  import { t as Gs } from "./chunks/close-button-GvqWbLl0.js";
@@ -1 +1 @@
1
- {"version":3,"file":"motion.js","names":[],"sources":["../src/motion/index.ts"],"sourcesContent":["/**\n * The motion vocabulary — every animation in Pho speaks it. Five springs,\n * three timings, two CSS eases, and a handful of distances cover the\n * system; components import from here and add nothing of their own.\n *\n * The grammar:\n * - **Arrive** on `SPRING_ENTRANCE` with `FADE_ENTRANCE` — content\n * surfaces, it doesn't slide in.\n * - **Swap** on `FADE_SWAP`, displacement on `TRAVEL_LINEAR` — crossfades\n * ease out, travel is linear.\n * - **Press** on the press springs — deeper mass for bigger surfaces.\n * - **Follow** on `SPRING_SETTLE` — heights and reflows chase content.\n */\n\n/**\n * Pages arrive on this — the `Stack` entrance, `Reveal`, `Title` / `Header`\n * enter, the Title ↔ Back morph, `Alert` / `Footer` height reveals.\n * Measured off a 60fps iOS push: ~330ms, a long gentle glide, everything\n * co-landing; essentially critically damped (overshoot would flash\n * clipped content).\n */\nexport const SPRING_ENTRANCE = {\n type: \"spring\",\n stiffness: 400,\n damping: 40,\n} as const;\n\n/**\n * Layout follows content on this — `AnimatedHeight`, a command palette's\n * rows gliding into new places. Stiffer than the entrance: reflow should\n * settle before it reads as motion.\n */\nexport const SPRING_SETTLE = {\n type: \"spring\",\n stiffness: 640,\n damping: 50,\n} as const;\n\n/**\n * The press pulse for full-size surfaces — `Button`, a sidebar row.\n * Framer source: `spring-physics 200 30 0.1`.\n */\nexport const SPRING_PRESS = {\n type: \"spring\",\n stiffness: 200,\n damping: 30,\n mass: 0.1,\n} as const;\n\n/** The press pulse for small controls — toggles, checks; quick and light. */\nexport const SPRING_PRESS_QUICK = {\n type: \"spring\",\n stiffness: 700,\n damping: 32,\n mass: 0.06,\n} as const;\n\n/** The press pulse for inline triggers — a `Select` sitting in text. */\nexport const SPRING_PRESS_INLINE = {\n type: \"spring\",\n stiffness: 1100,\n damping: 40,\n mass: 0.04,\n} as const;\n\n/** The fade that rides `SPRING_ENTRANCE` — spans most of the glide. */\nexport const FADE_ENTRANCE = { duration: 0.25, ease: \"easeOut\" } as const;\n\n/**\n * The swap — crossfading faces, a condensed bar arriving, a saved word,\n * an aside changing pages. Fades ease out.\n */\nexport const FADE_SWAP = { duration: 0.2, ease: \"easeOut\" } as const;\n\n/**\n * Displacement — a bar's rise, a drawer's scope swap, a search box's\n * width. Travel is linear: the eye reads speed changes as personality,\n * and furniture has none.\n */\nexport const TRAVEL_LINEAR = { duration: 0.2, ease: \"linear\" } as const;\n\n/** CSS pop-in for floating layers (menus, select popups) — slight overshoot. */\nexport const EASE_POP = \"cubic-bezier(0.34, 1.3, 0.64, 1)\";\n\n/** CSS stack glide — toasts trading places (Kumo's curve). */\nexport const EASE_STACK = \"cubic-bezier(0.22, 1, 0.36, 1)\";\n\n/** Rise distance for entrances — content surfaces from just below. */\nexport const RISE_PX = 12;\n\n/** Rise distance for the condensed bar — it comes from nearer still. */\nexport const BAR_RISE_PX = -8;\n\n/** Press depth for small controls. */\nexport const PRESS_SCALE = 0.96;\n\n/** Press depth for rows and full-size surfaces — shallower. */\nexport const PRESS_SCALE_ROW = 0.97;\n\n/** The blur budget — one breath of blur, everywhere it is used. */\nexport const BLUR_PX = 1;\n\n/**\n * The crossfade recipe — fade with a breath of blur, both ways. Spread\n * onto a motion element inside `AnimatePresence`:\n *\n * <motion.div {...fadeThroughBlur(reduceMotion)} />\n *\n * Under reduced motion the swap is instant and never blurs.\n */\nexport function fadeThroughBlur(reduceMotion?: boolean | null) {\n const away = reduceMotion ? \"blur(0px)\" : `blur(${BLUR_PX}px)`;\n return {\n initial: { opacity: 0, filter: away },\n animate: { opacity: 1, filter: \"blur(0px)\" },\n exit: { opacity: 0, filter: away },\n transition: reduceMotion ? ({ duration: 0 } as const) : FADE_SWAP,\n } as const;\n}\n\n/**\n * The entrance recipe — rise `RISE_PX` on `SPRING_ENTRANCE` while\n * `FADE_ENTRANCE` brings the ink. What `Stack` and `Reveal` do; spread it\n * onto a motion element for content that should arrive the same way.\n */\nexport function riseIn(reduceMotion?: boolean | null) {\n return {\n initial: reduceMotion ? false : ({ opacity: 0, y: RISE_PX } as const),\n animate: { opacity: 1, y: 0 } as const,\n transition: { y: SPRING_ENTRANCE, opacity: FADE_ENTRANCE } as const,\n } as const;\n}\n"],"mappings":";AAqBA,IAAa,IAAkB;AAAA,EAC7B,MAAM;AAAA,EACN,WAAW;AAAA,EACX,SAAS;AACX,GAOa,IAAgB;AAAA,EAC3B,MAAM;AAAA,EACN,WAAW;AAAA,EACX,SAAS;AACX,GAMa,IAAe;AAAA,EAC1B,MAAM;AAAA,EACN,WAAW;AAAA,EACX,SAAS;AAAA,EACT,MAAM;AACR,GAGa,IAAqB;AAAA,EAChC,MAAM;AAAA,EACN,WAAW;AAAA,EACX,SAAS;AAAA,EACT,MAAM;AACR,GAGa,IAAsB;AAAA,EACjC,MAAM;AAAA,EACN,WAAW;AAAA,EACX,SAAS;AAAA,EACT,MAAM;AACR,GAGa,IAAgB;AAAA,EAAE,UAAU;AAAA,EAAM,MAAM;AAAU,GAMlD,IAAY;AAAA,EAAE,UAAU;AAAA,EAAK,MAAM;AAAU,GAO7C,IAAgB;AAAA,EAAE,UAAU;AAAA,EAAK,MAAM;AAAS,GAGhD,IAAW,oCAGX,IAAa,kCAGb,IAAU,IAGV,IAAc,IAGd,IAAc,MAGd,IAAkB,MAGlB,IAAU;AAUvB,SAAgB,EAAgB,GAA+B;AAC7D,QAAM,IAAO,IAAe,cAAc;AAC1C,SAAO;AAAA,IACL,SAAS;AAAA,MAAE,SAAS;AAAA,MAAG,QAAQ;AAAA,IAAK;AAAA,IACpC,SAAS;AAAA,MAAE,SAAS;AAAA,MAAG,QAAQ;AAAA,IAAY;AAAA,IAC3C,MAAM;AAAA,MAAE,SAAS;AAAA,MAAG,QAAQ;AAAA,IAAK;AAAA,IACjC,YAAY,IAAgB,EAAE,UAAU,EAAE,IAAc;AAAA,EAC1D;AACF;AAOA,SAAgB,EAAO,GAA+B;AACpD,SAAO;AAAA,IACL,SAAS,IAAe,KAAS;AAAA,MAAE,SAAS;AAAA,MAAG,GAAA;AAAA,IAAW;AAAA,IAC1D,SAAS;AAAA,MAAE,SAAS;AAAA,MAAG,GAAG;AAAA,IAAE;AAAA,IAC5B,YAAY;AAAA,MAAE,GAAG;AAAA,MAAiB,SAAS;AAAA,IAAc;AAAA,EAC3D;AACF"}
1
+ {"version":3,"file":"motion.js","names":[],"sources":["../src/motion/index.ts"],"sourcesContent":["/**\n * The motion vocabulary — every animation in Pho speaks it. Five springs,\n * three timings, two CSS eases, and a handful of distances cover the\n * system; components import from here and add nothing of their own.\n *\n * The grammar:\n * - **Arrive** on `SPRING_ENTRANCE` with `FADE_ENTRANCE` — content\n * surfaces, it doesn't slide in.\n * - **Swap** on `FADE_SWAP`, displacement on `TRAVEL_LINEAR` — crossfades\n * ease out, travel is linear.\n * - **Press** on the press springs — deeper mass for bigger surfaces.\n * - **Follow** on `SPRING_SETTLE` — heights and reflows chase content.\n */\n\n/**\n * Pages arrive on this — the `Stack` entrance, `Reveal`, `Title` / `Header`\n * enter, the Title ↔ Back morph, `Alert` / `Footer` height reveals.\n * Measured off a 60fps iOS push: ~330ms, a long gentle glide, everything\n * co-landing; essentially critically damped (overshoot would flash\n * clipped content).\n */\nexport const SPRING_ENTRANCE = {\n type: \"spring\",\n stiffness: 400,\n damping: 40,\n} as const;\n\n/**\n * Layout follows content on this — `AnimatedHeight`, a command palette's\n * rows gliding into new places. Stiffer than the entrance: reflow should\n * settle before it reads as motion.\n */\nexport const SPRING_SETTLE = {\n type: \"spring\",\n stiffness: 640,\n damping: 50,\n} as const;\n\n/**\n * The press pulse for full-size surfaces — `Button`, a sidebar row.\n * Framer source: `spring-physics 200 30 0.1`.\n */\nexport const SPRING_PRESS = {\n type: \"spring\",\n stiffness: 200,\n damping: 30,\n mass: 0.1,\n} as const;\n\n/** The press pulse for small controls — toggles, checks; quick and light. */\nexport const SPRING_PRESS_QUICK = {\n type: \"spring\",\n stiffness: 700,\n damping: 32,\n mass: 0.06,\n} as const;\n\n/** The press pulse for inline triggers — a `Select` sitting in text. */\nexport const SPRING_PRESS_INLINE = {\n type: \"spring\",\n stiffness: 1100,\n damping: 40,\n mass: 0.04,\n} as const;\n\n/** The fade that rides `SPRING_ENTRANCE` — spans most of the glide. */\nexport const FADE_ENTRANCE = { duration: 0.25, ease: \"easeOut\" } as const;\n\n/**\n * The swap — crossfading faces, a condensed bar arriving, a saved word,\n * an aside changing pages. Fades ease out.\n */\nexport const FADE_SWAP = { duration: 0.2, ease: \"easeOut\" } as const;\n\n/**\n * Displacement — a bar's rise, a drawer's scope swap. Travel is linear:\n * the eye reads speed changes as personality, and furniture has none.\n * (A search box's width is a reflow, not travel: it rides the page\n * spring, as a menu opens.)\n */\nexport const TRAVEL_LINEAR = { duration: 0.2, ease: \"linear\" } as const;\n\n/** CSS pop-in for floating layers (menus, select popups) — slight overshoot. */\nexport const EASE_POP = \"cubic-bezier(0.34, 1.3, 0.64, 1)\";\n\n/** CSS stack glide — toasts trading places (Kumo's curve). */\nexport const EASE_STACK = \"cubic-bezier(0.22, 1, 0.36, 1)\";\n\n/** Rise distance for entrances — content surfaces from just below. */\nexport const RISE_PX = 12;\n\n/** Rise distance for the condensed bar — it comes from nearer still. */\nexport const BAR_RISE_PX = -8;\n\n/** Press depth for small controls. */\nexport const PRESS_SCALE = 0.96;\n\n/** Press depth for rows and full-size surfaces — shallower. */\nexport const PRESS_SCALE_ROW = 0.97;\n\n/** The blur budget — one breath of blur, everywhere it is used. */\nexport const BLUR_PX = 1;\n\n/**\n * The crossfade recipe — fade with a breath of blur, both ways. Spread\n * onto a motion element inside `AnimatePresence`:\n *\n * <motion.div {...fadeThroughBlur(reduceMotion)} />\n *\n * Under reduced motion the swap is instant and never blurs.\n */\nexport function fadeThroughBlur(reduceMotion?: boolean | null) {\n const away = reduceMotion ? \"blur(0px)\" : `blur(${BLUR_PX}px)`;\n return {\n initial: { opacity: 0, filter: away },\n animate: { opacity: 1, filter: \"blur(0px)\" },\n exit: { opacity: 0, filter: away },\n transition: reduceMotion ? ({ duration: 0 } as const) : FADE_SWAP,\n } as const;\n}\n\n/**\n * The entrance recipe — rise `RISE_PX` on `SPRING_ENTRANCE` while\n * `FADE_ENTRANCE` brings the ink. What `Stack` and `Reveal` do; spread it\n * onto a motion element for content that should arrive the same way.\n */\nexport function riseIn(reduceMotion?: boolean | null) {\n return {\n initial: reduceMotion ? false : ({ opacity: 0, y: RISE_PX } as const),\n animate: { opacity: 1, y: 0 } as const,\n transition: { y: SPRING_ENTRANCE, opacity: FADE_ENTRANCE } as const,\n } as const;\n}\n"],"mappings":";AAqBA,IAAa,IAAkB;AAAA,EAC7B,MAAM;AAAA,EACN,WAAW;AAAA,EACX,SAAS;AACX,GAOa,IAAgB;AAAA,EAC3B,MAAM;AAAA,EACN,WAAW;AAAA,EACX,SAAS;AACX,GAMa,IAAe;AAAA,EAC1B,MAAM;AAAA,EACN,WAAW;AAAA,EACX,SAAS;AAAA,EACT,MAAM;AACR,GAGa,IAAqB;AAAA,EAChC,MAAM;AAAA,EACN,WAAW;AAAA,EACX,SAAS;AAAA,EACT,MAAM;AACR,GAGa,IAAsB;AAAA,EACjC,MAAM;AAAA,EACN,WAAW;AAAA,EACX,SAAS;AAAA,EACT,MAAM;AACR,GAGa,IAAgB;AAAA,EAAE,UAAU;AAAA,EAAM,MAAM;AAAU,GAMlD,IAAY;AAAA,EAAE,UAAU;AAAA,EAAK,MAAM;AAAU,GAQ7C,IAAgB;AAAA,EAAE,UAAU;AAAA,EAAK,MAAM;AAAS,GAGhD,IAAW,oCAGX,IAAa,kCAGb,IAAU,IAGV,IAAc,IAGd,IAAc,MAGd,IAAkB,MAGlB,IAAU;AAUvB,SAAgB,EAAgB,GAA+B;AAC7D,QAAM,IAAO,IAAe,cAAc;AAC1C,SAAO;AAAA,IACL,SAAS;AAAA,MAAE,SAAS;AAAA,MAAG,QAAQ;AAAA,IAAK;AAAA,IACpC,SAAS;AAAA,MAAE,SAAS;AAAA,MAAG,QAAQ;AAAA,IAAY;AAAA,IAC3C,MAAM;AAAA,MAAE,SAAS;AAAA,MAAG,QAAQ;AAAA,IAAK;AAAA,IACjC,YAAY,IAAgB,EAAE,UAAU,EAAE,IAAc;AAAA,EAC1D;AACF;AAOA,SAAgB,EAAO,GAA+B;AACpD,SAAO;AAAA,IACL,SAAS,IAAe,KAAS;AAAA,MAAE,SAAS;AAAA,MAAG,GAAA;AAAA,IAAW;AAAA,IAC1D,SAAS;AAAA,MAAE,SAAS;AAAA,MAAG,GAAG;AAAA,IAAE;AAAA,IAC5B,YAAY;AAAA,MAAE,GAAG;AAAA,MAAiB,SAAS;AAAA,IAAc;AAAA,EAC3D;AACF"}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import { a as s, c as A, d as E, f as a, i as C, l as S, m as o, n as I, o as e, p as B, r as P, s as N, t as r, u as t } from "../chunks/basic-page-BF4jnCa1.js";
3
- import { o as R } from "../chunks/data-table--f6am1O3.js";
2
+ import { a as s, c as A, d as E, f as a, i as C, l as S, m as o, n as I, o as e, p as B, r as P, s as N, t as r, u as t } from "../chunks/basic-page-B3iBvhbz.js";
3
+ import { o as R } from "../chunks/data-table-Cmz0p-jt.js";
4
4
  export {
5
5
  r as AUTO_SAVE_DELAY_MS,
6
6
  I as BASIC_PAGE_CALLOUT,
@@ -4,19 +4,56 @@ import { Accordion as BaseAccordion } from '@base-ui/react/accordion';
4
4
  type StyledProps<T extends ElementType> = Omit<ComponentProps<T>, "className"> & {
5
5
  className?: string;
6
6
  };
7
- declare function Root({ className, ...props }: StyledProps<typeof BaseAccordion.Root>): import("react").JSX.Element;
7
+ export type AccordionSize = "sm" | "md" | "lg";
8
+ export interface AccordionRootProps extends StyledProps<typeof BaseAccordion.Root> {
9
+ /** @default "md" */
10
+ size?: AccordionSize;
11
+ }
12
+ /**
13
+ * The list — bare, so the page decides the surface: on photon.codes the
14
+ * FAQ card is the section's, 32px around the list; in a settings page it
15
+ * is a `Card`. Items divide with a hairline and the list has no outer
16
+ * lines. Items are independent, as on the site, so `multiple` is on
17
+ * unless said otherwise.
18
+ */
19
+ declare function Root({ className, multiple, size, ...props }: AccordionRootProps): import("react").JSX.Element;
20
+ /**
21
+ * One question, padded above and below, whatever is open inside. The
22
+ * hairline under it overlaps the next item by its own pixel, the way
23
+ * Framer draws a border inside the box, so an item advances by exactly
24
+ * its padding and its rows: 68px at `lg`, the site's figure. The last
25
+ * item draws none.
26
+ */
27
+ declare function Item({ className, ...props }: StyledProps<typeof BaseAccordion.Item>): import("react").JSX.Element;
8
28
  declare function Header({ className, ...props }: StyledProps<typeof BaseAccordion.Header>): import("react").JSX.Element;
9
- declare function Trigger({ className, ...props }: StyledProps<typeof BaseAccordion.Trigger>): import("react").JSX.Element;
10
- declare function Panel({ className, ...props }: StyledProps<typeof BaseAccordion.Panel>): import("react").JSX.Element;
11
29
  /**
12
- * Stacked, individually-collapsible sections built on Base UI `Accordion`.
13
- * Single-open by default; set `openMultiple` to allow several at once. Each
14
- * `Accordion.Item` needs a `value`; control the open set with `value` /
15
- * `onValueChange` or `defaultValue`.
30
+ * The question row: the question in medium with the chevron at the row's
31
+ * end. The chevron is the site's small one (a 6.5×3.5 stroke in a 24px
32
+ * box, weight 1.5) and the trigger wears it itself — pass only the
33
+ * question. Open, the question settles to the description tone and the
34
+ * chevron makes a half turn, both on the section spring; the chevron keeps
35
+ * the primary ink. The whole item is the target, as on the site: the hit
36
+ * area reaches through the item's padding above and below the row.
37
+ */
38
+ declare function Trigger({ className, children, ...props }: StyledProps<typeof BaseAccordion.Trigger>): import("react").JSX.Element;
39
+ /**
40
+ * The answer: medium in the primary ink, one type step under the
41
+ * question, a short gap below it. Opening, the panel's height rides the
42
+ * section spring while the answer arrives 100ms later on the appear
43
+ * spring, fading in as it rises 20px. Closing, the answer is gone at once
44
+ * and the height settles on the section spring — the site's way round.
45
+ */
46
+ declare function Panel({ className, children, ...props }: StyledProps<typeof BaseAccordion.Panel>): import("react").JSX.Element;
47
+ /**
48
+ * Questions that each reveal an answer — photon.codes's FAQ, as a
49
+ * component, built on Base UI `Accordion`. Each `Accordion.Item` needs a
50
+ * `value`; control the open set with `value` / `onValueChange` or
51
+ * `defaultValue`. Items are independent by default; `multiple={false}`
52
+ * opens one at a time. Three sizes; `lg` is the site's.
16
53
  */
17
54
  export declare const Accordion: {
18
55
  Root: typeof Root;
19
- Item: import('react').ForwardRefExoticComponent<Omit<import('@base-ui/react').AccordionItemProps, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
56
+ Item: typeof Item;
20
57
  Header: typeof Header;
21
58
  Trigger: typeof Trigger;
22
59
  Panel: typeof Panel;
@@ -11,7 +11,8 @@ export interface CheckboxCardProps extends Omit<ComponentProps<typeof BaseCheckb
11
11
  }
12
12
  /**
13
13
  * A `Checkbox` styled as a bordered, selectable tile — title, optional
14
- * description, and either a stacked icon or the checkbox box. The tile is a
14
+ * description, and either a stacked icon above them or the checkbox box at
15
+ * the end of the title row. The tile is a
15
16
  * `<label>` around the control so clicks (including on the icon/title) are a
16
17
  * trusted activation of the hidden input Base UI renders beside the root.
17
18
  */
@@ -110,9 +110,10 @@ export interface DataTableProps<TData extends RowData> extends Omit<ComponentPro
110
110
  /**
111
111
  * Rows that navigate: each row is one link into a subpage, through the
112
112
  * host's `LinkProvider`. Beside a `BasicPage.Aside`, the subpage opens
113
- * next to the table.
113
+ * next to the table. Return nothing for a row with no page yet (one
114
+ * still in flight) and that row stays plain.
114
115
  */
115
- rowHref?: (row: TData) => string;
116
+ rowHref?: (row: TData) => string | null | undefined;
116
117
  /** The row whose page is open (its id) — it keeps the wash. */
117
118
  current?: string | null;
118
119
  /**
@@ -11,8 +11,9 @@ export interface RadioCardProps extends Omit<ComponentProps<typeof BaseRadio.Roo
11
11
  }
12
12
  /**
13
13
  * A `Radio` styled as a bordered, selectable tile — title, optional
14
- * description, and either a stacked icon or the radio dot. The tile *is*
15
- * the radio (see `usePressPulse`); an inner glyph is decorative.
14
+ * description, and either a stacked icon above them or the radio dot at
15
+ * the end of the title row. The tile *is* the radio (see `usePressPulse`);
16
+ * an inner glyph is decorative.
16
17
  */
17
18
  export declare function RadioCard({ className, title, description, icon, disabled, ...props }: RadioCardProps): import("react").JSX.Element;
18
19
  export declare namespace RadioCard {
@@ -71,9 +71,10 @@ export declare const FADE_SWAP: {
71
71
  readonly ease: "easeOut";
72
72
  };
73
73
  /**
74
- * Displacement — a bar's rise, a drawer's scope swap, a search box's
75
- * width. Travel is linear: the eye reads speed changes as personality,
76
- * and furniture has none.
74
+ * Displacement — a bar's rise, a drawer's scope swap. Travel is linear:
75
+ * the eye reads speed changes as personality, and furniture has none.
76
+ * (A search box's width is a reflow, not travel: it rides the page
77
+ * spring, as a menu opens.)
77
78
  */
78
79
  export declare const TRAVEL_LINEAR: {
79
80
  readonly duration: 0.2;
@@ -211,6 +211,16 @@ export interface BasicPageHeaderProps extends Omit<ComponentProps<"div">, Reveal
211
211
  ref?: Ref<HTMLDivElement>;
212
212
  }
213
213
  declare function Header({ enter, className, ...props }: BasicPageHeaderProps): import("react").JSX.Element;
214
+ /**
215
+ * An action at the header row's end — a small button (or two) seated in
216
+ * the `Header` grid the way `Search` is: top-right of the block, its top
217
+ * edge on the title row, flush with the card edge below. Put it inside
218
+ * `Header` after the text; with a `Search` present it stands to the
219
+ * search's right.
220
+ */
221
+ declare function HeaderAction({ className, ...props }: ComponentProps<"div"> & {
222
+ ref?: Ref<HTMLDivElement>;
223
+ }): import("react").JSX.Element;
214
224
  /**
215
225
  * Section heading — same padding as the page title, smaller type. Hashable via
216
226
  * slug `id` (not a link).
@@ -679,8 +689,25 @@ export interface BasicPageAsideProps {
679
689
  */
680
690
  open?: boolean;
681
691
  /**
682
- * The empty panel's face — a quiet line that says what opens here.
683
- * Optional; without it the reserved panel stands empty.
692
+ * How the panel meets the page on a wide pane. `overlay`, the default,
693
+ * floats over the page's right edge: it takes no ground, surfaces when
694
+ * a subpage opens and leaves with it, and the page beneath stays live.
695
+ * `inline` docks it: the panel's ground is reserved from the first
696
+ * paint, the page beside it never reflows as subpages open and close,
697
+ * and `placeholder` wears the empty panel.
698
+ */
699
+ variant?: "overlay" | "inline";
700
+ /**
701
+ * For an overlay: dim the page behind and keep interaction in the panel.
702
+ * Focus stays inside, the backdrop is a way out, Escape still closes.
703
+ * Off by default, so the page stays live under the panel and opening
704
+ * another row swaps the panel's page.
705
+ */
706
+ modal?: boolean;
707
+ /**
708
+ * The empty inline panel's face — a quiet line that says what opens
709
+ * here. Optional; without it the reserved panel stands empty. An
710
+ * overlay has no empty face: closed, it is not there.
684
711
  */
685
712
  placeholder?: ReactNode;
686
713
  /**
@@ -692,29 +719,34 @@ export interface BasicPageAsideProps {
692
719
  }
693
720
  /**
694
721
  * A subpage beside the page. On a pane wide enough for two columns (48rem,
695
- * Tailwind's `md`) it is a panel at the right edge, and the panel holds
696
- * its ground from the first paint: the page beside it never reflows as
722
+ * Tailwind's `md`) it is a panel at the right edge. By default the panel
723
+ * floats: it takes no ground, surfaces over the page's edge when a
724
+ * subpage opens and leaves with it, and the page beneath stays live
725
+ * unless `modal` dims it. `variant="inline"` docks it: the panel holds
726
+ * its ground from the first paint, the page beside it never reflows as
697
727
  * subpages open and close — only the panel's face changes, `placeholder`
698
- * while empty, the subpage while one is open. The subpage scrolls on its
699
- * own, its `Back` is the close in the corner, Escape closes too. On a
700
- * narrower pane the same subpage is the page — the list steps aside, the
701
- * subpage takes the pane with its own `Back` — so one subpage component
702
- * serves both. Works under either `Root` width: the page keeps its own
703
- * column cap beside the panel, and leftover space is margin.
704
- */
705
- declare function Aside({ open, placeholder, onClose, children }: BasicPageAsideProps): import('react').ReactPortal | null;
706
- /**
707
- * The table's search, standing at the end of a section header — put it in
708
- * the `Header` beside the title and it adopts the searchable table's box
709
- * (the table then renders none of its own). On a header at least 36rem
710
- * wide the box simply stands open: the header is a container, CSS
711
- * decides, nothing is measured. On a narrower one a round button holds
712
- * the place; clicking it opens the box — the width glides linearly, the
713
- * page's travel — with focus landing inside. Emptied and blurred it
714
- * glides shut and the button returns; Escape clears, blurs, and shuts.
715
- * Renders nothing until a table with `search` is on the page.
716
- */
717
- declare function Search({ variant, className, ...props }: ComponentProps<"div"> & {
728
+ * while empty, the subpage while one is open. Either way the subpage
729
+ * scrolls on its own, its `Back` is the close in the corner, Escape
730
+ * closes too. On a narrower pane the same subpage is the page — the list
731
+ * steps aside, the subpage takes the pane with its own `Back` — so one
732
+ * subpage component serves both. Works under either `Root` width: the
733
+ * page keeps its own column cap beside the panel, and leftover space is
734
+ * margin.
735
+ */
736
+ declare function Aside({ open, variant, modal, placeholder, onClose, children, }: BasicPageAsideProps): import("react").JSX.Element | null;
737
+ /**
738
+ * Controlled, the query and its setter come together: a `value` with no
739
+ * way to change it would be a field that silently ignores typing.
740
+ */
741
+ type BasicPageSearchControl = {
742
+ value: string;
743
+ onValueChange: (value: string) => void;
744
+ } | {
745
+ value?: undefined;
746
+ onValueChange?: undefined;
747
+ };
748
+ export type BasicPageSearchProps = BasicPageSearchOwnProps & BasicPageSearchControl;
749
+ interface BasicPageSearchOwnProps extends ComponentProps<"div"> {
718
750
  /**
719
751
  * The face. `box` (default) is the bordered field beside a section
720
752
  * title — open on a wide header, a round button that glides it open on
@@ -723,7 +755,44 @@ declare function Search({ variant, className, ...props }: ComponentProps<"div">
723
755
  * a boxed control would be noise; it flexes instead of folding.
724
756
  */
725
757
  variant?: "box" | "ghost";
726
- }): import("react").JSX.Element;
758
+ /**
759
+ * When the box folds to its round button. `auto` (default): a header at
760
+ * least 36rem wide shows the box open, a narrower one the button.
761
+ * `always`: the button at every width, the box gliding open from it on
762
+ * click — for a list whose header has the room but wants the lighter
763
+ * face.
764
+ */
765
+ fold?: "auto" | "always";
766
+ /**
767
+ * The field's placeholder in the controlled mode (`value` with
768
+ * `onValueChange`: the page searches its own list rather than a table,
769
+ * the Search shows `placeholder`, reports typing through `onValueChange`,
770
+ * and clears through it on Escape; without `value` it adopts the
771
+ * searchable table on the page). About twenty characters fit the open box.
772
+ */
773
+ placeholder?: string;
774
+ /**
775
+ * The accessible name of the button and the field — the placeholder
776
+ * unless said otherwise, for a placeholder too short to say what the
777
+ * search covers.
778
+ */
779
+ label?: string;
780
+ }
781
+ /**
782
+ * A search standing at the end of a section header. Put it in the
783
+ * `Header` beside the title and it adopts the searchable table's box (the
784
+ * table then renders none of its own); give it `value` and it searches
785
+ * the page's own list of rows, no table needed. On a header at least
786
+ * 36rem wide the box simply stands open: the header is a container, CSS
787
+ * decides, nothing is measured. On a narrower one — or at every width
788
+ * with `fold="always"` — a round button holds the place; clicking it
789
+ * glides the box open on the page spring, the two faces crossfading, with
790
+ * focus landing inside. The box stays while it holds a query; emptied and
791
+ * blurred it glides shut and the button returns, and Escape clears,
792
+ * blurs, and shuts. Without `value`, renders nothing until a table with
793
+ * `search` is on the page.
794
+ */
795
+ declare function Search({ variant, fold, value, onValueChange, placeholder, label, className, ...props }: BasicPageSearchProps): import("react").JSX.Element;
727
796
  export declare const BasicPage: {
728
797
  Root: typeof Root;
729
798
  Aside: typeof Aside;
@@ -736,6 +805,7 @@ export declare const BasicPage: {
736
805
  Title: typeof Title;
737
806
  Stack: typeof Stack;
738
807
  Header: typeof Header;
808
+ HeaderAction: typeof HeaderAction;
739
809
  SectionTitle: typeof SectionTitle;
740
810
  Description: typeof Description;
741
811
  TableCount: typeof TableCount;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@photon-ai/pho-ui",
3
- "version": "2.7.1",
3
+ "version": "2.9.0",
4
4
  "description": "Pho Design System — Photon's React component library, built on Base UI",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -89,6 +89,60 @@
89
89
  1
90
90
  );
91
91
 
92
+ /* photon.codes's section spring (Framer spring-physics 436 42 1; critically
93
+ damped, settled by 500ms): an accordion item's height, its chevron's
94
+ half turn, and the question's tone all ride it. Sampled over 500ms. */
95
+ --ease-pho-spring-reveal: linear(
96
+ 0,
97
+ 0.097 5%,
98
+ 0.2805 10%,
99
+ 0.4642 15%,
100
+ 0.6175 20%,
101
+ 0.7348 25%,
102
+ 0.82 30%,
103
+ 0.8798 35%,
104
+ 0.9208 40%,
105
+ 0.9484 45%,
106
+ 0.9667 50%,
107
+ 0.9787 55%,
108
+ 0.9865 60%,
109
+ 0.9915 65%,
110
+ 0.9948 70%,
111
+ 0.9968 75%,
112
+ 0.9981 80%,
113
+ 0.999 85%,
114
+ 0.9995 90%,
115
+ 0.9998 95%,
116
+ 1
117
+ );
118
+
119
+ /* photon.codes's appear effect (Framer spring-physics 417 68 1; overdamped,
120
+ a long soft tail): content fading in and rising into place. Sampled
121
+ over 700ms; pair it with the site's 100ms delay. */
122
+ --ease-pho-spring-appear: linear(
123
+ 0,
124
+ 0.1294 5%,
125
+ 0.3063 10%,
126
+ 0.4544 15%,
127
+ 0.5721 20%,
128
+ 0.6649 25%,
129
+ 0.7381 30%,
130
+ 0.7957 35%,
131
+ 0.8411 40%,
132
+ 0.8769 45%,
133
+ 0.905 50%,
134
+ 0.9272 55%,
135
+ 0.9447 60%,
136
+ 0.9585 65%,
137
+ 0.9693 70%,
138
+ 0.9779 75%,
139
+ 0.9846 80%,
140
+ 0.9899 85%,
141
+ 0.9941 90%,
142
+ 0.9974 95%,
143
+ 1
144
+ );
145
+
92
146
  --text-*: initial;
93
147
  --text-xs: 0.8125rem;
94
148
  --text-xs--line-height: 1.125rem;