@photon-ai/pho-ui 4.12.0 → 4.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{basic-page-CJ11ekjH.js → basic-page-6dDfY33L.js} +717 -737
- package/dist/chunks/basic-page-6dDfY33L.js.map +1 -0
- package/dist/chunks/box-search-C9EVZgcb.js +56 -0
- package/dist/chunks/box-search-C9EVZgcb.js.map +1 -0
- package/dist/chunks/{chat-CU3XVm99.js → chat-Bu8UyIbT.js} +206 -204
- package/dist/chunks/chat-Bu8UyIbT.js.map +1 -0
- package/dist/chunks/chrome-Ds7WykBJ.js +14 -0
- package/dist/chunks/chrome-Ds7WykBJ.js.map +1 -0
- package/dist/chunks/{data-table-Ce_c6T73.js → data-table-hzZb7Otx.js} +56 -54
- package/dist/chunks/{data-table-Ce_c6T73.js.map → data-table-hzZb7Otx.js.map} +1 -1
- package/dist/chunks/{filter-bar-CMkvhvgL.js → filter-bar-C4sPEjw1.js} +62 -65
- package/dist/chunks/filter-bar-C4sPEjw1.js.map +1 -0
- package/dist/chunks/ghost-search-8PzP73zS.js +50 -0
- package/dist/chunks/ghost-search-8PzP73zS.js.map +1 -0
- package/dist/chunks/{trace-D1fOFzJt.js → trace-DyjsBY-S.js} +15 -13
- package/dist/chunks/{trace-D1fOFzJt.js.map → trace-DyjsBY-S.js.map} +1 -1
- package/dist/components/chat.js +1 -1
- package/dist/components/data-table.js +4 -3
- package/dist/components/filter-bar.js +1 -1
- package/dist/components/scope.js +418 -0
- package/dist/components/scope.js.map +1 -0
- package/dist/components/trace.js +1 -1
- package/dist/index.js +176 -175
- package/dist/motion.js +61 -36
- package/dist/motion.js.map +1 -1
- package/dist/sections/basic-page.js +2 -2
- package/dist/src/components/chat/chat.d.ts +7 -4
- package/dist/src/components/input-group/box-search.d.ts +29 -0
- package/dist/src/components/scope/index.d.ts +1 -0
- package/dist/src/components/scope/scope.d.ts +98 -0
- package/dist/src/motion/index.d.ts +52 -0
- package/dist/src/utils/ghost-search.d.ts +19 -3
- package/package.json +5 -1
- package/dist/chunks/basic-page-CJ11ekjH.js.map +0 -1
- package/dist/chunks/chat-CU3XVm99.js.map +0 -1
- package/dist/chunks/filter-bar-CMkvhvgL.js.map +0 -1
- package/dist/chunks/ghost-search-CNVs9k1o.js +0 -34
- package/dist/chunks/ghost-search-CNVs9k1o.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-table-Ce_c6T73.js","names":[],"sources":["../../../../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\": \"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 KeyboardEvent,\n type MouseEvent,\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 IconSelector,\n} from \"@tabler/icons-react\";\nimport { Skeleton } from \"../skeleton\";\nimport {\n DATA_TABLE_BODY,\n DATA_TABLE_HEADER_CELL,\n DATA_TABLE_SURFACE,\n DATA_TABLE_TEXT,\n} from \"./chrome\";\nimport { Checkbox } from \"../checkbox\";\nimport { SPRING_ENTRANCE } from \"../../motion\";\nimport { AnimatedHeight } from \"../../utils/animated-height\";\nimport { cn } from \"../../utils/cn\";\nimport { resolveVariant, type VariantMap } from \"../../utils/resolve-variant\";\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 * Left with no width of its own, the frame takes its container's\n * (`w-full`); a shrink-to-fit host (a flex row) would collapse it.\n * `layout=\"fixed\"` is the other way out: columns size from the frame,\n * and a long cell truncates instead of stretching the table.\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 * Extra classes on every `th` and `td` in this column. With\n * `layout=\"fixed\"`, size a short column here (`w-24`) and leave the\n * flexible ones unsaid — they share what remains. Prefer this over\n * `nth-child` width hacks on the table's `className`.\n */\n className?: string;\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/** Body row height — the header keeps its own `h-10` on every size. */\nexport const DATA_TABLE_SIZES = {\n sm: {\n classes: \"h-10\",\n description: \"Small — 40px at 13px; the header's height, for dense lists\",\n },\n base: {\n classes: \"h-12\",\n description: \"Base — 48px at 14px; one line of body type\",\n },\n lg: {\n classes: \"h-14\",\n description:\n \"Large — 56px at 16px; two-line cells (a number and a caption)\",\n },\n} satisfies VariantMap;\n\nexport type DataTableSize = keyof typeof DATA_TABLE_SIZES;\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 <Checkbox\n size=\"sm\"\n aria-label=\"Select row\"\n checked={row.getIsSelected()}\n onCheckedChange={(checked) => row.toggleSelected(checked === true)}\n />\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 /**\n * A click on the row (not on a link, button, or other control). The\n * table still activates `rowHref` unless the handler calls\n * `preventDefault`.\n */\n onRowClick?: (\n row: TData,\n event: MouseEvent<HTMLTableRowElement> | KeyboardEvent<HTMLTableRowElement>,\n ) => void;\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 /**\n * How columns take width. `auto` (the default) sizes them from their\n * content — a long cell grows the table and the frame scrolls sideways.\n * `fixed` sizes them from the table's width (`table-layout: fixed`):\n * short columns take a width hint on `meta.className`, the rest share\n * what remains, and cells get `min-w-0` so an inner `truncate` can\n * shrink. `width: 0` (`w-0`) is not a content-size hint here — fixed\n * layout treats it as a real zero — so give a short column a real\n * width (`w-24`) rather than the auto-layout shrink-wrap trick.\n */\n layout?: \"auto\" | \"fixed\";\n /**\n * Body row height and the type that comes with it. `base` (the default)\n * is 48px rows at 14px — one line of body type. `sm` is 40px at 13px,\n * the header's own height, for a dense list scanned more than read.\n * `lg` is 56px at 16px, for a two-line cell (a number and a caption)\n * that would crowd `base`. The header row keeps its 40px on every size\n * and follows the type.\n */\n size?: DataTableSize;\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 { GhostSearch, type GhostSearchProps } from \"../../utils/ghost-search\";\nimport { GhostSearch } from \"../../utils/ghost-search\";\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\n/** Controls keep their own click; the row only navigates from empty surface. */\nconst ROW_NAV_IGNORE = \"a, button, input, textarea, select, label\";\n\nfunction shouldIgnoreRowNav(target: EventTarget | null): boolean {\n return target instanceof Element && target.closest(ROW_NAV_IGNORE) != null;\n}\n\nfunction activateRowHref(event: MouseEvent<HTMLTableRowElement>, href: string) {\n const newTab =\n event.button === 1 || event.metaKey || event.ctrlKey || event.shiftKey;\n if (event.button === 1) event.preventDefault();\n if (event.button !== 0 && event.button !== 1) return;\n if (newTab) {\n window.open(href, \"_blank\", \"noopener,noreferrer\");\n return;\n }\n event.currentTarget.querySelector<HTMLAnchorElement>(\"a[href]\")?.click();\n}\n\nfunction DataTable<TData extends RowData>({\n columns,\n data,\n getRowId,\n rowHref,\n onRowClick,\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 layout = \"auto\",\n size = \"base\",\n className,\n ...props\n}: DataTableProps<TData>) {\n const fixed = layout === \"fixed\";\n const rowSize = resolveVariant(DATA_TABLE_SIZES, size);\n const rowText = DATA_TABLE_TEXT[size] ?? DATA_TABLE_TEXT.base;\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 const navigable = href != null || onRowClick != null;\n const onActivate = (event: MouseEvent<HTMLTableRowElement>) => {\n if (event.button !== 0 && event.button !== 1) return;\n if (event.defaultPrevented || shouldIgnoreRowNav(event.target)) return;\n onRowClick?.(row.original, event);\n if (event.defaultPrevented || href == null) return;\n activateRowHref(event, href);\n };\n const onRowKeyDown = (event: KeyboardEvent<HTMLTableRowElement>) => {\n if (href != null) return;\n if (event.key !== \"Enter\" && event.key !== \" \") return;\n if (event.defaultPrevented || shouldIgnoreRowNav(event.target)) return;\n event.preventDefault();\n onRowClick?.(row.original, event);\n };\n return (\n <tr\n key={row.id}\n className={cn(\n navigable &&\n \"hover:bg-pho-primary-hover outline-pho-brand cursor-pointer 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 tabIndex={navigable && href == null ? 0 : undefined}\n onClick={navigable ? onActivate : undefined}\n onAuxClick={navigable ? onActivate : undefined}\n onKeyDown={navigable && href == null ? onRowKeyDown : undefined}\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 px-5 align-middle whitespace-nowrap\",\n rowSize,\n fixed && \"min-w-0\",\n align === \"end\" && \"text-right tabular-nums\",\n cell.column.columnDef.meta?.className,\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 // One real link for keyboard and right-click; the row\n // click handler covers the rest of the surface.\n <Link\n href={href}\n aria-current={isCurrent ? \"page\" : undefined}\n className=\"outline-none\"\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={cn(\"w-full border-collapse\", rowText, fixed && \"table-fixed\")}\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 DATA_TABLE_HEADER_CELL,\n \"px-5\",\n fixed && \"min-w-0\",\n align === \"end\" && \"text-right\",\n header.column.columnDef.meta?.className,\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={DATA_TABLE_BODY}>\n {bones.length > 0 ? (\n bones.map((_, index) => (\n <tr key={index}>\n {table.getAllLeafColumns().map((column) => (\n <td\n key={column.id}\n className={cn(\n \"px-5 align-middle\",\n rowSize,\n fixed && \"min-w-0\",\n column.columnDef.meta?.className,\n )}\n >\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 className={cn(\n DATA_TABLE_SURFACE,\n \"w-full scroll-mt-12 contain-inline-size\",\n !animateHeight && \"overflow-x-auto\",\n className,\n )}\n {...props}\n data-data-table=\"\"\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],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"data-table-hzZb7Otx.js","names":[],"sources":["../../../../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\": \"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 KeyboardEvent,\n type MouseEvent,\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 IconSelector,\n} from \"@tabler/icons-react\";\nimport { Skeleton } from \"../skeleton\";\nimport {\n DATA_TABLE_BODY,\n DATA_TABLE_HEADER_CELL,\n DATA_TABLE_SURFACE,\n DATA_TABLE_TEXT,\n} from \"./chrome\";\nimport { Checkbox } from \"../checkbox\";\nimport { SPRING_ENTRANCE } from \"../../motion\";\nimport { AnimatedHeight } from \"../../utils/animated-height\";\nimport { cn } from \"../../utils/cn\";\nimport { resolveVariant, type VariantMap } from \"../../utils/resolve-variant\";\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 * Left with no width of its own, the frame takes its container's\n * (`w-full`); a shrink-to-fit host (a flex row) would collapse it.\n * `layout=\"fixed\"` is the other way out: columns size from the frame,\n * and a long cell truncates instead of stretching the table.\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 * Extra classes on every `th` and `td` in this column. With\n * `layout=\"fixed\"`, size a short column here (`w-24`) and leave the\n * flexible ones unsaid — they share what remains. Prefer this over\n * `nth-child` width hacks on the table's `className`.\n */\n className?: string;\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/** Body row height — the header keeps its own `h-10` on every size. */\nexport const DATA_TABLE_SIZES = {\n sm: {\n classes: \"h-10\",\n description: \"Small — 40px at 13px; the header's height, for dense lists\",\n },\n base: {\n classes: \"h-12\",\n description: \"Base — 48px at 14px; one line of body type\",\n },\n lg: {\n classes: \"h-14\",\n description:\n \"Large — 56px at 16px; two-line cells (a number and a caption)\",\n },\n} satisfies VariantMap;\n\nexport type DataTableSize = keyof typeof DATA_TABLE_SIZES;\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 <Checkbox\n size=\"sm\"\n aria-label=\"Select row\"\n checked={row.getIsSelected()}\n onCheckedChange={(checked) => row.toggleSelected(checked === true)}\n />\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 /**\n * A click on the row (not on a link, button, or other control). The\n * table still activates `rowHref` unless the handler calls\n * `preventDefault`.\n */\n onRowClick?: (\n row: TData,\n event: MouseEvent<HTMLTableRowElement> | KeyboardEvent<HTMLTableRowElement>,\n ) => void;\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 /**\n * How columns take width. `auto` (the default) sizes them from their\n * content — a long cell grows the table and the frame scrolls sideways.\n * `fixed` sizes them from the table's width (`table-layout: fixed`):\n * short columns take a width hint on `meta.className`, the rest share\n * what remains, and cells get `min-w-0` so an inner `truncate` can\n * shrink. `width: 0` (`w-0`) is not a content-size hint here — fixed\n * layout treats it as a real zero — so give a short column a real\n * width (`w-24`) rather than the auto-layout shrink-wrap trick.\n */\n layout?: \"auto\" | \"fixed\";\n /**\n * Body row height and the type that comes with it. `base` (the default)\n * is 48px rows at 14px — one line of body type. `sm` is 40px at 13px,\n * the header's own height, for a dense list scanned more than read.\n * `lg` is 56px at 16px, for a two-line cell (a number and a caption)\n * that would crowd `base`. The header row keeps its 40px on every size\n * and follows the type.\n */\n size?: DataTableSize;\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 { GhostSearch, type GhostSearchProps } from \"../../utils/ghost-search\";\nimport { GhostSearch } from \"../../utils/ghost-search\";\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\n/** Controls keep their own click; the row only navigates from empty surface. */\nconst ROW_NAV_IGNORE = \"a, button, input, textarea, select, label\";\n\nfunction shouldIgnoreRowNav(target: EventTarget | null): boolean {\n return target instanceof Element && target.closest(ROW_NAV_IGNORE) != null;\n}\n\nfunction activateRowHref(event: MouseEvent<HTMLTableRowElement>, href: string) {\n const newTab =\n event.button === 1 || event.metaKey || event.ctrlKey || event.shiftKey;\n if (event.button === 1) event.preventDefault();\n if (event.button !== 0 && event.button !== 1) return;\n if (newTab) {\n window.open(href, \"_blank\", \"noopener,noreferrer\");\n return;\n }\n event.currentTarget.querySelector<HTMLAnchorElement>(\"a[href]\")?.click();\n}\n\nfunction DataTable<TData extends RowData>({\n columns,\n data,\n getRowId,\n rowHref,\n onRowClick,\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 layout = \"auto\",\n size = \"base\",\n className,\n ...props\n}: DataTableProps<TData>) {\n const fixed = layout === \"fixed\";\n const rowSize = resolveVariant(DATA_TABLE_SIZES, size);\n const rowText = DATA_TABLE_TEXT[size] ?? DATA_TABLE_TEXT.base;\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 const navigable = href != null || onRowClick != null;\n const onActivate = (event: MouseEvent<HTMLTableRowElement>) => {\n if (event.button !== 0 && event.button !== 1) return;\n if (event.defaultPrevented || shouldIgnoreRowNav(event.target)) return;\n onRowClick?.(row.original, event);\n if (event.defaultPrevented || href == null) return;\n activateRowHref(event, href);\n };\n const onRowKeyDown = (event: KeyboardEvent<HTMLTableRowElement>) => {\n if (href != null) return;\n if (event.key !== \"Enter\" && event.key !== \" \") return;\n if (event.defaultPrevented || shouldIgnoreRowNav(event.target)) return;\n event.preventDefault();\n onRowClick?.(row.original, event);\n };\n return (\n <tr\n key={row.id}\n className={cn(\n navigable &&\n \"hover:bg-pho-primary-hover outline-pho-brand cursor-pointer 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 tabIndex={navigable && href == null ? 0 : undefined}\n onClick={navigable ? onActivate : undefined}\n onAuxClick={navigable ? onActivate : undefined}\n onKeyDown={navigable && href == null ? onRowKeyDown : undefined}\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 px-5 align-middle whitespace-nowrap\",\n rowSize,\n fixed && \"min-w-0\",\n align === \"end\" && \"text-right tabular-nums\",\n cell.column.columnDef.meta?.className,\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 // One real link for keyboard and right-click; the row\n // click handler covers the rest of the surface.\n <Link\n href={href}\n aria-current={isCurrent ? \"page\" : undefined}\n className=\"outline-none\"\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={cn(\"w-full border-collapse\", rowText, fixed && \"table-fixed\")}\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 DATA_TABLE_HEADER_CELL,\n \"px-5\",\n fixed && \"min-w-0\",\n align === \"end\" && \"text-right\",\n header.column.columnDef.meta?.className,\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={DATA_TABLE_BODY}>\n {bones.length > 0 ? (\n bones.map((_, index) => (\n <tr key={index}>\n {table.getAllLeafColumns().map((column) => (\n <td\n key={column.id}\n className={cn(\n \"px-5 align-middle\",\n rowSize,\n fixed && \"min-w-0\",\n column.columnDef.meta?.className,\n )}\n >\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 className={cn(\n DATA_TABLE_SURFACE,\n \"w-full scroll-mt-12 contain-inline-size\",\n !animateHeight && \"overflow-x-auto\",\n className,\n )}\n {...props}\n data-data-table=\"\"\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 onClear={() => 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],"mappings":"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"}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { t as y } from "./cn-ChIgwEl3.js";
|
|
3
|
-
import { a as
|
|
4
|
-
import { t as
|
|
3
|
+
import { a as T } from "./button-C9VqBBIr.js";
|
|
4
|
+
import { t as A } from "./createReactComponent-BqCmnqfX.js";
|
|
5
5
|
import { t as S } from "./IconCheck-D2WuculQ.js";
|
|
6
|
-
import { t as
|
|
6
|
+
import { t as P } from "./IconSearch-BKAHkLyX.js";
|
|
7
7
|
import { t as $ } from "./IconX-pZ9vrtSr.js";
|
|
8
8
|
import { t as g } from "./popover-BhBJO346.js";
|
|
9
|
-
import { t as
|
|
9
|
+
import { t as D } from "./box-search-C9EVZgcb.js";
|
|
10
10
|
import { t as d } from "./command-D5g49VqR.js";
|
|
11
|
-
import { useRef as
|
|
11
|
+
import { useRef as E, useState as I } from "react";
|
|
12
12
|
import { jsx as a, jsxs as p } from "react/jsx-runtime";
|
|
13
|
-
var
|
|
13
|
+
var R = [["path", {
|
|
14
14
|
d: "M12 5l0 14",
|
|
15
15
|
key: "svg-0"
|
|
16
16
|
}], ["path", {
|
|
17
17
|
d: "M5 12l14 0",
|
|
18
18
|
key: "svg-1"
|
|
19
|
-
}]], F =
|
|
19
|
+
}]], F = A("outline", "plus", "Plus", R), O = [
|
|
20
20
|
{
|
|
21
21
|
label: "Last hour",
|
|
22
22
|
hours: 1
|
|
@@ -33,41 +33,41 @@ var E = [["path", {
|
|
|
33
33
|
label: "Last 30 days",
|
|
34
34
|
hours: 720
|
|
35
35
|
}
|
|
36
|
-
],
|
|
37
|
-
function
|
|
36
|
+
], _ = 8;
|
|
37
|
+
function w(e) {
|
|
38
38
|
return e == null ? !1 : typeof e == "string" ? e !== "" : Array.isArray(e) ? e.length > 0 : e.from != null || e.to != null;
|
|
39
39
|
}
|
|
40
|
-
var
|
|
40
|
+
var L = /^\d{4}-\d{2}-\d{2}$/;
|
|
41
41
|
function x(e) {
|
|
42
|
-
const r =
|
|
42
|
+
const r = L.test(e), n = new Date(r ? `${e}T00:00:00Z` : e);
|
|
43
43
|
return Number.isNaN(n.getTime()) ? e : new Intl.DateTimeFormat("en-US", {
|
|
44
44
|
month: "short",
|
|
45
45
|
day: "numeric",
|
|
46
46
|
...r ? { timeZone: "UTC" } : {}
|
|
47
47
|
}).format(n);
|
|
48
48
|
}
|
|
49
|
-
function
|
|
50
|
-
if (!
|
|
49
|
+
function z(e, r) {
|
|
50
|
+
if (!w(r) || r == null) return "";
|
|
51
51
|
if (e.type === "date-range") {
|
|
52
52
|
const l = r;
|
|
53
53
|
return l.preset ? l.preset : l.from && l.to ? `${x(l.from)} to ${x(l.to)}` : l.from ? `From ${x(l.from)}` : l.to ? `Until ${x(l.to)}` : "";
|
|
54
54
|
}
|
|
55
|
-
const n = (l) => e.options.find((
|
|
55
|
+
const n = (l) => e.options.find((i) => i.value === l)?.label ?? l;
|
|
56
56
|
return Array.isArray(r) ? r.map(n).join(", ") : typeof r == "string" ? n(r) : "";
|
|
57
57
|
}
|
|
58
|
-
var
|
|
58
|
+
var k = "outline-pho-brand flex h-8 min-w-0 cursor-pointer items-center gap-1.5 rounded-full text-base transition-[color,background-color] duration-100 ease-linear focus-visible:outline-2 focus-visible:-outline-offset-2 [&>svg]:size-4 [&>svg]:shrink-0";
|
|
59
59
|
function B({ definition: e, value: r, onChange: n }) {
|
|
60
|
-
const [l,
|
|
60
|
+
const [l, i] = I(!1), u = E(null), c = w(r), t = z(e, r);
|
|
61
61
|
return /* @__PURE__ */ p(g.Root, {
|
|
62
62
|
open: l,
|
|
63
|
-
onOpenChange:
|
|
63
|
+
onOpenChange: i,
|
|
64
64
|
children: [/* @__PURE__ */ p("span", {
|
|
65
65
|
"data-filter-chip": e.key,
|
|
66
66
|
"data-set": c || void 0,
|
|
67
67
|
className: y("ring-pho-primary inline-flex max-w-full shrink-0 items-stretch rounded-full ring-1 ring-inset", c ? "bg-pho-secondary" : "bg-transparent"),
|
|
68
68
|
children: [/* @__PURE__ */ p(g.Trigger, {
|
|
69
69
|
"aria-label": c ? void 0 : `${e.label} filter`,
|
|
70
|
-
className: y(
|
|
70
|
+
className: y(k, "hover:bg-pho-ghost-hover", c ? "pr-1.5 pl-3" : "pr-3 pl-2.5", c ? "rounded-r-none" : ""),
|
|
71
71
|
children: [c ? null : /* @__PURE__ */ a(F, { "aria-hidden": !0 }), c ? /* @__PURE__ */ p("span", {
|
|
72
72
|
className: "flex min-w-0 items-baseline gap-1",
|
|
73
73
|
children: [/* @__PURE__ */ p("span", {
|
|
@@ -85,7 +85,7 @@ function B({ definition: e, value: r, onChange: n }) {
|
|
|
85
85
|
type: "button",
|
|
86
86
|
"aria-label": `Clear ${e.label.toLowerCase()} filter`,
|
|
87
87
|
onClick: () => n(void 0),
|
|
88
|
-
className: y(
|
|
88
|
+
className: y(k, "text-pho-secondary hover:bg-pho-ghost-hover hover:text-pho-secondary-hover rounded-l-none pr-2.5 pl-1 [&>svg]:size-3.5"),
|
|
89
89
|
children: /* @__PURE__ */ a($, { "aria-hidden": !0 })
|
|
90
90
|
}) : null]
|
|
91
91
|
}), /* @__PURE__ */ a(g.Portal, { children: /* @__PURE__ */ a(g.Positioner, {
|
|
@@ -93,29 +93,29 @@ function B({ definition: e, value: r, onChange: n }) {
|
|
|
93
93
|
sideOffset: 6,
|
|
94
94
|
children: /* @__PURE__ */ a(g.Popup, {
|
|
95
95
|
"aria-label": `${e.label} filter`,
|
|
96
|
-
initialFocus:
|
|
96
|
+
initialFocus: u,
|
|
97
97
|
className: y("w-64 p-0", e.type === "date-range" && "w-72"),
|
|
98
|
-
children: e.type === "date-range" ? /* @__PURE__ */ a(
|
|
98
|
+
children: e.type === "date-range" ? /* @__PURE__ */ a(j, {
|
|
99
99
|
definition: e,
|
|
100
100
|
value: r,
|
|
101
|
-
focusRef:
|
|
101
|
+
focusRef: u,
|
|
102
102
|
onChange: (m) => {
|
|
103
|
-
n(m), m?.preset &&
|
|
103
|
+
n(m), m?.preset && i(!1);
|
|
104
104
|
}
|
|
105
|
-
}) : /* @__PURE__ */ a(
|
|
105
|
+
}) : /* @__PURE__ */ a(U, {
|
|
106
106
|
definition: e,
|
|
107
107
|
value: r,
|
|
108
|
-
focusRef:
|
|
108
|
+
focusRef: u,
|
|
109
109
|
onChange: (m) => {
|
|
110
|
-
n(m), e.multiple ||
|
|
110
|
+
n(m), e.multiple || i(!1);
|
|
111
111
|
}
|
|
112
112
|
})
|
|
113
113
|
})
|
|
114
114
|
}) })]
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
|
-
function
|
|
118
|
-
const [
|
|
117
|
+
function U({ definition: e, value: r, focusRef: n, onChange: l }) {
|
|
118
|
+
const [i, u] = I(""), c = e.searchable ?? e.options.length > _, t = new Set(r == null ? [] : Array.isArray(r) ? r : [r]), m = i.trim().toLowerCase(), f = m ? e.options.filter((s) => s.label.toLowerCase().includes(m) || s.value.toLowerCase().includes(m)) : e.options, v = (s) => {
|
|
119
119
|
if (e.multiple) {
|
|
120
120
|
const b = new Set(t);
|
|
121
121
|
b.has(s.value) ? b.delete(s.value) : b.add(s.value);
|
|
@@ -131,15 +131,15 @@ function G({ definition: e, value: r, focusRef: n, onChange: l }) {
|
|
|
131
131
|
c || (n.current = s);
|
|
132
132
|
},
|
|
133
133
|
className: "rounded-base",
|
|
134
|
-
children: [c ? /* @__PURE__ */ p(d.InputGroup, { children: [/* @__PURE__ */ a(
|
|
134
|
+
children: [c ? /* @__PURE__ */ p(d.InputGroup, { children: [/* @__PURE__ */ a(P, {
|
|
135
135
|
"aria-hidden": !0,
|
|
136
136
|
className: "text-pho-description pointer-events-none absolute left-3 size-4"
|
|
137
137
|
}), /* @__PURE__ */ a(d.Input, {
|
|
138
138
|
ref: (s) => {
|
|
139
139
|
n.current = s;
|
|
140
140
|
},
|
|
141
|
-
value:
|
|
142
|
-
onValueChange:
|
|
141
|
+
value: i,
|
|
142
|
+
onValueChange: u,
|
|
143
143
|
placeholder: e.searchPlaceholder ?? "Search",
|
|
144
144
|
"aria-label": `Search ${e.label.toLowerCase()} options`,
|
|
145
145
|
className: "py-2 pr-3 pl-9 text-base"
|
|
@@ -153,12 +153,12 @@ function G({ definition: e, value: r, focusRef: n, onChange: l }) {
|
|
|
153
153
|
}, s.value))] })]
|
|
154
154
|
});
|
|
155
155
|
}
|
|
156
|
-
var
|
|
157
|
-
function
|
|
158
|
-
const
|
|
156
|
+
var C = "bg-pho-primary ring-pho-primary text-pho-primary focus-visible:ring-pho-brand w-full min-w-0 rounded-sm px-2 py-1 text-base ring-1 transition-shadow duration-100 ease-linear outline-none ring-inset focus-visible:ring-2";
|
|
157
|
+
function j({ definition: e, value: r, focusRef: n, onChange: l }) {
|
|
158
|
+
const i = e.presets ?? O, u = (t) => t == null ? "" : L.test(t) ? t : t.slice(0, 10), c = (t, m) => {
|
|
159
159
|
const f = {
|
|
160
|
-
from:
|
|
161
|
-
to:
|
|
160
|
+
from: u(r?.from) || null,
|
|
161
|
+
to: u(r?.to) || null,
|
|
162
162
|
[t]: m || null
|
|
163
163
|
};
|
|
164
164
|
l(f.from == null && f.to == null ? void 0 : f);
|
|
@@ -171,7 +171,7 @@ function U({ definition: e, value: r, focusRef: n, onChange: l }) {
|
|
|
171
171
|
n.current = t;
|
|
172
172
|
},
|
|
173
173
|
className: "rounded-t-[inherit]",
|
|
174
|
-
children: /* @__PURE__ */ a(d.List, { children:
|
|
174
|
+
children: /* @__PURE__ */ a(d.List, { children: i.map((t) => /* @__PURE__ */ a(d.Item, {
|
|
175
175
|
value: t.label,
|
|
176
176
|
size: "sm",
|
|
177
177
|
title: t.label,
|
|
@@ -189,26 +189,26 @@ function U({ definition: e, value: r, focusRef: n, onChange: l }) {
|
|
|
189
189
|
className: "text-pho-description flex min-w-0 flex-col gap-1 text-xs",
|
|
190
190
|
children: ["From", /* @__PURE__ */ a("input", {
|
|
191
191
|
type: "date",
|
|
192
|
-
value:
|
|
193
|
-
max:
|
|
192
|
+
value: u(r?.from),
|
|
193
|
+
max: u(r?.to) || void 0,
|
|
194
194
|
onChange: (t) => c("from", t.target.value),
|
|
195
|
-
className:
|
|
195
|
+
className: C
|
|
196
196
|
})]
|
|
197
197
|
}), /* @__PURE__ */ p("label", {
|
|
198
198
|
className: "text-pho-description flex min-w-0 flex-col gap-1 text-xs",
|
|
199
199
|
children: ["To", /* @__PURE__ */ a("input", {
|
|
200
200
|
type: "date",
|
|
201
|
-
value:
|
|
202
|
-
min:
|
|
201
|
+
value: u(r?.to),
|
|
202
|
+
min: u(r?.from) || void 0,
|
|
203
203
|
onChange: (t) => c("to", t.target.value),
|
|
204
|
-
className:
|
|
204
|
+
className: C
|
|
205
205
|
})]
|
|
206
206
|
})]
|
|
207
207
|
})]
|
|
208
208
|
});
|
|
209
209
|
}
|
|
210
|
-
function
|
|
211
|
-
const s = e.filter((o) =>
|
|
210
|
+
function G({ definitions: e, filters: r, onFiltersChange: n, search: l, onSearchChange: i, searchPlaceholder: u = "Search", searchLabel: c, sticky: t, className: m, children: f, ...v }) {
|
|
211
|
+
const s = e.filter((o) => w(r[o.key])).length, b = (o, h) => {
|
|
212
212
|
const N = { ...r };
|
|
213
213
|
h === void 0 ? delete N[o] : N[o] = h, n(N);
|
|
214
214
|
};
|
|
@@ -217,26 +217,23 @@ function j({ definitions: e, filters: r, onFiltersChange: n, search: l, onSearch
|
|
|
217
217
|
className: y("flex min-w-0 flex-wrap items-center gap-2", t && "sticky top-0 z-10 bg-[var(--pho-surface,var(--background-color-pho-page))] py-3", m),
|
|
218
218
|
...v,
|
|
219
219
|
children: [
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
placeholder: i,
|
|
231
|
-
"aria-label": c ?? i
|
|
232
|
-
})]
|
|
220
|
+
i != null ? /* @__PURE__ */ a(D, {
|
|
221
|
+
value: l ?? "",
|
|
222
|
+
onChange: (o) => i(o.target.value),
|
|
223
|
+
onClear: () => i(""),
|
|
224
|
+
onKeyDown: (o) => {
|
|
225
|
+
o.key === "Escape" && (o.preventDefault(), i(""));
|
|
226
|
+
},
|
|
227
|
+
placeholder: u,
|
|
228
|
+
"aria-label": c ?? u,
|
|
229
|
+
className: "w-64 max-w-full"
|
|
233
230
|
}) : null,
|
|
234
231
|
e.map((o) => /* @__PURE__ */ a(B, {
|
|
235
232
|
definition: o,
|
|
236
233
|
value: r[o.key],
|
|
237
234
|
onChange: (h) => b(o.key, h)
|
|
238
235
|
}, o.key)),
|
|
239
|
-
s > 1 ? /* @__PURE__ */ a(
|
|
236
|
+
s > 1 ? /* @__PURE__ */ a(T, {
|
|
240
237
|
variant: "ghost",
|
|
241
238
|
size: "sm",
|
|
242
239
|
onClick: () => {
|
|
@@ -250,12 +247,12 @@ function j({ definitions: e, filters: r, onFiltersChange: n, search: l, onSearch
|
|
|
250
247
|
]
|
|
251
248
|
});
|
|
252
249
|
}
|
|
253
|
-
|
|
250
|
+
G.displayName = "FilterBar";
|
|
254
251
|
export {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
252
|
+
w as i,
|
|
253
|
+
G as n,
|
|
254
|
+
z as r,
|
|
255
|
+
O as t
|
|
259
256
|
};
|
|
260
257
|
|
|
261
|
-
//# sourceMappingURL=filter-bar-
|
|
258
|
+
//# sourceMappingURL=filter-bar-C4sPEjw1.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-bar-C4sPEjw1.js","names":[],"sources":["../../../../node_modules/.pnpm/@tabler+icons-react@3.46.0_react@19.2.8/node_modules/@tabler/icons-react/dist/esm/icons/IconPlus.mjs","../../src/components/filter-bar/filter-bar.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\": \"M12 5l0 14\", \"key\": \"svg-0\" }], [\"path\", { \"d\": \"M5 12l14 0\", \"key\": \"svg-1\" }]];\nconst IconPlus = createReactComponent(\"outline\", \"plus\", \"Plus\", __iconNode);\n\nexport { __iconNode, IconPlus as default };\n//# sourceMappingURL=IconPlus.mjs.map\n","import {\n useRef,\n useState,\n type ComponentProps,\n type ReactNode,\n type Ref,\n type RefObject,\n} from \"react\";\nimport { IconCheck, IconPlus, IconSearch, IconX } from \"@tabler/icons-react\";\nimport { Button } from \"../button\";\nimport { Command } from \"../command\";\nimport { BoxSearch } from \"../input-group/box-search\";\nimport { Popover } from \"../popover\";\nimport { cn } from \"../../utils/cn\";\n\n/**\n * Filter Bar — the row that pins above a log list: a search field for an\n * id, then one chip per filter. A chip with nothing chosen reads\n * `+ Status`; with a value it reads `Status: Failed` and carries its own\n * clear. Each chip opens a small popover: a keyboard-first list of\n * options (with a search field past eight of them), or presets and a\n * from / to pair for a date range.\n *\n * Design notes:\n * - Fully controlled. The page owns `filters` and `search` and hands\n * them back changed; the bar keeps no state but which popover is open.\n * That keeps the query in one place (the URL, a store) and lets the\n * list refetch from it.\n * - Chips are real buttons (the trigger, and the clear beside it), so\n * the bar is one tab stop per control and Escape closes a popover\n * back onto its chip.\n * - No scroll container of its own: chips wrap. `sticky` pins it to the\n * top of the nearest scroll pane on the surface it sits on.\n */\n\nexport interface FilterOption {\n value: string;\n label: string;\n /**\n * Rich option row (a badge, mono path). Search and the set chip still\n * use `label` and `value` strings.\n */\n title?: ReactNode;\n}\n\ninterface FilterDefinitionBase {\n /** The key in `filters` this chip reads and writes. */\n key: string;\n /** The chip's word — \"Status\", \"Event type\". Sentence case. */\n label: string;\n}\n\nexport interface SelectFilterDefinition extends FilterDefinitionBase {\n type?: \"select\";\n options: readonly FilterOption[];\n /** Several values at once (`string[]`); one at a time by default. */\n multiple?: boolean;\n /**\n * A search field above the options. On by default past eight options,\n * off below; say so to override.\n */\n searchable?: boolean;\n /** The search field's placeholder. @default \"Search\" */\n searchPlaceholder?: string;\n}\n\n/** A relative preset for a date-range filter — \"Last 7 days\" is 168 hours. */\nexport interface DateRangePreset {\n label: string;\n hours: number;\n}\n\nexport interface DateRangeFilterDefinition extends FilterDefinitionBase {\n type: \"date-range\";\n /** The relative presets listed above the from / to pair. */\n presets?: readonly DateRangePreset[];\n}\n\nexport type FilterDefinition =\n SelectFilterDefinition | DateRangeFilterDefinition;\n\n/**\n * A date-range value. A preset stores an absolute ISO timestamp in `from`\n * (computed when chosen) and its label in `preset`; the from / to pair\n * stores plain `YYYY-MM-DD` dates. Either side may be open (`null`).\n */\nexport interface DateRangeValue {\n from: string | null;\n to: string | null;\n preset?: string;\n}\n\nexport type FilterValue = string | string[] | DateRangeValue;\n\n/** The page's filter state — one entry per definition key that is set. */\nexport type FilterValues = Record<string, FilterValue | undefined>;\n\n/** The relative presets a date-range chip lists when given none. */\nexport const FILTER_DATE_PRESETS: readonly DateRangePreset[] = [\n { label: \"Last hour\", hours: 1 },\n { label: \"Last 24 hours\", hours: 24 },\n { label: \"Last 7 days\", hours: 24 * 7 },\n { label: \"Last 30 days\", hours: 24 * 30 },\n];\n\n/** Past this many options a select chip shows a search field. */\nconst SEARCHABLE_PAST = 8;\n\n/** Whether a filter value counts as set — an empty string or list does not. */\nexport function isFilterSet(value: FilterValue | undefined): boolean {\n if (value == null) return false;\n if (typeof value === \"string\") return value !== \"\";\n if (Array.isArray(value)) return value.length > 0;\n return value.from != null || value.to != null;\n}\n\nconst DATE_ONLY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\nfunction formatDay(value: string): string {\n // A plain date is a calendar day: format it in UTC so the day survives\n // every viewer timezone. A timestamp is a moment: format it locally.\n const dateOnly = DATE_ONLY.test(value);\n const date = new Date(dateOnly ? `${value}T00:00:00Z` : value);\n if (Number.isNaN(date.getTime())) return value;\n return new Intl.DateTimeFormat(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n ...(dateOnly ? { timeZone: \"UTC\" } : {}),\n }).format(date);\n}\n\n/** The words a set chip shows after its label. */\nexport function formatFilterValue(\n definition: FilterDefinition,\n value: FilterValue | undefined,\n): string {\n if (!isFilterSet(value) || value == null) return \"\";\n if (definition.type === \"date-range\") {\n const range = value as DateRangeValue;\n if (range.preset) return range.preset;\n if (range.from && range.to) {\n return `${formatDay(range.from)} to ${formatDay(range.to)}`;\n }\n if (range.from) return `From ${formatDay(range.from)}`;\n if (range.to) return `Until ${formatDay(range.to)}`;\n return \"\";\n }\n const labelOf = (item: string) =>\n definition.options.find((option) => option.value === item)?.label ?? item;\n if (Array.isArray(value)) return value.map(labelOf).join(\", \");\n return typeof value === \"string\" ? labelOf(value) : \"\";\n}\n\n/* --------------------------------- chips -------------------------------- */\n\nconst CHIP_TRIGGER =\n \"outline-pho-brand flex h-8 min-w-0 cursor-pointer items-center gap-1.5 rounded-full text-base transition-[color,background-color] duration-100 ease-linear focus-visible:outline-2 focus-visible:-outline-offset-2 [&>svg]:size-4 [&>svg]:shrink-0\";\n\nfunction FilterChip({\n definition,\n value,\n onChange,\n}: {\n definition: FilterDefinition;\n value: FilterValue | undefined;\n onChange: (next: FilterValue | undefined) => void;\n}) {\n const [open, setOpen] = useState(false);\n const focusRef = useRef<HTMLElement | null>(null);\n const set = isFilterSet(value);\n const display = formatFilterValue(definition, value);\n return (\n <Popover.Root open={open} onOpenChange={setOpen}>\n <span\n data-filter-chip={definition.key}\n data-set={set || undefined}\n className={cn(\n \"ring-pho-primary inline-flex max-w-full shrink-0 items-stretch rounded-full ring-1 ring-inset\",\n set ? \"bg-pho-secondary\" : \"bg-transparent\",\n )}\n >\n <Popover.Trigger\n aria-label={set ? undefined : `${definition.label} filter`}\n className={cn(\n CHIP_TRIGGER,\n \"hover:bg-pho-ghost-hover\",\n set ? \"pr-1.5 pl-3\" : \"pr-3 pl-2.5\",\n set ? \"rounded-r-none\" : \"\",\n )}\n >\n {set ? null : <IconPlus aria-hidden />}\n {set ? (\n <span className=\"flex min-w-0 items-baseline gap-1\">\n <span className=\"text-pho-description shrink-0\">\n {definition.label}:\n </span>\n <span className=\"text-pho-primary max-w-56 truncate font-medium\">\n {display}\n </span>\n </span>\n ) : (\n <span className=\"text-pho-secondary\">{definition.label}</span>\n )}\n </Popover.Trigger>\n {set ? (\n <button\n type=\"button\"\n aria-label={`Clear ${definition.label.toLowerCase()} filter`}\n onClick={() => onChange(undefined)}\n className={cn(\n CHIP_TRIGGER,\n \"text-pho-secondary hover:bg-pho-ghost-hover hover:text-pho-secondary-hover rounded-l-none pr-2.5 pl-1 [&>svg]:size-3.5\",\n )}\n >\n <IconX aria-hidden />\n </button>\n ) : null}\n </span>\n <Popover.Portal>\n <Popover.Positioner align=\"start\" sideOffset={6}>\n <Popover.Popup\n aria-label={`${definition.label} filter`}\n initialFocus={focusRef}\n className={cn(\n \"w-64 p-0\",\n definition.type === \"date-range\" && \"w-72\",\n )}\n >\n {definition.type === \"date-range\" ? (\n <DateRangePanel\n definition={definition}\n value={value as DateRangeValue | undefined}\n focusRef={focusRef}\n onChange={(next) => {\n onChange(next);\n if (next?.preset) setOpen(false);\n }}\n />\n ) : (\n <OptionsPanel\n definition={definition}\n value={value as string | string[] | undefined}\n focusRef={focusRef}\n onChange={(next) => {\n onChange(next);\n if (!definition.multiple) setOpen(false);\n }}\n />\n )}\n </Popover.Popup>\n </Popover.Positioner>\n </Popover.Portal>\n </Popover.Root>\n );\n}\n\nfunction OptionsPanel({\n definition,\n value,\n focusRef,\n onChange,\n}: {\n definition: SelectFilterDefinition;\n value: string | string[] | undefined;\n focusRef: RefObject<HTMLElement | null>;\n onChange: (next: string | string[] | undefined) => void;\n}) {\n const [query, setQuery] = useState(\"\");\n const searchable =\n definition.searchable ?? definition.options.length > SEARCHABLE_PAST;\n const chosen = new Set(\n value == null ? [] : Array.isArray(value) ? value : [value],\n );\n const needle = query.trim().toLowerCase();\n const options = needle\n ? definition.options.filter(\n (option) =>\n option.label.toLowerCase().includes(needle) ||\n option.value.toLowerCase().includes(needle),\n )\n : definition.options;\n const pick = (option: FilterOption) => {\n if (definition.multiple) {\n const next = new Set(chosen);\n if (next.has(option.value)) next.delete(option.value);\n else next.add(option.value);\n // Keep the definition's order, so the chip reads the same way\n // whatever order the values were chosen in.\n const ordered = definition.options\n .map((item) => item.value)\n .filter((item) => next.has(item));\n onChange(ordered.length > 0 ? ordered : undefined);\n return;\n }\n onChange(chosen.has(option.value) ? undefined : option.value);\n };\n return (\n <Command.Root\n label={`${definition.label} options`}\n // Without a search field the list itself takes focus, so the arrow\n // keys work from the first frame.\n ref={(node: HTMLDivElement | null) => {\n if (!searchable) focusRef.current = node;\n }}\n className=\"rounded-base\"\n >\n {searchable ? (\n <Command.InputGroup>\n <IconSearch\n aria-hidden\n className=\"text-pho-description pointer-events-none absolute left-3 size-4\"\n />\n <Command.Input\n ref={(node: HTMLInputElement | null) => {\n focusRef.current = node;\n }}\n value={query}\n onValueChange={setQuery}\n placeholder={definition.searchPlaceholder ?? \"Search\"}\n aria-label={`Search ${definition.label.toLowerCase()} options`}\n className=\"py-2 pr-3 pl-9 text-base\"\n />\n </Command.InputGroup>\n ) : null}\n <Command.List>\n <Command.Empty>Nothing matches.</Command.Empty>\n {options.map((option) => (\n <Command.Item\n key={option.value}\n value={option.value}\n size=\"sm\"\n title={option.title ?? option.label}\n aria-checked={chosen.has(option.value)}\n icon={chosen.has(option.value) ? <IconCheck aria-hidden /> : null}\n onSelect={() => pick(option)}\n />\n ))}\n </Command.List>\n </Command.Root>\n );\n}\n\nconst DATE_INPUT =\n \"bg-pho-primary ring-pho-primary text-pho-primary focus-visible:ring-pho-brand w-full min-w-0 rounded-sm px-2 py-1 text-base ring-1 transition-shadow duration-100 ease-linear outline-none ring-inset focus-visible:ring-2\";\n\nfunction DateRangePanel({\n definition,\n value,\n focusRef,\n onChange,\n}: {\n definition: DateRangeFilterDefinition;\n value: DateRangeValue | undefined;\n focusRef: RefObject<HTMLElement | null>;\n onChange: (next: DateRangeValue | undefined) => void;\n}) {\n const presets = definition.presets ?? FILTER_DATE_PRESETS;\n // A preset's `from` is a timestamp; the day field shows its date.\n const day = (side: string | null | undefined) =>\n side == null ? \"\" : DATE_ONLY.test(side) ? side : side.slice(0, 10);\n const setSide = (side: \"from\" | \"to\", next: string) => {\n // Editing either day leaves a preset behind: the pair is the value now,\n // and a preset's timestamp becomes the day it fell on.\n const range: DateRangeValue = {\n from: day(value?.from) || null,\n to: day(value?.to) || null,\n [side]: next || null,\n };\n onChange(range.from == null && range.to == null ? undefined : range);\n };\n return (\n <div className=\"flex flex-col\">\n <Command.Root\n label={`${definition.label} presets`}\n ref={(node: HTMLDivElement | null) => {\n focusRef.current = node;\n }}\n className=\"rounded-t-[inherit]\"\n >\n <Command.List>\n {presets.map((preset) => (\n <Command.Item\n key={preset.label}\n value={preset.label}\n size=\"sm\"\n title={preset.label}\n aria-checked={value?.preset === preset.label}\n icon={\n value?.preset === preset.label ? (\n <IconCheck aria-hidden />\n ) : null\n }\n onSelect={() =>\n onChange({\n from: new Date(\n Date.now() - preset.hours * 60 * 60 * 1000,\n ).toISOString(),\n to: null,\n preset: preset.label,\n })\n }\n />\n ))}\n </Command.List>\n </Command.Root>\n {/* The day fields stand outside the command list: its arrow-key\n handling would otherwise swallow the native date stepping. */}\n <div className=\"border-pho-secondary grid grid-cols-2 gap-2 border-t p-2\">\n <label className=\"text-pho-description flex min-w-0 flex-col gap-1 text-xs\">\n From\n <input\n type=\"date\"\n value={day(value?.from)}\n max={day(value?.to) || undefined}\n onChange={(event) => setSide(\"from\", event.target.value)}\n className={DATE_INPUT}\n />\n </label>\n <label className=\"text-pho-description flex min-w-0 flex-col gap-1 text-xs\">\n To\n <input\n type=\"date\"\n value={day(value?.to)}\n min={day(value?.from) || undefined}\n onChange={(event) => setSide(\"to\", event.target.value)}\n className={DATE_INPUT}\n />\n </label>\n </div>\n </div>\n );\n}\n\n/* ---------------------------------- bar --------------------------------- */\n\nexport interface FilterBarProps extends Omit<\n ComponentProps<\"div\">,\n \"children\"\n> {\n /** One chip per definition, in this order. */\n definitions: readonly FilterDefinition[];\n /** The values, keyed by definition key. Absent or empty means unset. */\n filters: FilterValues;\n /** The whole map, changed — set, cleared, or cleared all. */\n onFiltersChange: (next: FilterValues) => void;\n /** The search field's text. The field renders only with `onSearchChange`. */\n search?: string;\n onSearchChange?: (value: string) => void;\n /** A teaching phrase — \"Find an event by id\". @default \"Search\" */\n searchPlaceholder?: string;\n /** The field's accessible name when the placeholder is too short to be one. */\n searchLabel?: string;\n /**\n * Pin the bar to the top of the nearest scroll pane. It paints the\n * surface it sits on (`--pho-surface`) so rows scroll under it.\n */\n sticky?: boolean;\n /** Controls after the chips — a range picker, an export button. */\n children?: ReactNode;\n ref?: Ref<HTMLDivElement>;\n}\n\nexport function FilterBar({\n definitions,\n filters,\n onFiltersChange,\n search,\n onSearchChange,\n searchPlaceholder = \"Search\",\n searchLabel,\n sticky,\n className,\n children,\n ...props\n}: FilterBarProps) {\n const setCount = definitions.filter((definition) =>\n isFilterSet(filters[definition.key]),\n ).length;\n const change = (key: string, next: FilterValue | undefined) => {\n const rest = { ...filters };\n if (next === undefined) delete rest[key];\n else rest[key] = next;\n onFiltersChange(rest);\n };\n return (\n <div\n data-filter-bar=\"\"\n className={cn(\n \"flex min-w-0 flex-wrap items-center gap-2\",\n sticky &&\n \"sticky top-0 z-10 bg-[var(--pho-surface,var(--background-color-pho-page))] py-3\",\n className,\n )}\n {...props}\n >\n {onSearchChange != null ? (\n <BoxSearch\n value={search ?? \"\"}\n onChange={(event) => onSearchChange(event.target.value)}\n onClear={() => onSearchChange(\"\")}\n onKeyDown={(event) => {\n if (event.key !== \"Escape\") return;\n event.preventDefault();\n onSearchChange(\"\");\n }}\n placeholder={searchPlaceholder}\n aria-label={searchLabel ?? searchPlaceholder}\n className=\"w-64 max-w-full\"\n />\n ) : null}\n {definitions.map((definition) => (\n <FilterChip\n key={definition.key}\n definition={definition}\n value={filters[definition.key]}\n onChange={(next) => change(definition.key, next)}\n />\n ))}\n {setCount > 1 ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => {\n const rest = { ...filters };\n for (const definition of definitions) delete rest[definition.key];\n onFiltersChange(rest);\n }}\n >\n Clear filters\n </Button>\n ) : null}\n {children}\n </div>\n );\n}\n\nFilterBar.displayName = \"FilterBar\";\n"],"x_google_ignoreList":[0],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { t as l } from "./cn-ChIgwEl3.js";
|
|
3
|
+
import { t as p } from "./IconSearch-BKAHkLyX.js";
|
|
4
|
+
import { t as h } from "./IconX-pZ9vrtSr.js";
|
|
5
|
+
import { useRef as m } from "react";
|
|
6
|
+
import { jsx as t, jsxs as d } from "react/jsx-runtime";
|
|
7
|
+
function k({ value: i, onChange: c, onClear: n, trailing: o, className: u, ref: a, ...r }) {
|
|
8
|
+
const s = m(null);
|
|
9
|
+
return /* @__PURE__ */ d("label", {
|
|
10
|
+
className: l("group flex min-w-40 flex-1 items-center gap-2", u),
|
|
11
|
+
children: [
|
|
12
|
+
/* @__PURE__ */ t(p, {
|
|
13
|
+
"aria-hidden": !0,
|
|
14
|
+
className: "text-pho-description group-focus-within:text-pho-primary size-4 shrink-0 transition-colors duration-100 ease-linear"
|
|
15
|
+
}),
|
|
16
|
+
/* @__PURE__ */ t("input", {
|
|
17
|
+
ref: (e) => {
|
|
18
|
+
s.current = e, typeof a == "function" ? a(e) : a != null && (a.current = e);
|
|
19
|
+
},
|
|
20
|
+
type: "search",
|
|
21
|
+
value: i,
|
|
22
|
+
onChange: (e) => {
|
|
23
|
+
e.stopPropagation(), c(e.target.value);
|
|
24
|
+
},
|
|
25
|
+
className: l("text-pho-primary placeholder:text-pho-description text-md w-full min-w-0 bg-transparent outline-none md:text-base", n != null && "[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none"),
|
|
26
|
+
...r,
|
|
27
|
+
"aria-label": r["aria-label"] ?? (r["aria-labelledby"] ? void 0 : "Search")
|
|
28
|
+
}),
|
|
29
|
+
o != null ? /* @__PURE__ */ t("span", {
|
|
30
|
+
className: "text-pho-description shrink-0 text-xs tabular-nums",
|
|
31
|
+
children: o
|
|
32
|
+
}) : null,
|
|
33
|
+
n != null && i !== "" && !r.disabled ? /* @__PURE__ */ t("button", {
|
|
34
|
+
type: "button",
|
|
35
|
+
"aria-label": "Clear search",
|
|
36
|
+
tabIndex: -1,
|
|
37
|
+
onClick: () => {
|
|
38
|
+
n(), s.current?.focus();
|
|
39
|
+
},
|
|
40
|
+
className: "text-pho-secondary hover:text-pho-secondary-hover flex shrink-0 cursor-pointer items-center transition-colors duration-100 ease-linear [&>svg]:size-4",
|
|
41
|
+
children: /* @__PURE__ */ t(h, { "aria-hidden": !0 })
|
|
42
|
+
}) : null
|
|
43
|
+
]
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
k as t
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=ghost-search-8PzP73zS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ghost-search-8PzP73zS.js","names":[],"sources":["../../src/utils/ghost-search.tsx"],"sourcesContent":["import { useRef, type ComponentProps, type ReactNode } from \"react\";\nimport { IconSearch, IconX } from \"@tabler/icons-react\";\nimport { cn } from \"./cn\";\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 * Empties the query from the field's own clear button, shown while it\n * holds text; focus stays in the field. Without it, the browser's own\n * clear glyph stays.\n */\n onClear?: () => void;\n /** Quiet words at the field's end, before the clear button — a count. */\n trailing?: ReactNode;\n}\n\n/**\n * A search box with no box: the glyph and the text on the surface's own\n * line, the way a toolbar band inside a card carries it (a `DataTable`'s\n * search, a `Trace.Waterfall`'s, a `Scope`'s first row). Quiet ink, and no\n * ring on focus either: a ring would draw the box the face exists not to\n * have. Focus shows in the caret and in the magnifier, which takes the\n * primary ink while the field holds focus.\n *\n * The query is the list's, not a form's: its change stops at the field, so\n * an enclosing form that saves on change (BasicPage `Form autoSave`) or\n * shows a save row once dirty never hears it. The type is an `sm` field's:\n * 16px on small viewports, where iOS zooms into anything smaller.\n */\nexport function GhostSearch({\n value,\n onChange,\n onClear,\n trailing,\n className,\n ref,\n ...props\n}: GhostSearchProps) {\n const inputRef = useRef<HTMLInputElement | null>(null);\n return (\n <label\n className={cn(\"group flex min-w-40 flex-1 items-center gap-2\", className)}\n >\n <IconSearch\n aria-hidden\n className=\"text-pho-description group-focus-within:text-pho-primary size-4 shrink-0 transition-colors duration-100 ease-linear\"\n />\n <input\n ref={(node) => {\n inputRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref != null) ref.current = node;\n }}\n type=\"search\"\n value={value}\n onChange={(event) => {\n event.stopPropagation();\n onChange(event.target.value);\n }}\n className={cn(\n \"text-pho-primary placeholder:text-pho-description text-md w-full min-w-0 bg-transparent outline-none md:text-base\",\n // The field's own clear takes over from the browser's.\n onClear != null &&\n \"[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none\",\n )}\n {...props}\n aria-label={\n props[\"aria-label\"] ??\n (props[\"aria-labelledby\"] ? undefined : \"Search\")\n }\n />\n {trailing != null ? (\n <span className=\"text-pho-description shrink-0 text-xs tabular-nums\">\n {trailing}\n </span>\n ) : null}\n {onClear != null && value !== \"\" && !props.disabled ? (\n <button\n type=\"button\"\n aria-label=\"Clear search\"\n // Reachable by click, skipped by Tab: `Escape` clears from the\n // keyboard, and Tab moves on to the list.\n tabIndex={-1}\n onClick={() => {\n onClear();\n inputRef.current?.focus();\n }}\n className=\"text-pho-secondary hover:text-pho-secondary-hover flex shrink-0 cursor-pointer items-center transition-colors duration-100 ease-linear [&>svg]:size-4\"\n >\n <IconX aria-hidden />\n </button>\n ) : null}\n </label>\n );\n}\n"],"mappings":";;;;;;AAiCA,SAAgB,EAAY,EAC1B,OAAA,GACA,UAAA,GACA,SAAA,GACA,UAAA,GACA,WAAA,GACA,KAAA,GACA,GAAG,EAAA,GACgB;AACnB,QAAM,IAAW,EAAgC,IAAI;AACrD,SACE,gBAAA,EAAC,SAAD;AAAA,IACE,WAAW,EAAG,iDAAiD,CAAS;AAAA,IAD1E,UAAA;AAAA,MAGE,gBAAA,EAAC,GAAD;AAAA,QACE,eAAA;AAAA,QACA,WAAU;AAAA,MACX,CAAA;AAAA,MACD,gBAAA,EAAC,SAAD;AAAA,QACE,KAAA,CAAM,MAAS;AACb,UAAA,EAAS,UAAU,GACf,OAAO,KAAQ,aAAY,EAAI,CAAI,IAC9B,KAAO,SAAM,EAAI,UAAU;AAAA,QACtC;AAAA,QACA,MAAK;AAAA,QACE,OAAA;AAAA,QACP,UAAA,CAAW,MAAU;AACnB,UAAA,EAAM,gBAAgB,GACtB,EAAS,EAAM,OAAO,KAAK;AAAA,QAC7B;AAAA,QACA,WAAW,EACT,qHAEA,KAAW,QACT,kGACJ;AAAA,QACA,GAAI;AAAA,QACJ,cACE,EAAM,YAAA,MACL,EAAM,iBAAA,IAAqB,SAAY;AAAA,MAE3C,CAAA;AAAA,MACA,KAAY,OACX,gBAAA,EAAC,QAAD;AAAA,QAAM,WAAU;AAAA,QACb,UAAA;AAAA,MACG,CAAA,IACJ;AAAA,MACH,KAAW,QAAQ,MAAU,MAAM,CAAC,EAAM,WACzC,gBAAA,EAAC,UAAD;AAAA,QACE,MAAK;AAAA,QACL,cAAW;AAAA,QAGX,UAAU;AAAA,QACV,SAAA,MAAe;AACb,UAAA,EAAQ,GACR,EAAS,SAAS,MAAM;AAAA,QAC1B;AAAA,QACA,WAAU;AAAA,QAEV,UAAA,gBAAA,EAAC,GAAD,EAAO,eAAA,GAAa,CAAA;AAAA,MACd,CAAA,IACN;AAAA,IACC;AAAA;AAEX"}
|