@photon-ai/pho-ui 2.10.0 → 2.12.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":"data-table-Cmz0p-jt.js","names":["__iconNode"],"sources":["../../../../node_modules/.pnpm/@tabler+icons-react@3.46.0_react@19.2.8/node_modules/@tabler/icons-react/dist/esm/icons/IconChevronUp.mjs","../../../../node_modules/.pnpm/@tabler+icons-react@3.46.0_react@19.2.8/node_modules/@tabler/icons-react/dist/esm/icons/IconSelector.mjs","../../src/components/data-table/data-table.tsx"],"sourcesContent":["/**\n * @license @tabler/icons-react v3.46.0 - MIT\n *\n * This source code is licensed under the MIT license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createReactComponent from '../createReactComponent.mjs';\n\nconst __iconNode = [[\"path\", { \"d\": \"M6 15l6 -6l6 6\", \"key\": \"svg-0\" }]];\nconst IconChevronUp = createReactComponent(\"outline\", \"chevron-up\", \"ChevronUp\", __iconNode);\n\nexport { __iconNode, IconChevronUp as default };\n//# sourceMappingURL=IconChevronUp.mjs.map\n","/**\n * @license @tabler/icons-react v3.46.0 - MIT\n *\n * This source code is licensed under the MIT license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createReactComponent from '../createReactComponent.mjs';\n\nconst __iconNode = [[\"path\", { \"d\": \"M8 9l4 -4l4 4\", \"key\": \"svg-0\" }], [\"path\", { \"d\": \"M16 15l-4 4l-4 -4\", \"key\": \"svg-1\" }]];\nconst IconSelector = createReactComponent(\"outline\", \"selector\", \"Selector\", __iconNode);\n\nexport { __iconNode, IconSelector as default };\n//# sourceMappingURL=IconSelector.mjs.map\n","import {\n createContext,\n Fragment,\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n type ComponentProps,\n type ReactNode,\n type Ref,\n} from \"react\";\nimport {\n columnFilteringFeature,\n createColumnHelper,\n createFilteredRowModel,\n createSortedRowModel,\n filterFn_includesString,\n FlexRender,\n globalFilteringFeature,\n rowSelectionFeature,\n rowSortingFeature,\n sortFn_alphanumeric,\n sortFn_basic,\n sortFn_datetime,\n sortFn_text,\n tableFeatures,\n useTable,\n type ColumnDef,\n type RowData,\n type SortingState,\n} from \"@tanstack/react-table\";\nimport {\n IconChevronDown,\n IconChevronUp,\n IconSearch,\n IconSelector,\n} from \"@tabler/icons-react\";\nimport { Skeleton } from \"../skeleton\";\nimport { Checkbox } from \"../checkbox\";\nimport { SPRING_ENTRANCE } from \"../../motion\";\nimport { AnimatedHeight } from \"../../utils/animated-height\";\nimport { cn } from \"../../utils/cn\";\nimport { useLinkComponent } from \"../../utils/link-provider\";\n\n/**\n * DataTable — a table of records with column semantics: sortable headers,\n * aligned cells, rows that are links into a subpage. TanStack Table (v9)\n * does the thinking — column defs, sorting, row identity — and renders\n * nothing; every pixel here is Pho's.\n *\n * Design notes:\n * - The surface is `BasicPage.Card`'s: hairline border, `base` radius, the\n * page's primary fill, hairline rules between rows. A table is a card of\n * rows that happen to line up in columns, so it sits in a page's `Stack`\n * as any card does.\n * - Header cells are description ink at body size — never small caps, never\n * bold. Hierarchy comes from ink alone; the header is a label row, not a\n * banner.\n * - A sortable header is a button inside the cell: the label and its\n * indicator (`IconSelector` at rest, a chevron once sorted). `aria-sort`\n * sits on the `th`.\n * - Rows that navigate stretch one link across the row (the first cell's\n * link, extended with a pseudo-element), so the whole row is one target\n * for pointer and one stop for keyboard. Hover washes the row; the row\n * whose page is open beside the table (`current`) keeps the wash — the\n * same cue `BasicPage.RowLink` gives under `aria-current`.\n * - Numbers align to the end with tabular figures (`meta.align: \"end\"`).\n * - `search` is one box over every column — plain contains matching, and\n * the body says when nothing matches. In a page, `BasicPage.Search` in\n * the section header adopts the box; alone, the table keeps one in a\n * toolbar row inside its frame. Server-side search keeps its own box\n * outside.\n * - A wide table scrolls sideways inside its own frame; the page never does.\n * `contain: inline-size` keeps the table's intrinsic width out of the\n * page's — a host that sizes to content (a scroll area's fit-content\n * pane, a grid's auto column) would otherwise grow to the widest row.\n *\n * Reach for it when a list needs column semantics — sorting, aligned\n * figures, several fields side by side. A list where each row is one\n * label and a chevron stays `BasicPage.RowLink`.\n */\n\n/** Column metadata a `DataTable` understands. */\nexport interface DataTableColumnMeta {\n /** Cell alignment — `end` for figures (right-aligned, tabular). */\n align?: \"start\" | \"end\";\n}\n\n/**\n * The feature set every `DataTable` runs on — sorting with the stock sort\n * functions, the column meta above. Fixed, so column definitions written\n * once fit every table.\n */\nconst features = tableFeatures({\n rowSortingFeature,\n rowSelectionFeature,\n columnFilteringFeature,\n globalFilteringFeature,\n sortedRowModel: createSortedRowModel(),\n filteredRowModel: createFilteredRowModel(),\n filterFns: { includesString: filterFn_includesString },\n sortFns: {\n alphanumeric: sortFn_alphanumeric,\n basic: sortFn_basic,\n datetime: sortFn_datetime,\n text: sortFn_text,\n },\n columnMeta: {} as DataTableColumnMeta,\n});\n\nexport type DataTableFeatures = typeof features;\n\n/** A column definition for a `DataTable` of `TData` rows. */\nexport type DataTableColumn<\n TData extends RowData,\n TValue = unknown,\n> = ColumnDef<DataTableFeatures, TData, TValue>;\n\n/**\n * The column helper, bound to the table's feature set — `accessor`,\n * `display`, `group`, and `columns` to collect them with their types\n * intact.\n *\n * @example\n * const col = createColumns<Request>();\n * const columns = col.columns([\n * col.accessor(\"actor\", { header: \"Actor\" }),\n * col.accessor(\"amount\", { header: \"Amount\", meta: { align: \"end\" } }),\n * ]);\n */\nexport function createColumns<TData extends RowData>() {\n return createColumnHelper<DataTableFeatures, TData>();\n}\n\n/**\n * The leading checkbox column selection adds. The header checkbox works\n * the visible set: everything the search still shows toggles together,\n * while rows selected earlier and then hidden stay selected.\n */\nconst SELECT_COLUMN = createColumns<RowData>().display({\n id: \"select\",\n enableSorting: false,\n header: ({ table }) => {\n const visible = table.getRowModel().rows;\n const chosen = visible.filter((row) => row.getIsSelected()).length;\n const all = visible.length > 0 && chosen === visible.length;\n return (\n <Checkbox\n size=\"sm\"\n aria-label=\"Select all\"\n checked={all}\n indeterminate={chosen > 0 && !all}\n onCheckedChange={(checked) => {\n table.setRowSelection((old) => {\n const next = { ...old };\n for (const row of visible) {\n if (checked === true) next[row.id] = true;\n else delete next[row.id];\n }\n return next;\n });\n }}\n />\n );\n },\n cell: ({ row }) => (\n // Above a row link's stretched overlay — the box stays clickable.\n // `relative` alone makes no stacking context; without `z-10` the\n // overlay's ::after paints later and eats the click.\n <span className=\"relative z-10 flex\">\n <Checkbox\n size=\"sm\"\n aria-label=\"Select row\"\n checked={row.getIsSelected()}\n onCheckedChange={(checked) => row.toggleSelected(checked === true)}\n />\n </span>\n ),\n});\n\nexport interface DataTableProps<TData extends RowData> extends Omit<\n ComponentProps<\"div\">,\n \"children\"\n> {\n columns: ReadonlyArray<DataTableColumn<TData, any>>;\n data: ReadonlyArray<TData>;\n /**\n * A row's identity — what `current` is matched against, and what keeps a\n * row itself across sorts. Defaults to the row's `id` field, else its\n * index.\n */\n getRowId?: (row: TData) => string;\n /**\n * Rows that navigate: each row is one link into a subpage, through the\n * host's `LinkProvider`. Beside a `BasicPage.Aside`, the subpage opens\n * next to the table. Return nothing for a row with no page yet (one\n * still in flight) and that row stays plain.\n */\n rowHref?: (row: TData) => string | null | undefined;\n /** The row whose page is open (its id) — it keeps the wash. */\n current?: string | null;\n /**\n * Sorting is on for every accessor column (`enableSorting: false` on a\n * column turns it off there). Uncontrolled by default; `defaultSort`\n * seeds it. Pass `sort` with `onSortChange` to own it — the way to sort\n * on the server.\n */\n defaultSort?: SortingState;\n sort?: SortingState;\n onSortChange?: (sort: SortingState) => void;\n /**\n * A search box on the table — `true`, or the placeholder it should\n * show. One box searches every column (a column opts out with\n * `enableGlobalFilter: false`); matching is a plain case-insensitive\n * contains, on the client. With no matches the body says so. When the\n * list doesn't all come down, keep the box outside and ask the server.\n */\n search?: boolean | string;\n /**\n * Controls in the frame's top band — a `BasicPage.Search` first, then a\n * filter or two. The band is part of the table (one surface, a hairline\n * above the header row), so nothing floats over the page and the\n * spacing above and below is the frame's own. A `Search` here adopts\n * the table's box the way a header one does.\n */\n toolbar?: ReactNode;\n /**\n * Row selection: hand either of these and rows grow a leading checkbox\n * column. The header checkbox works the visible set — everything the\n * search still shows — so select-all after a search means what it says;\n * rows selected earlier and then hidden stay selected. `selected` is\n * the selected ids (ids come from `getRowId`; pair with\n * `onSelectedChange` to own the state); `onSelectedChange` alone\n * listens while the table keeps it. The page renders its own bulk\n * actions from the ids — the table only keeps the score.\n */\n selected?: readonly string[];\n onSelectedChange?: (ids: string[]) => void;\n /**\n * File rows into labeled groups: return a label and the row renders\n * under it, `null` and it stays in the main body. Groups follow the\n * main body, in the order their labels first appear in `data`, each\n * under a quiet label row — and only while they have rows, so a group\n * a search empties simply leaves. Sorting orders rows within their\n * group.\n */\n group?: (row: TData) => string | null;\n /**\n * Glide the frame's height when the rows change — a search narrowing,\n * a filter, bones becoming rows. The height follows the content on the\n * page spring instead of jumping; reserve it for tables whose rows\n * change while mounted (same contract as `BasicPage.Card`'s).\n */\n animateHeight?: boolean;\n /** Bones while the rows are on their way — `true` for five, or a count. */\n loading?: boolean | number;\n /** What the body says when there are no rows. */\n empty?: ReactNode;\n ref?: Ref<HTMLDivElement>;\n}\n\n/**\n * A host that sizes itself around tables provides this; each table\n * announces itself while mounted. `BasicPage.Root` runs wide when the\n * page carries one and `width` was left unsaid.\n */\nexport const TableHostContext = createContext<\n ((present: boolean) => void) | null\n>(null);\n\n/** A searchable table's search, published for a header to adopt. */\nexport interface TableSearchApi {\n placeholder: string;\n query: string;\n setQuery: (query: string) => void;\n /** Rows still showing after the current query. */\n matched: number;\n /** Rows in the list, before search. */\n total: number;\n}\n\ninterface TableSearchState {\n api: TableSearchApi | null;\n adopted: boolean;\n}\n\n/**\n * The channel between a searchable table and a `BasicPage.Search` placed\n * in a section header: the table publishes its search (placeholder,\n * query, the setter); a mounted `Search` adopts it, and the table's own\n * box yields. Provided by `BasicPage.Root`; a table outside any page\n * keeps its built-in box. One searchable table per page — a later\n * publisher takes the slot.\n */\nexport interface TableSearchStore {\n get: () => TableSearchState;\n subscribe: (listener: () => void) => () => void;\n publish: (api: TableSearchApi | null) => void;\n adopt: (adopted: boolean) => void;\n}\n\nexport function createTableSearchStore(): TableSearchStore {\n let state: TableSearchState = { api: null, adopted: false };\n const listeners = new Set<() => void>();\n const emit = () => {\n for (const listener of listeners) listener();\n };\n return {\n get: () => state,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n publish(api) {\n state = { ...state, api };\n emit();\n },\n adopt(adopted) {\n state = { ...state, adopted };\n emit();\n },\n };\n}\n\nexport const TableSearchContext = createContext<TableSearchStore | null>(null);\n\nconst subscribeToNothing = () => () => {};\n\nexport interface GhostSearchProps extends Omit<\n ComponentProps<\"input\">,\n \"value\" | \"onChange\" | \"type\" | \"children\"\n> {\n value: string;\n onChange: (value: string) => void;\n}\n\n/**\n * The search's ghost face — a magnifier and a bare field, quiet ink on a\n * text line, no box around it. What a table wears in its own toolbar\n * band; `BasicPage.Search variant=\"ghost\"` is the same face wherever a\n * page wants it. Presentational: hand it the value and the setter.\n */\nexport function GhostSearch({\n value,\n onChange,\n className,\n ...props\n}: GhostSearchProps) {\n return (\n <label className={cn(\"flex min-w-40 flex-1 items-center gap-2\", className)}>\n <IconSearch\n aria-hidden\n className=\"text-pho-description size-4 shrink-0\"\n />\n <input\n type=\"search\"\n value={value}\n onChange={(event) => onChange(event.target.value)}\n className=\"text-pho-primary placeholder:text-pho-description outline-pho-brand w-full min-w-0 rounded-sm bg-transparent text-sm focus-visible:outline-2 focus-visible:outline-offset-2\"\n {...props}\n />\n </label>\n );\n}\n\n/** Same chrome as `BasicPage.Card`, minus its dividers — rows draw their own. */\nconst DATA_TABLE_SURFACE =\n \"rounded-base border border-pho-secondary bg-pho-primary\";\n\nconst DEFAULT_BONES = 5;\n\nfunction defaultRowId(row: unknown, index: number): string {\n const id = (row as { id?: unknown } | null)?.id;\n return typeof id === \"string\" || typeof id === \"number\"\n ? String(id)\n : String(index);\n}\n\nfunction DataTable<TData extends RowData>({\n columns,\n data,\n getRowId,\n rowHref,\n current,\n selected,\n onSelectedChange,\n defaultSort,\n sort,\n onSortChange,\n search,\n toolbar,\n group,\n animateHeight,\n loading,\n empty = \"Nothing here yet.\",\n className,\n ...props\n}: DataTableProps<TData>) {\n const Link = useLinkComponent();\n const announce = useContext(TableHostContext);\n const searching = search === true || typeof search === \"string\";\n const [query, setQuery] = useState(\"\");\n const placeholder = typeof search === \"string\" ? search : \"Search\";\n // A `BasicPage.Search` in a section header adopts the box; the table\n // then renders no toolbar of its own.\n const searchStore = useContext(TableSearchContext);\n const adopted = useSyncExternalStore(\n searchStore?.subscribe ?? subscribeToNothing,\n () => searchStore?.get().adopted ?? false,\n () => false,\n );\n useLayoutEffect(() => {\n if (announce == null) return;\n announce(true);\n return () => announce(false);\n }, [announce]);\n const selecting = selected != null || onSelectedChange != null;\n const [ownSelection, setOwnSelection] = useState<Record<string, true>>({});\n const rowSelection: Record<string, true> =\n selected != null\n ? Object.fromEntries(selected.map((id) => [id, true as const]))\n : ownSelection;\n // Sorting mirrors selection: the component always owns a fallback state,\n // so `defaultSort` + `onSortChange` without `sort` still sorts — a custom\n // `onSortingChange` replaces the table's own updater entirely.\n const [ownSort, setOwnSort] = useState<SortingState>(defaultSort ?? []);\n const sorting: SortingState = sort ?? ownSort;\n const table = useTable<DataTableFeatures, TData>({\n features,\n columns: (selecting\n ? [SELECT_COLUMN as DataTableColumn<TData, any>, ...columns]\n : columns) as Array<DataTableColumn<TData, any>>,\n data: data as Array<TData>,\n getRowId: getRowId ? (row) => getRowId(row) : defaultRowId,\n // Keys only when meant: a present-but-undefined slice of `state` reads\n // as \"controlled, with nobody listening\".\n state: {\n sorting,\n ...(searching ? { globalFilter: query } : {}),\n ...(selecting ? { rowSelection } : {}),\n },\n ...(selecting\n ? {\n enableRowSelection: true,\n onRowSelectionChange: (\n updater:\n | Record<string, true>\n | ((old: Record<string, true>) => Record<string, true>),\n ) => {\n const next =\n typeof updater === \"function\" ? updater(rowSelection) : updater;\n if (selected == null) setOwnSelection(next);\n onSelectedChange?.(Object.keys(next));\n },\n }\n : {}),\n ...(searching\n ? {\n onGlobalFilterChange: setQuery,\n globalFilterFn: \"includesString\" as const,\n }\n : {}),\n onSortingChange: (\n updater: SortingState | ((prev: SortingState) => SortingState),\n ) => {\n const next = typeof updater === \"function\" ? updater(sorting) : updater;\n if (sort == null) setOwnSort(next);\n onSortChange?.(next);\n },\n });\n const rows = table.getRowModel().rows;\n const total = data.length;\n const matched = searching ? rows.length : total;\n useLayoutEffect(() => {\n if (!searching || searchStore == null) return;\n searchStore.publish({ placeholder, query, setQuery, matched, total });\n }, [searching, searchStore, placeholder, query, matched, total]);\n useEffect(() => {\n if (searchStore == null) return;\n return () => searchStore.publish(null);\n }, [searchStore]);\n const leafCount = table.getAllLeafColumns().length;\n const bones = useMemo(\n () =>\n loading\n ? Array.from({ length: loading === true ? DEFAULT_BONES : loading })\n : [],\n [loading],\n );\n\n const renderRow = (row: (typeof rows)[number]) => {\n const href = rowHref?.(row.original);\n const isCurrent = current != null && current === row.id;\n return (\n <tr\n key={row.id}\n className={cn(\n \"relative\",\n href != null &&\n \"hover:bg-pho-primary-hover outline-pho-brand transition-colors has-[a:focus-visible]:outline-2 has-[a:focus-visible]:-outline-offset-2\",\n (isCurrent || row.getIsSelected()) && \"bg-pho-primary-hover\",\n )}\n >\n {row.getAllCells().map((cell, index) => {\n const align = cell.column.columnDef.meta?.align ?? \"start\";\n return (\n <td\n key={cell.id}\n className={cn(\n \"text-pho-primary h-12 px-5 align-middle whitespace-nowrap\",\n align === \"end\" && \"text-right tabular-nums\",\n )}\n >\n {/* The link lives in the first data cell; with selection on,\n cell 0 is the checkbox and must stay outside the anchor. */}\n {index === (selecting ? 1 : 0) && href != null ? (\n // The row's one link, stretched over the row; the\n // ring is the row's (`has-[a:focus-visible]`).\n <Link\n href={href}\n aria-current={isCurrent ? \"page\" : undefined}\n className=\"outline-none after:absolute after:inset-0 after:content-['']\"\n >\n <FlexRender cell={cell} />\n </Link>\n ) : (\n <FlexRender cell={cell} />\n )}\n </td>\n );\n })}\n </tr>\n );\n };\n\n // Partition the (sorted, filtered) rows: the main body, then each\n // labeled group in the order labels first appear in `data`.\n let mainRows = rows;\n let groupSections: Array<[string, typeof rows]> = [];\n if (group != null) {\n mainRows = [];\n const byLabel = new Map<string, typeof rows>();\n const order: string[] = [];\n for (const item of data) {\n const label = group(item);\n if (label != null && !byLabel.has(label)) {\n byLabel.set(label, []);\n order.push(label);\n }\n }\n for (const row of rows) {\n const label = group(row.original);\n if (label == null) {\n mainRows.push(row);\n } else {\n if (!byLabel.has(label)) {\n byLabel.set(label, []);\n order.push(label);\n }\n byLabel.get(label)!.push(row);\n }\n }\n groupSections = order\n .map((label) => [label, byLabel.get(label)!] as [string, typeof rows])\n .filter(([, sectionRows]) => sectionRows.length > 0);\n }\n\n const grid = (\n <table\n className=\"w-full border-collapse text-sm\"\n aria-busy={loading ? true : undefined}\n >\n <thead>\n {table.getHeaderGroups().map((headerGroup) => (\n <tr key={headerGroup.id}>\n {headerGroup.headers.map((header) => {\n const align = header.column.columnDef.meta?.align ?? \"start\";\n const sorted = header.column.getIsSorted();\n const sortable =\n !header.isPlaceholder && header.column.getCanSort();\n return (\n <th\n key={header.id}\n scope=\"col\"\n colSpan={header.colSpan}\n aria-sort={\n sorted === \"asc\"\n ? \"ascending\"\n : sorted === \"desc\"\n ? \"descending\"\n : undefined\n }\n className={cn(\n \"text-pho-description h-10 px-5 text-left align-middle font-normal whitespace-nowrap\",\n align === \"end\" && \"text-right\",\n )}\n >\n {header.isPlaceholder ? null : sortable ? (\n <button\n type=\"button\"\n onClick={header.column.getToggleSortingHandler()}\n className={cn(\n \"group/sort outline-pho-brand hover:text-pho-primary -mx-1 inline-flex items-center gap-1 rounded-sm px-1 transition-colors focus-visible:outline-2\",\n align === \"end\" && \"flex-row-reverse\",\n sorted && \"text-pho-primary\",\n )}\n >\n <FlexRender header={header} />\n <SortIndicator sorted={sorted} />\n </button>\n ) : (\n <FlexRender header={header} />\n )}\n </th>\n );\n })}\n </tr>\n ))}\n </thead>\n <tbody className=\"divide-pho-secondary border-pho-secondary divide-y border-t\">\n {bones.length > 0 ? (\n bones.map((_, index) => (\n <tr key={index}>\n {table.getAllLeafColumns().map((column) => (\n <td key={column.id} className=\"h-12 px-5 align-middle\">\n <Skeleton\n className={cn(\n \"h-3.5 w-24\",\n column.columnDef.meta?.align === \"end\" && \"ml-auto\",\n )}\n />\n </td>\n ))}\n </tr>\n ))\n ) : rows.length === 0 ? (\n <tr>\n <td\n colSpan={leafCount}\n className=\"text-pho-description px-5 py-10 text-center\"\n >\n {searching && query !== \"\" && data.length > 0 ? (\n <>Nothing matches “{query}”.</>\n ) : (\n empty\n )}\n </td>\n </tr>\n ) : (\n <>\n {mainRows.map(renderRow)}\n {groupSections.map(([label, sectionRows]) => (\n <Fragment key={label}>\n <tr data-table-group=\"\">\n <td\n colSpan={leafCount}\n // A tinted band, not spacing: the wash marks the group\n // off, the height stays compact.\n className=\"bg-pho-page text-pho-description h-9 px-5 align-middle text-xs font-medium\"\n >\n {label}\n </td>\n </tr>\n {sectionRows.map(renderRow)}\n </Fragment>\n ))}\n </>\n )}\n </tbody>\n </table>\n );\n\n return (\n <div\n data-data-table=\"\"\n className={cn(\n DATA_TABLE_SURFACE,\n \"scroll-mt-12 contain-inline-size\",\n !animateHeight && \"overflow-x-auto\",\n className,\n )}\n {...props}\n >\n {(toolbar != null || (searching && !adopted)) && (\n // The toolbar band — table chrome, like the header row: the ghost\n // search on the cells' text line (`px-5`), filters at the end.\n // Quiet ink, no boxes in the box; the hairline below is the only\n // border it brings.\n <div className=\"border-pho-secondary flex min-h-11 flex-wrap items-center gap-x-4 gap-y-1 border-b px-5 py-2\">\n {searching && !adopted && (\n <GhostSearch\n value={query}\n onChange={setQuery}\n placeholder={placeholder}\n aria-label={placeholder}\n />\n )}\n {toolbar}\n </div>\n )}\n {animateHeight ? (\n // The height glides on the page spring while the rows inside swap;\n // sideways scrolling moves onto the measured layer so the frame\n // clips height only.\n <AnimatedHeight\n transition={SPRING_ENTRANCE}\n className=\"overflow-x-auto\"\n >\n {grid}\n </AnimatedHeight>\n ) : (\n grid\n )}\n </div>\n );\n}\n\n/**\n * The sort cue beside a sortable header: the two-way glyph at rest (shown\n * on hover and focus, so a header reads as plain text until it matters), a\n * chevron pointing the way once the column is sorted.\n */\nfunction SortIndicator({ sorted }: { sorted: false | \"asc\" | \"desc\" }) {\n return (\n <span\n aria-hidden\n className={cn(\n \"inline-flex shrink-0 [&>svg]:size-3.5\",\n !sorted &&\n \"opacity-0 transition-opacity group-hover/sort:opacity-100 group-focus-visible/sort:opacity-100\",\n )}\n >\n {sorted === \"asc\" ? (\n <IconChevronUp />\n ) : sorted === \"desc\" ? (\n <IconChevronDown />\n ) : (\n <IconSelector />\n )}\n </span>\n );\n}\n\nexport { DataTable, DATA_TABLE_SURFACE };\n"],"x_google_ignoreList":[0,1],"mappings":"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"}
1
+ {"version":3,"file":"data-table-CHLJfGkS.js","names":["__iconNode"],"sources":["../../../../node_modules/.pnpm/@tabler+icons-react@3.46.0_react@19.2.8/node_modules/@tabler/icons-react/dist/esm/icons/IconChevronUp.mjs","../../../../node_modules/.pnpm/@tabler+icons-react@3.46.0_react@19.2.8/node_modules/@tabler/icons-react/dist/esm/icons/IconSelector.mjs","../../src/components/data-table/data-table.tsx"],"sourcesContent":["/**\n * @license @tabler/icons-react v3.46.0 - MIT\n *\n * This source code is licensed under the MIT license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createReactComponent from '../createReactComponent.mjs';\n\nconst __iconNode = [[\"path\", { \"d\": \"M6 15l6 -6l6 6\", \"key\": \"svg-0\" }]];\nconst IconChevronUp = createReactComponent(\"outline\", \"chevron-up\", \"ChevronUp\", __iconNode);\n\nexport { __iconNode, IconChevronUp as default };\n//# sourceMappingURL=IconChevronUp.mjs.map\n","/**\n * @license @tabler/icons-react v3.46.0 - MIT\n *\n * This source code is licensed under the MIT license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createReactComponent from '../createReactComponent.mjs';\n\nconst __iconNode = [[\"path\", { \"d\": \"M8 9l4 -4l4 4\", \"key\": \"svg-0\" }], [\"path\", { \"d\": \"M16 15l-4 4l-4 -4\", \"key\": \"svg-1\" }]];\nconst IconSelector = createReactComponent(\"outline\", \"selector\", \"Selector\", __iconNode);\n\nexport { __iconNode, IconSelector as default };\n//# sourceMappingURL=IconSelector.mjs.map\n","import {\n createContext,\n Fragment,\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n type ComponentProps,\n type ReactNode,\n type Ref,\n} from \"react\";\nimport {\n columnFilteringFeature,\n createColumnHelper,\n createFilteredRowModel,\n createSortedRowModel,\n filterFn_includesString,\n FlexRender,\n globalFilteringFeature,\n rowSelectionFeature,\n rowSortingFeature,\n sortFn_alphanumeric,\n sortFn_basic,\n sortFn_datetime,\n sortFn_text,\n tableFeatures,\n useTable,\n type ColumnDef,\n type RowData,\n type SortingState,\n} from \"@tanstack/react-table\";\nimport {\n IconChevronDown,\n IconChevronUp,\n IconSearch,\n IconSelector,\n} from \"@tabler/icons-react\";\nimport { Skeleton } from \"../skeleton\";\nimport { Checkbox } from \"../checkbox\";\nimport { SPRING_ENTRANCE } from \"../../motion\";\nimport { AnimatedHeight } from \"../../utils/animated-height\";\nimport { cn } from \"../../utils/cn\";\nimport { useLinkComponent } from \"../../utils/link-provider\";\n\n/**\n * DataTable — a table of records with column semantics: sortable headers,\n * aligned cells, rows that are links into a subpage. TanStack Table (v9)\n * does the thinking — column defs, sorting, row identity — and renders\n * nothing; every pixel here is Pho's.\n *\n * Design notes:\n * - The surface is `BasicPage.Card`'s: hairline border, `base` radius, the\n * page's primary fill, hairline rules between rows. A table is a card of\n * rows that happen to line up in columns, so it sits in a page's `Stack`\n * as any card does.\n * - Header cells are description ink at body size — never small caps, never\n * bold. Hierarchy comes from ink alone; the header is a label row, not a\n * banner.\n * - A sortable header is a button inside the cell: the label and its\n * indicator (`IconSelector` at rest, a chevron once sorted). `aria-sort`\n * sits on the `th`.\n * - Rows that navigate stretch one link across the row (the first cell's\n * link, extended with a pseudo-element), so the whole row is one target\n * for pointer and one stop for keyboard. Hover washes the row; the row\n * whose page is open beside the table (`current`) keeps the wash — the\n * same cue `BasicPage.RowLink` gives under `aria-current`.\n * - Numbers align to the end with tabular figures (`meta.align: \"end\"`).\n * - `search` is one box over every column — plain contains matching, and\n * the body says when nothing matches. In a page, `BasicPage.Search` in\n * the section header adopts the box; alone, the table keeps one in a\n * toolbar row inside its frame. Server-side search keeps its own box\n * outside.\n * - A wide table scrolls sideways inside its own frame; the page never does.\n * `contain: inline-size` keeps the table's intrinsic width out of the\n * page's — a host that sizes to content (a scroll area's fit-content\n * pane, a grid's auto column) would otherwise grow to the widest row.\n *\n * Reach for it when a list needs column semantics — sorting, aligned\n * figures, several fields side by side. A list where each row is one\n * label and a chevron stays `BasicPage.RowLink`.\n */\n\n/** Column metadata a `DataTable` understands. */\nexport interface DataTableColumnMeta {\n /** Cell alignment — `end` for figures (right-aligned, tabular). */\n align?: \"start\" | \"end\";\n}\n\n/**\n * The feature set every `DataTable` runs on — sorting with the stock sort\n * functions, the column meta above. Fixed, so column definitions written\n * once fit every table.\n */\nconst features = tableFeatures({\n rowSortingFeature,\n rowSelectionFeature,\n columnFilteringFeature,\n globalFilteringFeature,\n sortedRowModel: createSortedRowModel(),\n filteredRowModel: createFilteredRowModel(),\n filterFns: { includesString: filterFn_includesString },\n sortFns: {\n alphanumeric: sortFn_alphanumeric,\n basic: sortFn_basic,\n datetime: sortFn_datetime,\n text: sortFn_text,\n },\n columnMeta: {} as DataTableColumnMeta,\n});\n\nexport type DataTableFeatures = typeof features;\n\n/** A column definition for a `DataTable` of `TData` rows. */\nexport type DataTableColumn<\n TData extends RowData,\n TValue = unknown,\n> = ColumnDef<DataTableFeatures, TData, TValue>;\n\n/**\n * The column helper, bound to the table's feature set — `accessor`,\n * `display`, `group`, and `columns` to collect them with their types\n * intact.\n *\n * @example\n * const col = createColumns<Request>();\n * const columns = col.columns([\n * col.accessor(\"actor\", { header: \"Actor\" }),\n * col.accessor(\"amount\", { header: \"Amount\", meta: { align: \"end\" } }),\n * ]);\n */\nexport function createColumns<TData extends RowData>() {\n return createColumnHelper<DataTableFeatures, TData>();\n}\n\n/**\n * The leading checkbox column selection adds. The header checkbox works\n * the visible set: everything the search still shows toggles together,\n * while rows selected earlier and then hidden stay selected.\n */\nconst SELECT_COLUMN = createColumns<RowData>().display({\n id: \"select\",\n enableSorting: false,\n header: ({ table }) => {\n const visible = table.getRowModel().rows;\n const chosen = visible.filter((row) => row.getIsSelected()).length;\n const all = visible.length > 0 && chosen === visible.length;\n return (\n <Checkbox\n size=\"sm\"\n aria-label=\"Select all\"\n checked={all}\n indeterminate={chosen > 0 && !all}\n onCheckedChange={(checked) => {\n table.setRowSelection((old) => {\n const next = { ...old };\n for (const row of visible) {\n if (checked === true) next[row.id] = true;\n else delete next[row.id];\n }\n return next;\n });\n }}\n />\n );\n },\n cell: ({ row }) => (\n // Above a row link's stretched overlay — the box stays clickable.\n // `relative` alone makes no stacking context; without `z-10` the\n // overlay's ::after paints later and eats the click.\n <span className=\"relative z-10 flex\">\n <Checkbox\n size=\"sm\"\n aria-label=\"Select row\"\n checked={row.getIsSelected()}\n onCheckedChange={(checked) => row.toggleSelected(checked === true)}\n />\n </span>\n ),\n});\n\nexport interface DataTableProps<TData extends RowData> extends Omit<\n ComponentProps<\"div\">,\n \"children\"\n> {\n columns: ReadonlyArray<DataTableColumn<TData, any>>;\n data: ReadonlyArray<TData>;\n /**\n * A row's identity — what `current` is matched against, and what keeps a\n * row itself across sorts. Defaults to the row's `id` field, else its\n * index.\n */\n getRowId?: (row: TData) => string;\n /**\n * Rows that navigate: each row is one link into a subpage, through the\n * host's `LinkProvider`. Beside a `BasicPage.Aside`, the subpage opens\n * next to the table. Return nothing for a row with no page yet (one\n * still in flight) and that row stays plain.\n */\n rowHref?: (row: TData) => string | null | undefined;\n /** The row whose page is open (its id) — it keeps the wash. */\n current?: string | null;\n /**\n * Sorting is on for every accessor column (`enableSorting: false` on a\n * column turns it off there). Uncontrolled by default; `defaultSort`\n * seeds it. Pass `sort` with `onSortChange` to own it — the way to sort\n * on the server.\n */\n defaultSort?: SortingState;\n sort?: SortingState;\n onSortChange?: (sort: SortingState) => void;\n /**\n * A search box on the table — `true`, or the placeholder it should\n * show. One box searches every column (a column opts out with\n * `enableGlobalFilter: false`); matching is a plain case-insensitive\n * contains, on the client. With no matches the body says so. When the\n * list doesn't all come down, keep the box outside and ask the server.\n */\n search?: boolean | string;\n /**\n * Controls in the frame's top band — a `BasicPage.Search` first, then a\n * filter or two. The band is part of the table (one surface, a hairline\n * above the header row), so nothing floats over the page and the\n * spacing above and below is the frame's own. A `Search` here adopts\n * the table's box the way a header one does.\n */\n toolbar?: ReactNode;\n /**\n * Row selection: hand either of these and rows grow a leading checkbox\n * column. The header checkbox works the visible set — everything the\n * search still shows — so select-all after a search means what it says;\n * rows selected earlier and then hidden stay selected. `selected` is\n * the selected ids (ids come from `getRowId`; pair with\n * `onSelectedChange` to own the state); `onSelectedChange` alone\n * listens while the table keeps it. The page renders its own bulk\n * actions from the ids — the table only keeps the score.\n */\n selected?: readonly string[];\n onSelectedChange?: (ids: string[]) => void;\n /**\n * File rows into labeled groups: return a label and the row renders\n * under it, `null` and it stays in the main body. Groups follow the\n * main body, in the order their labels first appear in `data`, each\n * under a quiet label row — and only while they have rows, so a group\n * a search empties simply leaves. Sorting orders rows within their\n * group.\n */\n group?: (row: TData) => string | null;\n /**\n * Glide the frame's height when the rows change — a search narrowing,\n * a filter, bones becoming rows. The height follows the content on the\n * page spring instead of jumping; reserve it for tables whose rows\n * change while mounted (same contract as `BasicPage.Card`'s).\n */\n animateHeight?: boolean;\n /** Bones while the rows are on their way — `true` for five, or a count. */\n loading?: boolean | number;\n /** What the body says when there are no rows. */\n empty?: ReactNode;\n ref?: Ref<HTMLDivElement>;\n}\n\n/**\n * A host that sizes itself around tables provides this; each table\n * announces itself while mounted. `BasicPage.Root` runs wide when the\n * page carries one and `width` was left unsaid.\n */\nexport const TableHostContext = createContext<\n ((present: boolean) => void) | null\n>(null);\n\n/** A searchable table's search, published for a header to adopt. */\nexport interface TableSearchApi {\n placeholder: string;\n query: string;\n setQuery: (query: string) => void;\n /** Rows still showing after the current query. */\n matched: number;\n /** Rows in the list, before search. */\n total: number;\n}\n\ninterface TableSearchState {\n api: TableSearchApi | null;\n adopted: boolean;\n}\n\n/**\n * The channel between a searchable table and a `BasicPage.Search` placed\n * in a section header: the table publishes its search (placeholder,\n * query, the setter); a mounted `Search` adopts it, and the table's own\n * box yields. Provided by `BasicPage.Root`; a table outside any page\n * keeps its built-in box. One searchable table per page — a later\n * publisher takes the slot.\n */\nexport interface TableSearchStore {\n get: () => TableSearchState;\n subscribe: (listener: () => void) => () => void;\n publish: (api: TableSearchApi | null) => void;\n adopt: (adopted: boolean) => void;\n}\n\nexport function createTableSearchStore(): TableSearchStore {\n let state: TableSearchState = { api: null, adopted: false };\n const listeners = new Set<() => void>();\n const emit = () => {\n for (const listener of listeners) listener();\n };\n return {\n get: () => state,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n publish(api) {\n state = { ...state, api };\n emit();\n },\n adopt(adopted) {\n state = { ...state, adopted };\n emit();\n },\n };\n}\n\nexport const TableSearchContext = createContext<TableSearchStore | null>(null);\n\nconst subscribeToNothing = () => () => {};\n\nexport interface GhostSearchProps extends Omit<\n ComponentProps<\"input\">,\n \"value\" | \"onChange\" | \"type\" | \"children\"\n> {\n value: string;\n onChange: (value: string) => void;\n}\n\n/**\n * The search's ghost face — a magnifier and a bare field, quiet ink on a\n * text line, no box around it. What a table wears in its own toolbar\n * band; `BasicPage.Search variant=\"ghost\"` is the same face wherever a\n * page wants it. Presentational: hand it the value and the setter.\n */\nexport function GhostSearch({\n value,\n onChange,\n className,\n ...props\n}: GhostSearchProps) {\n return (\n <label className={cn(\"flex min-w-40 flex-1 items-center gap-2\", className)}>\n <IconSearch\n aria-hidden\n className=\"text-pho-description size-4 shrink-0\"\n />\n <input\n type=\"search\"\n value={value}\n onChange={(event) => onChange(event.target.value)}\n className=\"text-pho-primary placeholder:text-pho-description outline-pho-brand w-full min-w-0 rounded-sm bg-transparent text-sm focus-visible:outline-2 focus-visible:outline-offset-2\"\n {...props}\n />\n </label>\n );\n}\n\n/** Same chrome as `BasicPage.Card`, minus its dividers — rows draw their own. */\nconst DATA_TABLE_SURFACE =\n \"rounded-base border border-pho-secondary bg-pho-primary\";\n\nconst DEFAULT_BONES = 5;\n\nfunction defaultRowId(row: unknown, index: number): string {\n const id = (row as { id?: unknown } | null)?.id;\n return typeof id === \"string\" || typeof id === \"number\"\n ? String(id)\n : String(index);\n}\n\nfunction DataTable<TData extends RowData>({\n columns,\n data,\n getRowId,\n rowHref,\n current,\n selected,\n onSelectedChange,\n defaultSort,\n sort,\n onSortChange,\n search,\n toolbar,\n group,\n animateHeight,\n loading,\n empty = \"Nothing here yet.\",\n className,\n ...props\n}: DataTableProps<TData>) {\n const Link = useLinkComponent();\n const announce = useContext(TableHostContext);\n const searching = search === true || typeof search === \"string\";\n const [query, setQuery] = useState(\"\");\n const placeholder = typeof search === \"string\" ? search : \"Search\";\n // A `BasicPage.Search` in a section header adopts the box; the table\n // then renders no toolbar of its own.\n const searchStore = useContext(TableSearchContext);\n const adopted = useSyncExternalStore(\n searchStore?.subscribe ?? subscribeToNothing,\n () => searchStore?.get().adopted ?? false,\n () => false,\n );\n useLayoutEffect(() => {\n if (announce == null) return;\n announce(true);\n return () => announce(false);\n }, [announce]);\n const selecting = selected != null || onSelectedChange != null;\n const [ownSelection, setOwnSelection] = useState<Record<string, true>>({});\n const rowSelection: Record<string, true> =\n selected != null\n ? Object.fromEntries(selected.map((id) => [id, true as const]))\n : ownSelection;\n // Sorting mirrors selection: the component always owns a fallback state,\n // so `defaultSort` + `onSortChange` without `sort` still sorts — a custom\n // `onSortingChange` replaces the table's own updater entirely.\n const [ownSort, setOwnSort] = useState<SortingState>(defaultSort ?? []);\n const sorting: SortingState = sort ?? ownSort;\n const table = useTable<DataTableFeatures, TData>({\n features,\n columns: (selecting\n ? [SELECT_COLUMN as DataTableColumn<TData, any>, ...columns]\n : columns) as Array<DataTableColumn<TData, any>>,\n data: data as Array<TData>,\n getRowId: getRowId ? (row) => getRowId(row) : defaultRowId,\n // Keys only when meant: a present-but-undefined slice of `state` reads\n // as \"controlled, with nobody listening\".\n state: {\n sorting,\n ...(searching ? { globalFilter: query } : {}),\n ...(selecting ? { rowSelection } : {}),\n },\n ...(selecting\n ? {\n enableRowSelection: true,\n onRowSelectionChange: (\n updater:\n | Record<string, true>\n | ((old: Record<string, true>) => Record<string, true>),\n ) => {\n const next =\n typeof updater === \"function\" ? updater(rowSelection) : updater;\n if (selected == null) setOwnSelection(next);\n onSelectedChange?.(Object.keys(next));\n },\n }\n : {}),\n ...(searching\n ? {\n onGlobalFilterChange: setQuery,\n globalFilterFn: \"includesString\" as const,\n }\n : {}),\n onSortingChange: (\n updater: SortingState | ((prev: SortingState) => SortingState),\n ) => {\n const next = typeof updater === \"function\" ? updater(sorting) : updater;\n if (sort == null) setOwnSort(next);\n onSortChange?.(next);\n },\n });\n const rows = table.getRowModel().rows;\n const total = data.length;\n const matched = searching ? rows.length : total;\n useLayoutEffect(() => {\n if (!searching || searchStore == null) return;\n searchStore.publish({ placeholder, query, setQuery, matched, total });\n }, [searching, searchStore, placeholder, query, matched, total]);\n useEffect(() => {\n if (searchStore == null) return;\n return () => searchStore.publish(null);\n }, [searchStore]);\n const leafCount = table.getAllLeafColumns().length;\n const bones = useMemo(\n () =>\n loading\n ? Array.from({ length: loading === true ? DEFAULT_BONES : loading })\n : [],\n [loading],\n );\n\n const renderRow = (row: (typeof rows)[number]) => {\n const href = rowHref?.(row.original);\n const isCurrent = current != null && current === row.id;\n return (\n <tr\n key={row.id}\n className={cn(\n \"relative\",\n href != null &&\n \"hover:bg-pho-primary-hover outline-pho-brand transition-colors has-[a:focus-visible]:outline-2 has-[a:focus-visible]:-outline-offset-2\",\n (isCurrent || row.getIsSelected()) && \"bg-pho-primary-hover\",\n )}\n >\n {row.getAllCells().map((cell, index) => {\n const align = cell.column.columnDef.meta?.align ?? \"start\";\n return (\n <td\n key={cell.id}\n className={cn(\n \"text-pho-primary h-12 px-5 align-middle whitespace-nowrap\",\n align === \"end\" && \"text-right tabular-nums\",\n )}\n >\n {/* The link lives in the first data cell; with selection on,\n cell 0 is the checkbox and must stay outside the anchor. */}\n {index === (selecting ? 1 : 0) && href != null ? (\n // The row's one link, stretched over the row; the\n // ring is the row's (`has-[a:focus-visible]`).\n <Link\n href={href}\n aria-current={isCurrent ? \"page\" : undefined}\n className=\"outline-none after:absolute after:inset-0 after:content-['']\"\n >\n <FlexRender cell={cell} />\n </Link>\n ) : (\n <FlexRender cell={cell} />\n )}\n </td>\n );\n })}\n </tr>\n );\n };\n\n // Partition the (sorted, filtered) rows: the main body, then each\n // labeled group in the order labels first appear in `data`.\n let mainRows = rows;\n let groupSections: Array<[string, typeof rows]> = [];\n if (group != null) {\n mainRows = [];\n const byLabel = new Map<string, typeof rows>();\n const order: string[] = [];\n for (const item of data) {\n const label = group(item);\n if (label != null && !byLabel.has(label)) {\n byLabel.set(label, []);\n order.push(label);\n }\n }\n for (const row of rows) {\n const label = group(row.original);\n if (label == null) {\n mainRows.push(row);\n } else {\n if (!byLabel.has(label)) {\n byLabel.set(label, []);\n order.push(label);\n }\n byLabel.get(label)!.push(row);\n }\n }\n groupSections = order\n .map((label) => [label, byLabel.get(label)!] as [string, typeof rows])\n .filter(([, sectionRows]) => sectionRows.length > 0);\n }\n\n const grid = (\n <table\n className=\"w-full border-collapse text-sm\"\n aria-busy={loading ? true : undefined}\n >\n <thead>\n {table.getHeaderGroups().map((headerGroup) => (\n <tr key={headerGroup.id}>\n {headerGroup.headers.map((header) => {\n const align = header.column.columnDef.meta?.align ?? \"start\";\n const sorted = header.column.getIsSorted();\n const sortable =\n !header.isPlaceholder && header.column.getCanSort();\n return (\n <th\n key={header.id}\n scope=\"col\"\n colSpan={header.colSpan}\n aria-sort={\n sorted === \"asc\"\n ? \"ascending\"\n : sorted === \"desc\"\n ? \"descending\"\n : undefined\n }\n className={cn(\n \"text-pho-description h-10 px-5 text-left align-middle font-normal whitespace-nowrap\",\n align === \"end\" && \"text-right\",\n )}\n >\n {header.isPlaceholder ? null : sortable ? (\n <button\n type=\"button\"\n onClick={header.column.getToggleSortingHandler()}\n className={cn(\n \"group/sort outline-pho-brand hover:text-pho-primary -mx-1 inline-flex items-center gap-1 rounded-sm px-1 transition-colors focus-visible:outline-2\",\n align === \"end\" && \"flex-row-reverse\",\n sorted && \"text-pho-primary\",\n )}\n >\n <FlexRender header={header} />\n <SortIndicator sorted={sorted} />\n </button>\n ) : (\n <FlexRender header={header} />\n )}\n </th>\n );\n })}\n </tr>\n ))}\n </thead>\n <tbody className=\"divide-pho-secondary border-pho-secondary divide-y border-t\">\n {bones.length > 0 ? (\n bones.map((_, index) => (\n <tr key={index}>\n {table.getAllLeafColumns().map((column) => (\n <td key={column.id} className=\"h-12 px-5 align-middle\">\n <Skeleton\n className={cn(\n \"h-3.5 w-24\",\n column.columnDef.meta?.align === \"end\" && \"ml-auto\",\n )}\n />\n </td>\n ))}\n </tr>\n ))\n ) : rows.length === 0 ? (\n <tr>\n <td\n colSpan={leafCount}\n className=\"text-pho-description px-5 py-10 text-center\"\n >\n {searching && query !== \"\" && data.length > 0 ? (\n <>Nothing matches “{query}”.</>\n ) : (\n empty\n )}\n </td>\n </tr>\n ) : (\n <>\n {mainRows.map(renderRow)}\n {groupSections.map(([label, sectionRows]) => (\n <Fragment key={label}>\n <tr data-table-group=\"\">\n <td\n colSpan={leafCount}\n // A tinted band, not spacing: the wash marks the group\n // off, the height stays compact.\n className=\"bg-pho-page text-pho-description h-9 px-5 align-middle text-xs font-medium\"\n >\n {label}\n </td>\n </tr>\n {sectionRows.map(renderRow)}\n </Fragment>\n ))}\n </>\n )}\n </tbody>\n </table>\n );\n\n return (\n <div\n data-data-table=\"\"\n className={cn(\n DATA_TABLE_SURFACE,\n \"scroll-mt-12 contain-inline-size\",\n !animateHeight && \"overflow-x-auto\",\n className,\n )}\n {...props}\n >\n {(toolbar != null || (searching && !adopted)) && (\n // The toolbar band — table chrome, like the header row: the ghost\n // search on the cells' text line (`px-5`), filters at the end.\n // Quiet ink, no boxes in the box; the hairline below is the only\n // border it brings.\n <div className=\"border-pho-secondary flex min-h-11 flex-wrap items-center gap-x-4 gap-y-1 border-b px-5 py-2\">\n {searching && !adopted && (\n <GhostSearch\n value={query}\n onChange={setQuery}\n placeholder={placeholder}\n aria-label={placeholder}\n />\n )}\n {toolbar}\n </div>\n )}\n {animateHeight ? (\n // The height glides on the page spring while the rows inside swap;\n // sideways scrolling moves onto the measured layer so the frame\n // clips height only.\n <AnimatedHeight\n transition={SPRING_ENTRANCE}\n className=\"overflow-x-auto\"\n >\n {grid}\n </AnimatedHeight>\n ) : (\n grid\n )}\n </div>\n );\n}\n\n/**\n * The sort cue beside a sortable header: the two-way glyph at rest (shown\n * on hover and focus, so a header reads as plain text until it matters), a\n * chevron pointing the way once the column is sorted.\n */\nfunction SortIndicator({ sorted }: { sorted: false | \"asc\" | \"desc\" }) {\n return (\n <span\n aria-hidden\n className={cn(\n \"inline-flex shrink-0 [&>svg]:size-3.5\",\n !sorted &&\n \"opacity-0 transition-opacity group-hover/sort:opacity-100 group-focus-visible/sort:opacity-100\",\n )}\n >\n {sorted === \"asc\" ? (\n <IconChevronUp />\n ) : sorted === \"desc\" ? (\n <IconChevronDown />\n ) : (\n <IconSelector />\n )}\n </span>\n );\n}\n\nexport { DataTable, DATA_TABLE_SURFACE };\n"],"x_google_ignoreList":[0,1],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { t as s } from "../chunks/cn-ChIgwEl3.js";
2
+ import { t as o } from "../chunks/cn-ChIgwEl3.js";
3
3
  import { t as a } from "../chunks/resolve-variant-CcD8iG5l.js";
4
4
  import "react";
5
5
  import { jsx as l } from "react/jsx-runtime";
@@ -15,6 +15,10 @@ var p = "inline-flex shrink-0 items-center whitespace-nowrap rounded-full font-m
15
15
  md: {
16
16
  classes: "h-6 gap-1 px-2.5 text-xs leading-none",
17
17
  description: "Medium — 24px; the default"
18
+ },
19
+ lg: {
20
+ classes: "h-7 gap-1 px-3 text-base leading-none",
21
+ description: "Large — 28px; beside a page title (BasicPage.Title trailing)"
18
22
  }
19
23
  }, c = {
20
24
  gray: {
@@ -48,19 +52,19 @@ var p = "inline-flex shrink-0 items-center whitespace-nowrap rounded-full font-m
48
52
  description: "Error — failed, blocked, over limit"
49
53
  }
50
54
  };
51
- function g(r) {
52
- const { color: t = "gray", contrast: n = "high", size: i = "md", className: o } = r ?? {}, e = c[t];
53
- return s(p, a(h, i), n === "low" ? s(d, e?.low) : e?.high, o);
55
+ function g(t) {
56
+ const { color: r = "gray", contrast: i = "high", size: n = "md", className: s } = t ?? {}, e = c[r];
57
+ return o(p, a(h, n), i === "low" ? o(d, e?.low) : e?.high, s);
54
58
  }
55
- function x({ color: r, contrast: t, size: n, className: i, children: o, ...e }) {
59
+ function x({ color: t, contrast: r, size: i, className: n, children: s, ...e }) {
56
60
  return /* @__PURE__ */ l("span", {
57
- className: s(g({
58
- color: r,
59
- contrast: t,
60
- size: n
61
- }), i),
61
+ className: o(g({
62
+ color: t,
63
+ contrast: r,
64
+ size: i
65
+ }), n),
62
66
  ...e,
63
- children: o
67
+ children: s
64
68
  });
65
69
  }
66
70
  x.displayName = "Badge";
@@ -1 +1 @@
1
- {"version":3,"file":"badge.js","names":[],"sources":["../../src/components/badge/badge.tsx"],"sourcesContent":["import { type ComponentProps } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { resolveVariant, type VariantMap } from \"../../utils/resolve-variant\";\n\n/**\n * Design notes (Geist-aligned):\n * - ONE color axis, a tight semantic set (`gray` / `muted` / `brand` /\n * `success` / `warning` / `error`). Untitled UI's `type` triplication\n * (modern / pill / badge-color) is dropped.\n * - TWO contrasts (`high` / `low`, default `high`). High is the solid state\n * fill — the site's \"Most Popular\" pill — pairing each color's ink with\n * flipping on-ink content. Low is the soft accent material: fill and\n * hairline ring derive from `currentColor` at low opacity, so every color\n * shares one recipe.\n * - THREE sizes (`xs` / `sm` / `md`, default `md`). A badge is inherently small.\n * - The richer Untitled features (dot, icon, avatar, count, dismiss button) are\n * deliberately left out — a dismissible or selectable chip is a Tag, not a\n * Badge, and a badge that triggers an action is a Button.\n */\n\nconst BADGE_BASE =\n \"inline-flex shrink-0 items-center whitespace-nowrap rounded-full font-medium\";\n\n/** Low-contrast material — tint and hairline derived from the color's text token. */\nconst LOW_MATERIAL = \"bg-current/10 ring-1 ring-current/20 ring-inset\";\n\n/**\n * Size axis — fixed heights on a 4px step (16 / 20 / 24) so the ladder stays\n * proportional. Horizontal padding and type scale with each step; vertical\n * centering comes from `items-center` on the base (no `py`).\n */\nexport const BADGE_SIZES = {\n xs: {\n classes: \"h-4 gap-0.5 px-1.5 text-[10px] leading-none\",\n description: \"Extra small — 16px; inline next to 14px labels (plan chips)\",\n },\n sm: {\n classes: \"h-5 gap-0.5 px-2 text-xs leading-none\",\n description: \"Small — 20px; dense rows, table cells\",\n },\n md: {\n classes: \"h-6 gap-1 px-2.5 text-xs leading-none\",\n description: \"Medium — 24px; the default\",\n },\n} satisfies VariantMap;\n\n/**\n * Color axis — a closed semantic set with one recipe per contrast. `high`\n * fills with the state ink (text flips with it per mode); `low` tints text\n * only and lets `LOW_MATERIAL` derive fill and ring from it.\n */\nexport const BADGE_COLORS = {\n gray: {\n high: \"bg-pho-inverse text-pho-inverse\",\n low: \"text-pho-secondary\",\n description: \"Neutral — the low-signal default (solid inverse)\",\n },\n muted: {\n high: \"bg-pho-secondary text-pho-primary\",\n low: \"text-pho-description\",\n description: \"Muted — soft solid surface for quieter labels (Free, Shared)\",\n },\n brand: {\n high: \"bg-pho-brand-solid text-pho-on-brand\",\n low: \"text-pho-brand\",\n description: \"Brand — plan tiers, feature flags, highlights\",\n },\n success: {\n high: \"bg-pho-success-ink text-pho-on-brand\",\n low: \"text-pho-success\",\n description: \"Success — healthy, live, complete\",\n },\n warning: {\n high: \"bg-pho-warning-ink text-pho-on-brand\",\n low: \"text-pho-warning\",\n description: \"Warning — needs attention, degraded\",\n },\n error: {\n high: \"bg-pho-error-ink text-pho-on-brand\",\n low: \"text-pho-error\",\n description: \"Error — failed, blocked, over limit\",\n },\n} satisfies Record<string, { high: string; low: string; description: string }>;\n\nexport type BadgeSize = keyof typeof BADGE_SIZES;\nexport type BadgeColor = keyof typeof BADGE_COLORS;\nexport type BadgeContrast = \"high\" | \"low\";\n\n/** Compose the full class string for a badge — exported for reuse/testing. */\nexport function badgeVariants(opts?: {\n color?: BadgeColor;\n contrast?: BadgeContrast;\n size?: BadgeSize;\n className?: string;\n}): string {\n const {\n color = \"gray\",\n contrast = \"high\",\n size = \"md\",\n className,\n } = opts ?? {};\n const def = BADGE_COLORS[color] as\n (typeof BADGE_COLORS)[BadgeColor] | undefined;\n return cn(\n BADGE_BASE,\n resolveVariant(BADGE_SIZES, size),\n contrast === \"low\" ? cn(LOW_MATERIAL, def?.low) : def?.high,\n className,\n );\n}\n\ninterface BadgeBaseProps {\n /** Semantic color. @default \"gray\" */\n color?: BadgeColor;\n /** Solid state fill (`high`) or the soft currentColor tint (`low`). @default \"high\" */\n contrast?: BadgeContrast;\n /** Size variant. @default \"md\" */\n size?: BadgeSize;\n}\n\nexport interface BadgeProps\n extends BadgeBaseProps, Omit<ComponentProps<\"span\">, keyof BadgeBaseProps> {}\n\n/**\n * Badge — a small, non-interactive status label: a plan tier (`Pro`), a state\n * (`Live`, `Failed`), a count. It's a plain `<span>`, so it never takes focus or\n * handles clicks — for a dismissible or selectable chip reach for a `Tag`, and\n * for a standalone action use a `Button`. Let `color` carry the meaning and keep\n * the label to a word or two.\n */\nexport function Badge({\n color,\n contrast,\n size,\n className,\n children,\n ...props\n}: BadgeProps) {\n return (\n <span\n className={cn(badgeVariants({ color, contrast, size }), className)}\n {...props}\n >\n {children}\n </span>\n );\n}\n\nBadge.displayName = \"Badge\";\n"],"mappings":";;;;;AAoBA,IAAM,IACJ,gFAGI,IAAe,mDAOR,IAAc;AAAA,EACzB,IAAI;AAAA,IACF,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA,IAAI;AAAA,IACF,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA,IAAI;AAAA,IACF,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AACF,GAOa,IAAe;AAAA,EAC1B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AACF;AAOA,SAAgB,EAAc,GAKnB;AACT,QAAM,EACJ,OAAA,IAAQ,QACR,UAAA,IAAW,QACX,MAAA,IAAO,MACP,WAAA,EAAA,IACE,KAAQ,CAAC,GACP,IAAM,EAAa,CAAA;AAEzB,SAAO,EACL,GACA,EAAe,GAAa,CAAI,GAChC,MAAa,QAAQ,EAAG,GAAc,GAAK,GAAG,IAAI,GAAK,MACvD,CACF;AACF;AAqBA,SAAgB,EAAM,EACpB,OAAA,GACA,UAAA,GACA,MAAA,GACA,WAAA,GACA,UAAA,GACA,GAAG,EAAA,GACU;AACb,SACE,gBAAA,EAAC,QAAD;AAAA,IACE,WAAW,EAAG,EAAc;AAAA,MAAE,OAAA;AAAA,MAAO,UAAA;AAAA,MAAU,MAAA;AAAA,IAAK,CAAC,GAAG,CAAS;AAAA,IACjE,GAAI;AAAA,IAEH,UAAA;AAAA,EACG,CAAA;AAEV;AAEA,EAAM,cAAc"}
1
+ {"version":3,"file":"badge.js","names":[],"sources":["../../src/components/badge/badge.tsx"],"sourcesContent":["import { type ComponentProps } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { resolveVariant, type VariantMap } from \"../../utils/resolve-variant\";\n\n/**\n * Design notes (Geist-aligned):\n * - ONE color axis, a tight semantic set (`gray` / `muted` / `brand` /\n * `success` / `warning` / `error`). Untitled UI's `type` triplication\n * (modern / pill / badge-color) is dropped.\n * - TWO contrasts (`high` / `low`, default `high`). High is the solid state\n * fill — the site's \"Most Popular\" pill — pairing each color's ink with\n * flipping on-ink content. Low is the soft accent material: fill and\n * hairline ring derive from `currentColor` at low opacity, so every color\n * shares one recipe.\n * - FOUR sizes (`xs` / `sm` / `md` / `lg`, default `md`). A badge is inherently\n * small; `lg` is the one step up, for a pill seated beside a page title.\n * - The richer Untitled features (dot, icon, avatar, count, dismiss button) are\n * deliberately left out — a dismissible or selectable chip is a Tag, not a\n * Badge, and a badge that triggers an action is a Button.\n */\n\nconst BADGE_BASE =\n \"inline-flex shrink-0 items-center whitespace-nowrap rounded-full font-medium\";\n\n/** Low-contrast material — tint and hairline derived from the color's text token. */\nconst LOW_MATERIAL = \"bg-current/10 ring-1 ring-current/20 ring-inset\";\n\n/**\n * Size axis — fixed heights on a 4px step (16 / 20 / 24 / 28) so the ladder\n * stays proportional. Horizontal padding and type scale with each step;\n * vertical centering comes from `items-center` on the base (no `py`).\n */\nexport const BADGE_SIZES = {\n xs: {\n classes: \"h-4 gap-0.5 px-1.5 text-[10px] leading-none\",\n description: \"Extra small — 16px; inline next to 14px labels (plan chips)\",\n },\n sm: {\n classes: \"h-5 gap-0.5 px-2 text-xs leading-none\",\n description: \"Small — 20px; dense rows, table cells\",\n },\n md: {\n classes: \"h-6 gap-1 px-2.5 text-xs leading-none\",\n description: \"Medium — 24px; the default\",\n },\n lg: {\n classes: \"h-7 gap-1 px-3 text-base leading-none\",\n description: \"Large — 28px; beside a page title (BasicPage.Title trailing)\",\n },\n} satisfies VariantMap;\n\n/**\n * Color axis — a closed semantic set with one recipe per contrast. `high`\n * fills with the state ink (text flips with it per mode); `low` tints text\n * only and lets `LOW_MATERIAL` derive fill and ring from it.\n */\nexport const BADGE_COLORS = {\n gray: {\n high: \"bg-pho-inverse text-pho-inverse\",\n low: \"text-pho-secondary\",\n description: \"Neutral — the low-signal default (solid inverse)\",\n },\n muted: {\n high: \"bg-pho-secondary text-pho-primary\",\n low: \"text-pho-description\",\n description: \"Muted — soft solid surface for quieter labels (Free, Shared)\",\n },\n brand: {\n high: \"bg-pho-brand-solid text-pho-on-brand\",\n low: \"text-pho-brand\",\n description: \"Brand — plan tiers, feature flags, highlights\",\n },\n success: {\n high: \"bg-pho-success-ink text-pho-on-brand\",\n low: \"text-pho-success\",\n description: \"Success — healthy, live, complete\",\n },\n warning: {\n high: \"bg-pho-warning-ink text-pho-on-brand\",\n low: \"text-pho-warning\",\n description: \"Warning — needs attention, degraded\",\n },\n error: {\n high: \"bg-pho-error-ink text-pho-on-brand\",\n low: \"text-pho-error\",\n description: \"Error — failed, blocked, over limit\",\n },\n} satisfies Record<string, { high: string; low: string; description: string }>;\n\nexport type BadgeSize = keyof typeof BADGE_SIZES;\nexport type BadgeColor = keyof typeof BADGE_COLORS;\nexport type BadgeContrast = \"high\" | \"low\";\n\n/** Compose the full class string for a badge — exported for reuse/testing. */\nexport function badgeVariants(opts?: {\n color?: BadgeColor;\n contrast?: BadgeContrast;\n size?: BadgeSize;\n className?: string;\n}): string {\n const {\n color = \"gray\",\n contrast = \"high\",\n size = \"md\",\n className,\n } = opts ?? {};\n const def = BADGE_COLORS[color] as\n (typeof BADGE_COLORS)[BadgeColor] | undefined;\n return cn(\n BADGE_BASE,\n resolveVariant(BADGE_SIZES, size),\n contrast === \"low\" ? cn(LOW_MATERIAL, def?.low) : def?.high,\n className,\n );\n}\n\ninterface BadgeBaseProps {\n /** Semantic color. @default \"gray\" */\n color?: BadgeColor;\n /** Solid state fill (`high`) or the soft currentColor tint (`low`). @default \"high\" */\n contrast?: BadgeContrast;\n /** Size variant. @default \"md\" */\n size?: BadgeSize;\n}\n\nexport interface BadgeProps\n extends BadgeBaseProps, Omit<ComponentProps<\"span\">, keyof BadgeBaseProps> {}\n\n/**\n * Badge — a small, non-interactive status label: a plan tier (`Pro`), a state\n * (`Live`, `Failed`), a count. It's a plain `<span>`, so it never takes focus or\n * handles clicks — for a dismissible or selectable chip reach for a `Tag`, and\n * for a standalone action use a `Button`. Let `color` carry the meaning and keep\n * the label to a word or two.\n */\nexport function Badge({\n color,\n contrast,\n size,\n className,\n children,\n ...props\n}: BadgeProps) {\n return (\n <span\n className={cn(badgeVariants({ color, contrast, size }), className)}\n {...props}\n >\n {children}\n </span>\n );\n}\n\nBadge.displayName = \"Badge\";\n"],"mappings":";;;;;AAqBA,IAAM,IACJ,gFAGI,IAAe,mDAOR,IAAc;AAAA,EACzB,IAAI;AAAA,IACF,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA,IAAI;AAAA,IACF,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA,IAAI;AAAA,IACF,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA,IAAI;AAAA,IACF,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AACF,GAOa,IAAe;AAAA,EAC1B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,IACL,aAAa;AAAA,EACf;AACF;AAOA,SAAgB,EAAc,GAKnB;AACT,QAAM,EACJ,OAAA,IAAQ,QACR,UAAA,IAAW,QACX,MAAA,IAAO,MACP,WAAA,EAAA,IACE,KAAQ,CAAC,GACP,IAAM,EAAa,CAAA;AAEzB,SAAO,EACL,GACA,EAAe,GAAa,CAAI,GAChC,MAAa,QAAQ,EAAG,GAAc,GAAK,GAAG,IAAI,GAAK,MACvD,CACF;AACF;AAqBA,SAAgB,EAAM,EACpB,OAAA,GACA,UAAA,GACA,MAAA,GACA,WAAA,GACA,UAAA,GACA,GAAG,EAAA,GACU;AACb,SACE,gBAAA,EAAC,QAAD;AAAA,IACE,WAAW,EAAG,EAAc;AAAA,MAAE,OAAA;AAAA,MAAO,UAAA;AAAA,MAAU,MAAA;AAAA,IAAK,CAAC,GAAG,CAAS;AAAA,IACjE,GAAI;AAAA,IAEH,UAAA;AAAA,EACG,CAAA;AAEV;AAEA,EAAM,cAAc"}
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { n as e, r, t as a } from "../chunks/checkbox-CznlNhDY.js";
2
+ import { n as e, r, t as a } from "../chunks/checkbox-CLPmblNA.js";
3
3
  export {
4
4
  r as Checkbox,
5
5
  a as CheckboxCard,
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { a as e, i as t, n as s, o, r, s as c, t as l } from "../chunks/data-table-Cmz0p-jt.js";
2
+ import { a as e, i as t, n as s, o, r, s as c, t as l } from "../chunks/data-table-CHLJfGkS.js";
3
3
  export {
4
4
  l as DATA_TABLE_SURFACE,
5
5
  s as DataTable,
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-CB8dQ0jX.js";
12
- import { o as Ps } from "./chunks/data-table-Cmz0p-jt.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-CJwG4I83.js";
12
+ import { o as Ps } from "./chunks/data-table-CHLJfGkS.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,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-CB8dQ0jX.js";
3
- import { o as R } from "../chunks/data-table-Cmz0p-jt.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-CJwG4I83.js";
3
+ import { o as R } from "../chunks/data-table-CHLJfGkS.js";
4
4
  export {
5
5
  r as AUTO_SAVE_DELAY_MS,
6
6
  I as BASIC_PAGE_CALLOUT,
@@ -1,8 +1,8 @@
1
1
  import { ComponentProps } from 'react';
2
2
  /**
3
- * Size axis — fixed heights on a 4px step (16 / 20 / 24) so the ladder stays
4
- * proportional. Horizontal padding and type scale with each step; vertical
5
- * centering comes from `items-center` on the base (no `py`).
3
+ * Size axis — fixed heights on a 4px step (16 / 20 / 24 / 28) so the ladder
4
+ * stays proportional. Horizontal padding and type scale with each step;
5
+ * vertical centering comes from `items-center` on the base (no `py`).
6
6
  */
7
7
  export declare const BADGE_SIZES: {
8
8
  xs: {
@@ -17,6 +17,10 @@ export declare const BADGE_SIZES: {
17
17
  classes: string;
18
18
  description: string;
19
19
  };
20
+ lg: {
21
+ classes: string;
22
+ description: string;
23
+ };
20
24
  };
21
25
  /**
22
26
  * Color axis — a closed semantic set with one recipe per contrast. `high`
@@ -162,6 +162,16 @@ export interface BasicPageTitleProps extends ComponentProps<"h1"> {
162
162
  * under reduced motion.
163
163
  */
164
164
  enter?: boolean;
165
+ /**
166
+ * A trailing accessory — a status badge, a word in its own color — at
167
+ * the label's end, centered on its line. It stands outside the word: it
168
+ * neither flies with a morph nor goes into the condensed bar's label,
169
+ * and the label still ellipsizes beside it. When the word arrives in
170
+ * motion — flying in from a back link, or rising on `enter` — the
171
+ * accessory rises into place beside it on the same spring, so the two
172
+ * land together.
173
+ */
174
+ trailing?: ReactNode;
165
175
  ref?: Ref<HTMLHeadingElement>;
166
176
  }
167
177
  /**
@@ -171,7 +181,7 @@ export interface BasicPageTitleProps extends ComponentProps<"h1"> {
171
181
  * `shrink-0` siblings of a `min-w-0 truncate` label inside a `flex min-w-0`
172
182
  * row so they stay visible.
173
183
  */
174
- declare function Title({ id, morphId, enter, className, children, ...props }: BasicPageTitleProps): import("react").JSX.Element;
184
+ declare function Title({ id, morphId, enter, trailing, className, children, ...props }: BasicPageTitleProps): import("react").JSX.Element;
175
185
  export interface BasicPageStackProps extends Omit<ComponentProps<"div">, RevealMotionConflicts> {
176
186
  /**
177
187
  * Play the page entrance on mount (the default). `false` renders the
@@ -339,6 +349,88 @@ declare function CalloutTitle({ id, className, children, ...props }: ComponentPr
339
349
  declare function CalloutDescription({ className, ...props }: ComponentProps<"p"> & {
340
350
  ref?: Ref<HTMLParagraphElement>;
341
351
  }): import("react").JSX.Element;
352
+ export interface BasicPageOnboardProps extends ComponentProps<"ol"> {
353
+ ref?: Ref<HTMLOListElement>;
354
+ }
355
+ /**
356
+ * The service's setup, before the page: an ordered card of `Step`s under
357
+ * the page's `Header`, for a service that needs something done before it
358
+ * can be used — a number chosen, a business registered. Steps are
359
+ * `Onboard`'s direct children, in the order they are done, and the list
360
+ * decides the rest: it numbers them by position and finds the one at
361
+ * hand (the first not `done`). That step is open — its line and its
362
+ * button under the title; the others fold to their title, the done ones
363
+ * with a check and what they came to, the waiting ones muted. A step
364
+ * finishing folds closed as the next opens, on the Accordion's spring.
365
+ * When the last step is done the service is on — render the page in its
366
+ * place.
367
+ */
368
+ declare function Onboard({ className, children, ...props }: BasicPageOnboardProps): import("react").JSX.Element;
369
+ export interface BasicPageStepProps extends Omit<ComponentProps<"li">, "title"> {
370
+ /**
371
+ * Decorative glyph for the step at hand, top-right of its body the way a
372
+ * `Callout`'s is — typically a Tabler outline icon at stroke 1.25. It is
373
+ * aria-hidden (keep the meaning in the title) and folds away with the
374
+ * body: a done or waiting step shows none.
375
+ */
376
+ icon?: ReactNode;
377
+ /**
378
+ * Something to look at beside the step at hand — a code to scan, with
379
+ * its caption — in the corner where the `icon` would be, its top on the
380
+ * title's, holding still as the fold opens beside it. It takes the
381
+ * icon's place when both are given, and folds away with the body.
382
+ */
383
+ aside?: ReactNode;
384
+ /** The title: a verb and its object — "Choose a number". */
385
+ title: ReactNode;
386
+ /**
387
+ * The line under the title while the step is at hand: what it involves,
388
+ * or, while it waits, how long. Folded away with the rest once the step
389
+ * is done or before its turn.
390
+ */
391
+ description?: ReactNode;
392
+ /**
393
+ * Set once the step is done — its ring draws a check, its body folds
394
+ * closed, and the next step opens.
395
+ */
396
+ done?: boolean;
397
+ /**
398
+ * Set while Photon itself is at work on the step — a number being
399
+ * allocated, a machine's task of a minute or less. It stays at hand and
400
+ * open, and its ring spins around the number. Waiting on someone else
401
+ * is `pending` instead.
402
+ */
403
+ working?: boolean;
404
+ /**
405
+ * Set while the step waits on someone else — a carrier's review, a
406
+ * teammate's approval, anything that takes minutes or days. It stays at
407
+ * hand and open, its ring dashed; say how long in the description. A
408
+ * step Photon itself is at work on is `working`, not pending.
409
+ */
410
+ pending?: boolean;
411
+ /**
412
+ * At hand: the step's action — a small `Button` or `ButtonLink` — under
413
+ * its line. Done: what it came to — a `Value` — at the title's end.
414
+ * Waiting its turn: not shown.
415
+ */
416
+ children?: ReactNode;
417
+ ref?: Ref<HTMLLIElement>;
418
+ }
419
+ /**
420
+ * One step of an `Onboard`, left to right: the ring, the words, the
421
+ * corner. The ring and the title share one row and center on each
422
+ * other's midline; while the step is at hand, the line sits flush under
423
+ * the title (as a Header's description sits under its title) with the
424
+ * button below, folded open in the title's own column by `Reveal` — the
425
+ * page spring, the `Footer` grammar: never clipped, the button whole
426
+ * from the first frame, fading in as the space opens. The corner holds
427
+ * the icon at hand and what the step came to once done. The step at hand sits on the page's ground, the way a `Callout`
428
+ * does; the others stay on the card's white. The title and the line wear the
429
+ * section's type — `SectionTitle` over `Description`, both
430
+ * `text-display-xs`. Its standing comes from the list; on its own it is
431
+ * the step at hand.
432
+ */
433
+ declare function Step({ icon, aside, title, description, done, working, pending, className, children, ...props }: BasicPageStepProps): import("react").JSX.Element;
342
434
  export interface BasicPageRowProps extends Omit<ComponentProps<"div">, "title" | RevealMotionConflicts> {
343
435
  /** Left-side field name — plain text, or compose with a trailing hint. */
344
436
  label: ReactNode;
@@ -525,9 +617,10 @@ export interface BasicPageRevealProps extends Omit<ComponentProps<"div">, Reveal
525
617
  * Which gap the presence variant compensates while collapsing —
526
618
  * `"stack"` for a Stack block (`gap-10`), `"fields"` for a field in a
527
619
  * `Fields` sheet (`gap-5`), `"block"` for an element
528
- * inside a block (`gap-3`). @default "stack"
620
+ * inside a block (`gap-3`), `"none"` for a region with no gap around
621
+ * it (a grid row of its own). @default "stack"
529
622
  */
530
- gap?: "stack" | "block" | "fields";
623
+ gap?: "stack" | "block" | "fields" | "none";
531
624
  /**
532
625
  * Set when this wraps multiple `Card` rows — the extra element Reveal
533
626
  * needs for `AnimatePresence` sits between them and the card's own
@@ -828,6 +921,8 @@ export declare const BasicPage: {
828
921
  Callout: typeof Callout;
829
922
  CalloutTitle: typeof CalloutTitle;
830
923
  CalloutDescription: typeof CalloutDescription;
924
+ Onboard: typeof Onboard;
925
+ Step: typeof Step;
831
926
  Row: typeof Row;
832
927
  RowLink: typeof RowLink;
833
928
  Value: typeof Value;
@@ -1,2 +1,2 @@
1
- export { BasicPage, formToasts, AUTO_SAVE_DELAY_MS, useCondense, useAutoSave, type BasicPageCondense, BASIC_PAGE_CARD, CONDENSE_BAR_ENTER, CONDENSE_BAR_TRAVEL, CONDENSE_BAR_RISE_PX, BASIC_PAGE_CALLOUT, BASIC_PAGE_ROW, BASIC_PAGE_ITEM, BASIC_PAGE_ITEM_PANEL, BASIC_PAGE_ITEM_PANEL_DIVIDED, type BasicPageRootProps, type BasicPageAsideProps, type BasicPageRowProps, type BasicPageRowLinkProps, type BasicPageBackProps, type BasicPageBreadcrumbsProps, type BasicPageCrumbProps, type BasicPageFooterProps, type BasicPageFormProps, type BasicPageFormErrors, type BasicPageItemPanelProps, } from './basic-page';
1
+ export { BasicPage, formToasts, AUTO_SAVE_DELAY_MS, useCondense, useAutoSave, type BasicPageCondense, BASIC_PAGE_CARD, CONDENSE_BAR_ENTER, CONDENSE_BAR_TRAVEL, CONDENSE_BAR_RISE_PX, BASIC_PAGE_CALLOUT, BASIC_PAGE_ROW, BASIC_PAGE_ITEM, BASIC_PAGE_ITEM_PANEL, BASIC_PAGE_ITEM_PANEL_DIVIDED, type BasicPageRootProps, type BasicPageAsideProps, type BasicPageRowProps, type BasicPageRowLinkProps, type BasicPageBackProps, type BasicPageBreadcrumbsProps, type BasicPageCrumbProps, type BasicPageFooterProps, type BasicPageFormProps, type BasicPageFormErrors, type BasicPageItemPanelProps, type BasicPageOnboardProps, type BasicPageStepProps, } from './basic-page';
2
2
  export { createColumns, type DataTableColumn, type DataTableColumnMeta, type DataTableProps, type SortingState, } from '../../components/data-table';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@photon-ai/pho-ui",
3
- "version": "2.10.0",
3
+ "version": "2.12.0",
4
4
  "description": "Pho Design System — Photon's React component library, built on Base UI",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {