@burtson-labs/ui 0.12.4 → 0.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/README.md +1 -1
- package/dist/components/checkbox-card.d.ts +25 -0
- package/dist/components/checkbox-card.js +65 -0
- package/dist/components/checkbox-card.js.map +1 -0
- package/dist/components/data-table.d.ts +53 -3
- package/dist/components/data-table.js +345 -112
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/dialog.d.ts +6 -1
- package/dist/components/dialog.js +21 -10
- package/dist/components/dialog.js.map +1 -1
- package/dist/components/pagination.d.ts +104 -3
- package/dist/components/pagination.js +244 -58
- package/dist/components/pagination.js.map +1 -1
- package/dist/components/sheet.d.ts +6 -1
- package/dist/components/sheet.js +2 -2
- package/dist/components/sheet.js.map +1 -1
- package/dist/components/steps.d.ts +15 -2
- package/dist/components/steps.js +68 -22
- package/dist/components/steps.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -3
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-table.js","names":[],"sources":["../../src/components/data-table.tsx"],"sourcesContent":["import ArrowDown from '@burtson-labs/icons/react/arrow-down';\nimport ArrowUp from '@burtson-labs/icons/react/arrow-up';\nimport ChevronsUpDown from '@burtson-labs/icons/react/chevrons-up-down';\nimport Search from '@burtson-labs/icons/react/search';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\nimport { Checkbox } from './checkbox';\nimport { Input } from './input';\nimport { Pagination } from './pagination';\nimport { Skeleton } from './skeleton';\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './table';\n\nexport interface DataTableColumn<T> {\n id: string;\n header: React.ReactNode;\n cell: (row: T) => React.ReactNode;\n sortable?: boolean;\n /** Right-aligned tabular figures. */\n numeric?: boolean;\n className?: string;\n}\n\nexport type SortDirection = 'asc' | 'desc';\n\nexport interface DataTableSort {\n columnId: string;\n direction: SortDirection;\n}\n\nexport interface DataTableProps<T> extends Omit<React.ComponentProps<'div'>, 'children'> {\n /** Required: what the table lists, e.g. \"Workspace members\". */\n 'aria-label': string;\n columns: DataTableColumn<T>[];\n /** The rows to show now: already sorted, filtered and paged by the app or server. */\n rows: T[];\n getRowId: (row: T) => string;\n /** Names a row for screen readers (\"Select Ada Lovelace\"). Defaults to its id. */\n getRowLabel?: (row: T) => string;\n\n /** Controlled sort. A sortable header cycles ascending, descending, none. */\n sort?: DataTableSort | null;\n onSortChange?: (sort: DataTableSort | null) => void;\n\n /** Controlled filter text; a search box appears when onFilterChange is set. */\n filter?: string;\n onFilterChange?: (value: string) => void;\n filterPlaceholder?: string;\n\n /** Controlled selection by row id; checkboxes appear when onSelectedChange is set. */\n selected?: string[];\n onSelectedChange?: (ids: string[]) => void;\n\n /** Enter or a click on the row, e.g. open the record. */\n onRowAction?: (row: T) => void;\n /** Per-row controls in a trailing column, e.g. a DropdownMenu. */\n rowActions?: (row: T) => React.ReactNode;\n\n /** 1-based; Pagination appears when onPageChange is set. */\n page?: number;\n pageCount?: number;\n onPageChange?: (page: number) => void;\n /** e.g. \"1–25 of 312\". */\n pageSummary?: React.ReactNode;\n\n loading?: boolean;\n /** A message shown in place of the rows. */\n error?: React.ReactNode;\n onRetry?: () => void;\n /** Shown when there are no rows and nothing failed. */\n empty?: React.ReactNode;\n /** Extra controls beside the search box, e.g. filters or a bulk action. */\n toolbar?: React.ReactNode;\n density?: 'compact' | 'default';\n stickyHeader?: boolean;\n}\n\nconst nextSort = (current: DataTableSort | null | undefined, id: string): DataTableSort | null => {\n if (!current || current.columnId !== id) return { columnId: id, direction: 'asc' };\n return current.direction === 'asc' ? { columnId: id, direction: 'desc' } : null;\n};\n\n// Clicks and keys aimed at these belong to them, not to the row.\nconst INTERACTIVE = 'a,button,input,select,textarea,[role=\"checkbox\"],[role=\"menuitem\"]';\n\n/**\n * A table recipe for records: sort, search, select, page, and row actions,\n * with loading, empty and error states. Fully controlled, so the data can\n * come from a server; the table never reorders rows itself.\n */\nfunction DataTable<T>({\n 'aria-label': ariaLabel,\n columns,\n rows,\n getRowId,\n getRowLabel,\n sort,\n onSortChange,\n filter,\n onFilterChange,\n filterPlaceholder = 'Search',\n selected,\n onSelectedChange,\n onRowAction,\n rowActions,\n page,\n pageCount,\n onPageChange,\n pageSummary,\n loading = false,\n error,\n onRetry,\n empty = 'No results.',\n toolbar,\n density = 'default',\n stickyHeader,\n className,\n ...props\n}: DataTableProps<T>) {\n const selectable = Boolean(onSelectedChange);\n const selectedSet = React.useMemo(() => new Set(selected ?? []), [selected]);\n const ids = rows.map(getRowId);\n const pageSelected = ids.filter((id) => selectedSet.has(id)).length;\n const allState: boolean | 'indeterminate' =\n ids.length > 0 && pageSelected === ids.length\n ? true\n : pageSelected > 0\n ? 'indeterminate'\n : false;\n const colCount = columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0);\n const interactive = Boolean(onRowAction) || selectable;\n\n // One tab stop in the body: the active row. Arrows move it.\n const [active, setActive] = React.useState(0);\n const frameRef = React.useRef<HTMLDivElement>(null);\n // Rows can shrink under the active index (a new page, a filter).\n const activeRow = Math.min(active, Math.max(0, rows.length - 1));\n\n const toggle = (id: string) => {\n if (!onSelectedChange) return;\n const next = new Set(selectedSet);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n onSelectedChange([...next]);\n };\n const toggleAll = () => {\n if (!onSelectedChange) return;\n const next = new Set(selectedSet);\n if (allState === true) ids.forEach((id) => next.delete(id));\n else ids.forEach((id) => next.add(id));\n onSelectedChange([...next]);\n };\n const focusRow = (i: number) => {\n const clamped = Math.min(Math.max(i, 0), rows.length - 1);\n setActive(clamped);\n frameRef.current\n ?.querySelectorAll<HTMLElement>('[data-slot=\"table-body\"] > [data-slot=\"table-row\"]')\n [clamped]?.focus();\n };\n\n const onRowKeyDown = (event: React.KeyboardEvent<HTMLTableRowElement>, i: number, row: T) => {\n if (event.target !== event.currentTarget) return;\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n focusRow(i + 1);\n break;\n case 'ArrowUp':\n event.preventDefault();\n focusRow(i - 1);\n break;\n case 'Home':\n event.preventDefault();\n focusRow(0);\n break;\n case 'End':\n event.preventDefault();\n focusRow(rows.length - 1);\n break;\n case 'Enter':\n if (onRowAction) {\n event.preventDefault();\n onRowAction(row);\n }\n break;\n case ' ':\n if (selectable) {\n event.preventDefault();\n toggle(getRowId(row));\n }\n break;\n }\n };\n\n const showToolbar = Boolean(onFilterChange || toolbar || (selectable && selectedSet.size));\n let body: React.ReactNode;\n if (error) {\n body = (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell colSpan={colCount} className=\"h-auto py-8 text-center whitespace-normal\">\n <div role=\"alert\" className=\"grid justify-items-center gap-3 text-sm\">\n <span className=\"text-destructive\">{error}</span>\n {onRetry && (\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={onRetry}>\n Try again\n </Button>\n )}\n </div>\n </TableCell>\n </TableRow>\n );\n } else if (loading && rows.length === 0) {\n body = Array.from({ length: 3 }, (_, r) => (\n <TableRow key={`loading-${r}`} className=\"hover:bg-transparent\" aria-hidden>\n {Array.from({ length: colCount }, (_, c) => (\n <TableCell key={c}>\n <Skeleton className=\"h-3.5 w-full max-w-40\" />\n </TableCell>\n ))}\n </TableRow>\n ));\n } else if (rows.length === 0) {\n body = (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell\n colSpan={colCount}\n className=\"h-auto py-10 text-center whitespace-normal text-muted-foreground\"\n >\n {empty}\n </TableCell>\n </TableRow>\n );\n } else {\n body = rows.map((row, i) => {\n const id = getRowId(row);\n const isSelected = selectedSet.has(id);\n const label = getRowLabel?.(row) ?? id;\n return (\n <TableRow\n key={id}\n selected={isSelected}\n tabIndex={interactive ? (i === activeRow ? 0 : -1) : undefined}\n onFocus={() => setActive(i)}\n onKeyDown={interactive ? (e) => onRowKeyDown(e, i, row) : undefined}\n onClick={\n onRowAction\n ? (e) => {\n if ((e.target as HTMLElement).closest(INTERACTIVE)) return;\n onRowAction(row);\n }\n : undefined\n }\n className={cn(\n interactive &&\n 'outline-none focus-visible:bg-muted/60 focus-visible:shadow-[inset_2px_0_0_var(--ring)]',\n onRowAction && 'cursor-pointer',\n )}\n >\n {selectable && (\n <TableCell className=\"w-10\">\n <Checkbox\n checked={isSelected}\n onCheckedChange={() => toggle(id)}\n aria-label={`Select ${label}`}\n tabIndex={-1}\n />\n </TableCell>\n )}\n {columns.map((col) => (\n <TableCell key={col.id} numeric={col.numeric} className={col.className}>\n {col.cell(row)}\n </TableCell>\n ))}\n {rowActions && <TableCell className=\"w-12 text-right\">{rowActions(row)}</TableCell>}\n </TableRow>\n );\n });\n }\n\n return (\n <div data-slot=\"data-table\" className={cn('grid min-w-0 gap-3', className)} {...props}>\n {showToolbar && (\n <div className=\"flex flex-wrap items-center gap-2\">\n {onFilterChange && (\n <div className=\"relative w-full max-w-xs min-w-0 flex-1\">\n <Search\n className=\"pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground\"\n aria-hidden\n />\n <Input\n type=\"search\"\n value={filter ?? ''}\n onChange={(e) => onFilterChange(e.target.value)}\n placeholder={filterPlaceholder}\n aria-label={`Search ${ariaLabel.toLowerCase()}`}\n className=\"pl-8\"\n />\n </div>\n )}\n {selectable && (\n <span aria-live=\"polite\" className=\"text-xs text-muted-foreground tabular-nums\">\n {selectedSet.size ? `${selectedSet.size} selected` : ''}\n </span>\n )}\n {toolbar && <div className=\"ml-auto flex flex-wrap items-center gap-2\">{toolbar}</div>}\n </div>\n )}\n <div ref={frameRef} className=\"rounded-lg border bg-surface\">\n <Table\n aria-label={ariaLabel}\n scrollLabel={ariaLabel}\n aria-busy={loading || undefined}\n density={density}\n stickyHeader={stickyHeader}\n className={cn(loading && rows.length > 0 && 'opacity-60 transition-opacity')}\n >\n <TableHeader>\n <TableRow className=\"hover:bg-transparent\">\n {selectable && (\n <TableHead className=\"w-10\">\n <Checkbox\n checked={allState}\n onCheckedChange={toggleAll}\n disabled={rows.length === 0}\n aria-label=\"Select all rows on this page\"\n />\n </TableHead>\n )}\n {columns.map((col) => {\n const sorted = sort?.columnId === col.id ? sort.direction : undefined;\n return (\n <TableHead\n key={col.id}\n numeric={col.numeric}\n className={col.className}\n aria-sort={\n col.sortable\n ? sorted === 'asc'\n ? 'ascending'\n : sorted === 'desc'\n ? 'descending'\n : 'none'\n : undefined\n }\n >\n {col.sortable && onSortChange ? (\n <button\n type=\"button\"\n onClick={() => onSortChange(nextSort(sort, col.id))}\n className={cn(\n '-mx-1.5 inline-flex h-7 items-center gap-1 rounded-sm px-1.5 uppercase outline-none hover:bg-muted hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/20',\n sorted && 'text-foreground',\n col.numeric && 'flex-row-reverse',\n )}\n >\n {col.header}\n {sorted === 'asc' ? (\n <ArrowUp className=\"size-3.5\" aria-hidden />\n ) : sorted === 'desc' ? (\n <ArrowDown className=\"size-3.5\" aria-hidden />\n ) : (\n <ChevronsUpDown className=\"size-3.5 opacity-50\" aria-hidden />\n )}\n </button>\n ) : (\n col.header\n )}\n </TableHead>\n );\n })}\n {rowActions && (\n <TableHead className=\"w-12\">\n <span className=\"sr-only\">Actions</span>\n </TableHead>\n )}\n </TableRow>\n </TableHeader>\n <TableBody>{body}</TableBody>\n </Table>\n </div>\n {onPageChange && page !== undefined && pageCount !== undefined && pageCount > 0 && (\n <Pagination\n page={page}\n pageCount={pageCount}\n onPageChange={onPageChange}\n summary={pageSummary}\n />\n )}\n </div>\n );\n}\n\nexport { DataTable };\n"],"mappings":";;;;;;;;;;;;;;AA+EA,IAAM,YAAY,SAA2C,OAAqC;CAChG,IAAI,CAAC,WAAW,QAAQ,aAAa,IAAI,OAAO;EAAE,UAAU;EAAI,WAAW;CAAM;CACjF,OAAO,QAAQ,cAAc,QAAQ;EAAE,UAAU;EAAI,WAAW;CAAO,IAAI;AAC7E;AAGA,IAAM,cAAc;;;;;;AAOpB,SAAS,UAAa,EACpB,cAAc,WACd,SACA,MACA,UACA,aACA,MACA,cACA,QACA,gBACA,oBAAoB,UACpB,UACA,kBACA,aACA,YACA,MACA,WACA,cACA,aACA,UAAU,OACV,OACA,SACA,QAAQ,eACR,SACA,UAAU,WACV,cACA,WACA,GAAG,SACiB;CACpB,MAAM,aAAa,QAAQ,gBAAgB;CAC3C,MAAM,cAAc,MAAM,cAAc,IAAI,IAAI,YAAY,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;CAC3E,MAAM,MAAM,KAAK,IAAI,QAAQ;CAC7B,MAAM,eAAe,IAAI,QAAQ,OAAO,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC;CAC7D,MAAM,WACJ,IAAI,SAAS,KAAK,iBAAiB,IAAI,SACnC,OACA,eAAe,IACb,kBACA;CACR,MAAM,WAAW,QAAQ,UAAU,aAAa,IAAI,MAAM,aAAa,IAAI;CAC3E,MAAM,cAAc,QAAQ,WAAW,KAAK;CAG5C,MAAM,CAAC,QAAQ,aAAa,MAAM,SAAS,CAAC;CAC5C,MAAM,WAAW,MAAM,OAAuB,IAAI;CAElD,MAAM,YAAY,KAAK,IAAI,QAAQ,KAAK,IAAI,GAAG,KAAK,SAAS,CAAC,CAAC;CAE/D,MAAM,UAAU,OAAe;EAC7B,IAAI,CAAC,kBAAkB;EACvB,MAAM,OAAO,IAAI,IAAI,WAAW;EAChC,IAAI,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,EAAE;OAC3B,KAAK,IAAI,EAAE;EAChB,iBAAiB,CAAC,GAAG,IAAI,CAAC;CAC5B;CACA,MAAM,kBAAkB;EACtB,IAAI,CAAC,kBAAkB;EACvB,MAAM,OAAO,IAAI,IAAI,WAAW;EAChC,IAAI,aAAa,MAAM,IAAI,SAAS,OAAO,KAAK,OAAO,EAAE,CAAC;OACrD,IAAI,SAAS,OAAO,KAAK,IAAI,EAAE,CAAC;EACrC,iBAAiB,CAAC,GAAG,IAAI,CAAC;CAC5B;CACA,MAAM,YAAY,MAAc;EAC9B,MAAM,UAAU,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC;EACxD,UAAU,OAAO;EACjB,SAAS,SACL,iBAA8B,wDAAoD,CAAC,CACpF,QAAQ,EAAE,MAAM;CACrB;CAEA,MAAM,gBAAgB,OAAiD,GAAW,QAAW;EAC3F,IAAI,MAAM,WAAW,MAAM,eAAe;EAC1C,QAAQ,MAAM,KAAd;GACE,KAAK;IACH,MAAM,eAAe;IACrB,SAAS,IAAI,CAAC;IACd;GACF,KAAK;IACH,MAAM,eAAe;IACrB,SAAS,IAAI,CAAC;IACd;GACF,KAAK;IACH,MAAM,eAAe;IACrB,SAAS,CAAC;IACV;GACF,KAAK;IACH,MAAM,eAAe;IACrB,SAAS,KAAK,SAAS,CAAC;IACxB;GACF,KAAK;IACH,IAAI,aAAa;KACf,MAAM,eAAe;KACrB,YAAY,GAAG;IACjB;IACA;GACF,KAAK,KACH,IAAI,YAAY;IACd,MAAM,eAAe;IACrB,OAAO,SAAS,GAAG,CAAC;GACtB;EAEJ;CACF;CAEA,MAAM,cAAc,QAAQ,kBAAkB,WAAY,cAAc,YAAY,IAAK;CACzF,IAAI;CACJ,IAAI,OACF,OACE,oBAAC,UAAD;EAAU,WAAU;EAClB,UAAA,oBAAC,WAAD;GAAW,SAAS;GAAU,WAAU;GACtC,UAAA,qBAAC,OAAD;IAAK,MAAK;IAAQ,WAAU;IAA5B,UAAA,CACE,oBAAC,QAAD;KAAM,WAAU;KAAoB,UAAA;IAAY,CAAA,GAC/C,WACC,oBAAC,QAAD;KAAQ,MAAK;KAAS,MAAK;KAAK,SAAQ;KAAU,SAAS;KAAS,UAAA;IAE5D,CAAA,CAEP;;EACI,CAAA;CACH,CAAA;MAEP,IAAI,WAAW,KAAK,WAAW,GACpC,OAAO,MAAM,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,MACnC,oBAAC,UAAD;EAA+B,WAAU;EAAuB,eAAA;EAC7D,UAAA,MAAM,KAAK,EAAE,QAAQ,SAAS,IAAI,GAAG,MACpC,oBAAC,WAAD,EAAA,UACE,oBAAC,UAAD,EAAU,WAAU,wBAAyB,CAAA,EACpC,GAFK,CAEL,CACZ;CACO,GANK,WAAW,GAMhB,CACX;MACI,IAAI,KAAK,WAAW,GACzB,OACE,oBAAC,UAAD;EAAU,WAAU;EAClB,UAAA,oBAAC,WAAD;GACE,SAAS;GACT,WAAU;GAET,UAAA;EACQ,CAAA;CACH,CAAA;MAGZ,OAAO,KAAK,KAAK,KAAK,MAAM;EAC1B,MAAM,KAAK,SAAS,GAAG;EACvB,MAAM,aAAa,YAAY,IAAI,EAAE;EACrC,MAAM,QAAQ,cAAc,GAAG,KAAK;EACpC,OACE,qBAAC,UAAD;GAEE,UAAU;GACV,UAAU,cAAe,MAAM,YAAY,IAAI,KAAM,KAAA;GACrD,eAAe,UAAU,CAAC;GAC1B,WAAW,eAAe,MAAM,aAAa,GAAG,GAAG,GAAG,IAAI,KAAA;GAC1D,SACE,eACK,MAAM;IACL,IAAK,EAAE,OAAuB,QAAQ,WAAW,GAAG;IACpD,YAAY,GAAG;GACjB,IACA,KAAA;GAEN,WAAW,GACT,eACE,2FACF,eAAe,gBACjB;GAlBF,UAAA;IAoBG,cACC,oBAAC,WAAD;KAAW,WAAU;KACnB,UAAA,oBAAC,UAAD;MACE,SAAS;MACT,uBAAuB,OAAO,EAAE;MAChC,cAAY,UAAU;MACtB,UAAU;KACX,CAAA;IACQ,CAAA;IAEZ,QAAQ,KAAK,QACZ,oBAAC,WAAD;KAAwB,SAAS,IAAI;KAAS,WAAW,IAAI;KAC1D,UAAA,IAAI,KAAK,GAAG;IACJ,GAFK,IAAI,EAET,CACZ;IACA,cAAc,oBAAC,WAAD;KAAW,WAAU;KAAmB,UAAA,WAAW,GAAG;IAAa,CAAA;GAC1E;EAnCH,GAAA,EAmCG;CAEd,CAAC;CAGH,OACE,qBAAC,OAAD;EAAK,aAAU;EAAa,WAAW,GAAG,sBAAsB,SAAS;EAAG,GAAI;EAAhF,UAAA;GACG,eACC,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACG,kBACC,qBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,oBAAC,QAAD;OACE,WAAU;OACV,eAAA;MACD,CAAA,GACD,oBAAC,OAAD;OACE,MAAK;OACL,OAAO,UAAU;OACjB,WAAW,MAAM,eAAe,EAAE,OAAO,KAAK;OAC9C,aAAa;OACb,cAAY,UAAU,UAAU,YAAY;OAC5C,WAAU;MACX,CAAA,CACE;;KAEN,cACC,oBAAC,QAAD;MAAM,aAAU;MAAS,WAAU;MAChC,UAAA,YAAY,OAAO,GAAG,YAAY,KAAK,aAAa;KACjD,CAAA;KAEP,WAAW,oBAAC,OAAD;MAAK,WAAU;MAA6C,UAAA;KAAa,CAAA;IAClF;;GAEP,oBAAC,OAAD;IAAK,KAAK;IAAU,WAAU;IAC5B,UAAA,qBAAC,OAAD;KACE,cAAY;KACZ,aAAa;KACb,aAAW,WAAW,KAAA;KACb;KACK;KACd,WAAW,GAAG,WAAW,KAAK,SAAS,KAAK,+BAA+B;KAN7E,UAAA,CAQE,oBAAC,aAAD,EAAA,UACE,qBAAC,UAAD;MAAU,WAAU;MAApB,UAAA;OACG,cACC,oBAAC,WAAD;QAAW,WAAU;QACnB,UAAA,oBAAC,UAAD;SACE,SAAS;SACT,iBAAiB;SACjB,UAAU,KAAK,WAAW;SAC1B,cAAW;QACZ,CAAA;OACQ,CAAA;OAEZ,QAAQ,KAAK,QAAQ;QACpB,MAAM,SAAS,MAAM,aAAa,IAAI,KAAK,KAAK,YAAY,KAAA;QAC5D,OACE,oBAAC,WAAD;SAEE,SAAS,IAAI;SACb,WAAW,IAAI;SACf,aACE,IAAI,WACA,WAAW,QACT,cACA,WAAW,SACT,eACA,SACJ,KAAA;SAGL,UAAA,IAAI,YAAY,eACf,qBAAC,UAAD;UACE,MAAK;UACL,eAAe,aAAa,SAAS,MAAM,IAAI,EAAE,CAAC;UAClD,WAAW,GACT,gLACA,UAAU,mBACV,IAAI,WAAW,kBACjB;UAPF,UAAA,CASG,IAAI,QACJ,WAAW,QACV,oBAAC,SAAD;WAAS,WAAU;WAAW,eAAA;UAAa,CAAA,IACzC,WAAW,SACb,oBAAC,WAAD;WAAW,WAAU;WAAW,eAAA;UAAa,CAAA,IAE7C,oBAAC,gBAAD;WAAgB,WAAU;WAAsB,eAAA;UAAa,CAAA,CAEzD;SAER,CAAA,IAAA,IAAI;QAEG,GAnCJ,IAAI,EAmCA;OAEf,CAAC;OACA,cACC,oBAAC,WAAD;QAAW,WAAU;QACnB,UAAA,oBAAC,QAAD;SAAM,WAAU;SAAU,UAAA;QAAa,CAAA;OAC9B,CAAA;MAEL;KACC,CAAA,EAAA,CAAA,GACb,oBAAC,WAAD,EAAA,UAAY,KAAgB,CAAA,CACvB;;GACJ,CAAA;GACJ,gBAAgB,SAAS,KAAA,KAAa,cAAc,KAAA,KAAa,YAAY,KAC5E,oBAAC,YAAD;IACQ;IACK;IACG;IACd,SAAS;GACV,CAAA;EAEA;;AAET"}
|
|
1
|
+
{"version":3,"file":"data-table.js","names":[],"sources":["../../src/components/data-table.tsx"],"sourcesContent":["import ArrowDown from '@burtson-labs/icons/react/arrow-down';\nimport ArrowUp from '@burtson-labs/icons/react/arrow-up';\nimport ChevronsUpDown from '@burtson-labs/icons/react/chevrons-up-down';\nimport Search from '@burtson-labs/icons/react/search';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nimport { Button } from './button';\nimport { Checkbox } from './checkbox';\nimport { Input } from './input';\nimport { Pagination, pageWindow, paginationSummary, usePagination } from './pagination';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './select';\nimport { Skeleton } from './skeleton';\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './table';\n\n/**\n * Where a column goes on a phone card: `title` and `subtitle` head the card,\n * `aside` sits top right (a status badge), `field` is a labelled value in a\n * two-column grid, `footer` is a full-width line at the bottom (links,\n * buttons), `hidden` is left off the card.\n */\nexport type DataTableCardSlot = 'title' | 'subtitle' | 'aside' | 'field' | 'footer' | 'hidden';\n\nexport type DataTableBreakpoint = 'sm' | 'md' | 'lg' | 'xl';\n\nexport interface DataTableColumn<T> {\n id: string;\n header: React.ReactNode;\n cell: (row: T) => React.ReactNode;\n sortable?: boolean;\n /** Right-aligned tabular figures. */\n numeric?: boolean;\n className?: string;\n /**\n * Place on a phone card. With no column marked `title`, the first column\n * is the title; every other column is a `field` unless it says otherwise.\n */\n card?: DataTableCardSlot;\n /** The field's label on a card when `header` is not plain text. */\n cardLabel?: string;\n /** A shorter rendering for the card, e.g. a date without the time. */\n cardCell?: (row: T) => React.ReactNode;\n /** Only on cards, never a table column (e.g. a subtitle the table shows in another cell). */\n cardOnly?: boolean;\n /** Leave the column out of the table below this width (tablets). */\n hideBelow?: DataTableBreakpoint;\n}\n\nexport interface DataTablePaginateOptions {\n /** Starting rows per page; the first option by default. */\n pageSize?: number;\n /** 25, 50 and 100 by default. */\n pageSizeOptions?: readonly number[];\n /** Remembers the chosen page size in localStorage under this key. */\n storageKey?: string;\n}\n\nexport type SortDirection = 'asc' | 'desc';\n\nexport interface DataTableSort {\n columnId: string;\n direction: SortDirection;\n}\n\nexport interface DataTableProps<T> extends Omit<React.ComponentProps<'div'>, 'children'> {\n /** Required: what the table lists, e.g. \"Workspace members\". */\n 'aria-label': string;\n columns: DataTableColumn<T>[];\n /** The rows to show now: already sorted, filtered and paged by the app or server. */\n rows: T[];\n getRowId: (row: T) => string;\n /** Names a row for screen readers (\"Select Ada Lovelace\"). Defaults to its id. */\n getRowLabel?: (row: T) => string;\n\n /** Controlled sort. A sortable header cycles ascending, descending, none. */\n sort?: DataTableSort | null;\n onSortChange?: (sort: DataTableSort | null) => void;\n\n /** Controlled filter text; a search box appears when onFilterChange is set. */\n filter?: string;\n onFilterChange?: (value: string) => void;\n filterPlaceholder?: string;\n\n /** Controlled selection by row id; checkboxes appear when onSelectedChange is set. */\n selected?: string[];\n onSelectedChange?: (ids: string[]) => void;\n\n /** Enter or a click on the row, e.g. open the record. */\n onRowAction?: (row: T) => void;\n /** Per-row controls in a trailing column, e.g. a DropdownMenu. */\n rowActions?: (row: T) => React.ReactNode;\n\n /** 1-based; Pagination appears when onPageChange is set. */\n page?: number;\n pageCount?: number;\n onPageChange?: (page: number) => void;\n /** e.g. \"1–25 of 312\". Worked out from `totalRows` and `pageSize` when left out. */\n pageSummary?: React.ReactNode;\n /** Rows per page, for the page-size menu and the summary. */\n pageSize?: number;\n pageSizeOptions?: readonly number[];\n /** Adds a rows-per-page menu beside the pages. */\n onPageSizeChange?: (pageSize: number) => void;\n /** Every row on every page (server paging), for the summary. */\n totalRows?: number;\n /**\n * Page in the browser: pass every row in `rows` and the table shows one\n * page, with a rows-per-page menu. It goes back to page 1 when `filter` or\n * `sort` changes. Leave out to page in your app or on the server.\n */\n paginate?: boolean | DataTablePaginateOptions;\n /** Plural noun appended to the built-in summary: \"1–25 of 312 members\". */\n rowNoun?: string;\n\n loading?: boolean;\n /** A message shown in place of the rows. */\n error?: React.ReactNode;\n onRetry?: () => void;\n /** Shown when there are no rows and nothing failed. */\n empty?: React.ReactNode;\n /** Extra controls beside the search box, e.g. filters or a bulk action. */\n toolbar?: React.ReactNode;\n density?: 'compact' | 'default';\n stickyHeader?: boolean;\n /**\n * Below this width the rows become a list of cards (see DataTableColumn\n * `card`). `md` (768px) by default; `false` keeps the table everywhere.\n */\n cardsBelow?: DataTableBreakpoint | false;\n}\n\nconst BREAKPOINTS: Record<DataTableBreakpoint, number> = { sm: 640, md: 768, lg: 1024, xl: 1280 };\n\nconst hideBelowClass: Record<DataTableBreakpoint, string> = {\n sm: 'hidden sm:table-cell',\n md: 'hidden md:table-cell',\n lg: 'hidden lg:table-cell',\n xl: 'hidden xl:table-cell',\n};\n\nconst subscribeNone = () => () => undefined;\n\n/**\n * True while the viewport is narrower than `bp`. Without matchMedia (server\n * render, tests) it is false, so the table is the fallback.\n */\nfunction useNarrowerThan(bp: DataTableBreakpoint | false): boolean {\n const query = bp ? `(max-width: ${BREAKPOINTS[bp] - 0.02}px)` : null;\n const subscribe = React.useCallback(\n (onChange: () => void) => {\n if (!query || typeof matchMedia !== 'function') return () => undefined;\n const mq = matchMedia(query);\n mq.addEventListener?.('change', onChange);\n return () => mq.removeEventListener?.('change', onChange);\n },\n [query],\n );\n return React.useSyncExternalStore(\n query ? subscribe : subscribeNone,\n () => Boolean(query && typeof matchMedia === 'function' && matchMedia(query).matches),\n () => false,\n );\n}\n\nconst textOf = (node: React.ReactNode): string | undefined =>\n typeof node === 'string' || typeof node === 'number' ? String(node) : undefined;\n\nconst nextSort = (current: DataTableSort | null | undefined, id: string): DataTableSort | null => {\n if (!current || current.columnId !== id) return { columnId: id, direction: 'asc' };\n return current.direction === 'asc' ? { columnId: id, direction: 'desc' } : null;\n};\n\n// Clicks and keys aimed at these belong to them, not to the row.\nconst INTERACTIVE = 'a,button,input,select,textarea,[role=\"checkbox\"],[role=\"menuitem\"]';\n\n/**\n * A table recipe for records: sort, search, select, page, and row actions,\n * with loading, empty and error states. Fully controlled, so the data can\n * come from a server; the table never reorders rows itself. On a phone the\n * rows become cards (`cardsBelow`), with the same columns.\n */\nfunction DataTable<T>({\n 'aria-label': ariaLabel,\n columns,\n rows: rowsProp,\n getRowId,\n getRowLabel,\n sort,\n onSortChange,\n filter,\n onFilterChange,\n filterPlaceholder = 'Search',\n selected,\n onSelectedChange,\n onRowAction,\n rowActions,\n page,\n pageCount,\n onPageChange,\n pageSummary,\n pageSize,\n pageSizeOptions,\n onPageSizeChange,\n totalRows,\n paginate,\n rowNoun,\n loading = false,\n error,\n onRetry,\n empty = 'No results.',\n toolbar,\n density = 'default',\n stickyHeader,\n cardsBelow = 'md',\n className,\n ...props\n}: DataTableProps<T>) {\n const paginateOptions = paginate === true ? {} : paginate || undefined;\n const paging = usePagination(rowsProp, {\n defaultPageSize: paginateOptions?.pageSize,\n pageSizeOptions: paginateOptions?.pageSizeOptions,\n storageKey: paginateOptions?.storageKey,\n // A new search or sort starts again at page 1.\n resetKey: `${filter ?? ''}\\u0000${sort?.columnId ?? ''}\\u0000${sort?.direction ?? ''}`,\n });\n const rows = paginateOptions ? paging.rows : rowsProp;\n const cards = useNarrowerThan(cardsBelow);\n const uid = React.useId();\n\n const selectable = Boolean(onSelectedChange);\n const selectedSet = React.useMemo(() => new Set(selected ?? []), [selected]);\n const ids = rows.map(getRowId);\n const pageSelected = ids.filter((id) => selectedSet.has(id)).length;\n const allState: boolean | 'indeterminate' =\n ids.length > 0 && pageSelected === ids.length\n ? true\n : pageSelected > 0\n ? 'indeterminate'\n : false;\n const tableColumns = columns.filter((c) => !c.cardOnly);\n const colCount = tableColumns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0);\n const interactive = Boolean(onRowAction) || selectable;\n\n // One tab stop in the body: the active row. Arrows move it.\n const [active, setActive] = React.useState(0);\n const frameRef = React.useRef<HTMLDivElement>(null);\n // Rows can shrink under the active index (a new page, a filter).\n const activeRow = Math.min(active, Math.max(0, rows.length - 1));\n\n const toggle = (id: string) => {\n if (!onSelectedChange) return;\n const next = new Set(selectedSet);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n onSelectedChange([...next]);\n };\n const toggleAll = () => {\n if (!onSelectedChange) return;\n const next = new Set(selectedSet);\n if (allState === true) ids.forEach((id) => next.delete(id));\n else ids.forEach((id) => next.add(id));\n onSelectedChange([...next]);\n };\n const focusRow = (i: number) => {\n const clamped = Math.min(Math.max(i, 0), rows.length - 1);\n setActive(clamped);\n frameRef.current\n ?.querySelectorAll<HTMLElement>('[data-slot=\"table-body\"] > [data-slot=\"table-row\"]')\n [clamped]?.focus();\n };\n\n const onRowKeyDown = (event: React.KeyboardEvent<HTMLTableRowElement>, i: number, row: T) => {\n if (event.target !== event.currentTarget) return;\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n focusRow(i + 1);\n break;\n case 'ArrowUp':\n event.preventDefault();\n focusRow(i - 1);\n break;\n case 'Home':\n event.preventDefault();\n focusRow(0);\n break;\n case 'End':\n event.preventDefault();\n focusRow(rows.length - 1);\n break;\n case 'Enter':\n if (onRowAction) {\n event.preventDefault();\n onRowAction(row);\n }\n break;\n case ' ':\n if (selectable) {\n event.preventDefault();\n toggle(getRowId(row));\n }\n break;\n }\n };\n\n // Headers are gone on cards, so sorting moves to a menu in the toolbar.\n const sortable = onSortChange ? columns.filter((c) => c.sortable) : [];\n const columnLabel = (c: DataTableColumn<T>) => c.cardLabel ?? textOf(c.header) ?? c.id;\n const cardSort =\n cards && sortable.length > 0 && onSortChange ? (\n <Select\n value={sort ? `${sort.columnId}:${sort.direction}` : 'none'}\n onValueChange={(v) => {\n if (v === 'none') return onSortChange(null);\n const [columnId = '', direction] = v.split(':');\n onSortChange({ columnId, direction: direction === 'desc' ? 'desc' : 'asc' });\n }}\n >\n <SelectTrigger aria-label=\"Sort by\" className=\"pointer-coarse:h-11\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent align=\"end\">\n <SelectItem value=\"none\">Default order</SelectItem>\n {sortable.flatMap((c) => [\n <SelectItem key={`${c.id}:asc`} value={`${c.id}:asc`}>\n {columnLabel(c)} ascending\n </SelectItem>,\n <SelectItem key={`${c.id}:desc`} value={`${c.id}:desc`}>\n {columnLabel(c)} descending\n </SelectItem>,\n ])}\n </SelectContent>\n </Select>\n ) : null;\n const cardSelectAll =\n cards && selectable ? (\n <label\n htmlFor={`${uid}-all`}\n className=\"flex min-h-11 cursor-pointer items-center gap-2.5 pr-1 text-sm sm:min-h-9\"\n >\n <Checkbox\n id={`${uid}-all`}\n checked={allState}\n onCheckedChange={toggleAll}\n disabled={rows.length === 0}\n aria-label=\"Select all rows on this page\"\n />\n Select all\n </label>\n ) : null;\n\n const showToolbar = Boolean(\n onFilterChange || toolbar || (selectable && selectedSet.size) || cardSort || cardSelectAll,\n );\n\n const withNoun = (summary: string, total: number) =>\n rowNoun && total ? `${summary} ${rowNoun}` : summary;\n let pager: React.ReactNode = null;\n if (paginateOptions) {\n if (paging.hasPages)\n pager = (\n <Pagination {...paging.paginationProps} summary={withNoun(paging.summary, paging.total)} />\n );\n } else if (onPageChange && page !== undefined && pageCount !== undefined && pageCount > 0) {\n pager = (\n <Pagination\n page={page}\n pageCount={pageCount}\n onPageChange={onPageChange}\n summary={\n pageSummary ??\n (totalRows !== undefined && pageSize\n ? withNoun(\n paginationSummary(pageWindow({ page, pageSize, total: totalRows })),\n totalRows,\n )\n : undefined)\n }\n pageSize={pageSize}\n pageSizeOptions={pageSizeOptions}\n onPageSizeChange={onPageSizeChange}\n />\n );\n }\n\n const toolbarNode = showToolbar && (\n <div className=\"flex flex-wrap items-center gap-2\">\n {onFilterChange && (\n <div className=\"relative min-w-0 basis-full sm:max-w-xs sm:flex-1 sm:basis-0\">\n <Search\n className=\"pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground\"\n aria-hidden\n />\n <Input\n type=\"search\"\n value={filter ?? ''}\n onChange={(e) => onFilterChange(e.target.value)}\n placeholder={filterPlaceholder}\n aria-label={`Search ${ariaLabel.toLowerCase()}`}\n className=\"pl-8\"\n />\n </div>\n )}\n {cardSelectAll}\n {selectable && (\n <span aria-live=\"polite\" className=\"text-xs text-muted-foreground tabular-nums\">\n {selectedSet.size ? `${selectedSet.size} selected` : ''}\n </span>\n )}\n {(toolbar || cardSort) && (\n <div className=\"ml-auto flex flex-wrap items-center gap-2\">\n {cardSort}\n {toolbar}\n </div>\n )}\n </div>\n );\n\n return (\n <div\n data-slot=\"data-table\"\n data-layout={cards ? 'cards' : 'table'}\n // One minmax(0,1fr) track: a wide table scrolls inside its frame instead\n // of stretching a grid or flex parent.\n className={cn('grid min-w-0 grid-cols-[minmax(0,1fr)] gap-3', className)}\n {...props}\n >\n {toolbarNode}\n {cards ? (\n <DataTableCards\n aria-label={ariaLabel}\n columns={columns}\n rows={rows}\n getRowId={getRowId}\n getRowLabel={getRowLabel}\n selectedSet={selectedSet}\n onToggle={selectable ? toggle : undefined}\n onRowAction={onRowAction}\n rowActions={rowActions}\n loading={loading}\n error={error}\n onRetry={onRetry}\n empty={empty}\n />\n ) : (\n <div ref={frameRef} className=\"rounded-lg border bg-surface\">\n <Table\n aria-label={ariaLabel}\n scrollLabel={ariaLabel}\n aria-busy={loading || undefined}\n density={density}\n stickyHeader={stickyHeader}\n className={cn(loading && rows.length > 0 && 'opacity-60 transition-opacity')}\n >\n <TableHeader>\n <TableRow className=\"hover:bg-transparent\">\n {selectable && (\n <TableHead className=\"w-10\">\n <Checkbox\n checked={allState}\n onCheckedChange={toggleAll}\n disabled={rows.length === 0}\n aria-label=\"Select all rows on this page\"\n />\n </TableHead>\n )}\n {tableColumns.map((col) => {\n const sorted = sort?.columnId === col.id ? sort.direction : undefined;\n return (\n <TableHead\n key={col.id}\n numeric={col.numeric}\n className={cn(col.hideBelow && hideBelowClass[col.hideBelow], col.className)}\n aria-sort={\n col.sortable\n ? sorted === 'asc'\n ? 'ascending'\n : sorted === 'desc'\n ? 'descending'\n : 'none'\n : undefined\n }\n >\n {col.sortable && onSortChange ? (\n <button\n type=\"button\"\n onClick={() => onSortChange(nextSort(sort, col.id))}\n className={cn(\n '-mx-1.5 inline-flex h-7 items-center gap-1 rounded-sm px-1.5 uppercase outline-none hover:bg-muted hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/20',\n sorted && 'text-foreground',\n col.numeric && 'flex-row-reverse',\n )}\n >\n {col.header}\n {sorted === 'asc' ? (\n <ArrowUp className=\"size-3.5\" aria-hidden />\n ) : sorted === 'desc' ? (\n <ArrowDown className=\"size-3.5\" aria-hidden />\n ) : (\n <ChevronsUpDown className=\"size-3.5 opacity-50\" aria-hidden />\n )}\n </button>\n ) : (\n col.header\n )}\n </TableHead>\n );\n })}\n {rowActions && (\n <TableHead className=\"w-12\">\n <span className=\"sr-only\">Actions</span>\n </TableHead>\n )}\n </TableRow>\n </TableHeader>\n <TableBody>\n {error ? (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell\n colSpan={colCount}\n className=\"h-auto py-8 text-center whitespace-normal\"\n >\n <ErrorMessage error={error} onRetry={onRetry} />\n </TableCell>\n </TableRow>\n ) : loading && rows.length === 0 ? (\n Array.from({ length: 3 }, (_, r) => (\n <TableRow key={`loading-${r}`} className=\"hover:bg-transparent\" aria-hidden>\n {Array.from({ length: colCount }, (_, c) => (\n <TableCell key={c}>\n <Skeleton className=\"h-3.5 w-full max-w-40\" />\n </TableCell>\n ))}\n </TableRow>\n ))\n ) : rows.length === 0 ? (\n <TableRow className=\"hover:bg-transparent\">\n <TableCell\n colSpan={colCount}\n className=\"h-auto py-10 text-center whitespace-normal text-muted-foreground\"\n >\n {empty}\n </TableCell>\n </TableRow>\n ) : (\n rows.map((row, i) => {\n const id = getRowId(row);\n const isSelected = selectedSet.has(id);\n const label = getRowLabel?.(row) ?? id;\n return (\n <TableRow\n key={id}\n selected={isSelected}\n tabIndex={interactive ? (i === activeRow ? 0 : -1) : undefined}\n onFocus={() => setActive(i)}\n onKeyDown={interactive ? (e) => onRowKeyDown(e, i, row) : undefined}\n onClick={\n onRowAction\n ? (e) => {\n if ((e.target as HTMLElement).closest(INTERACTIVE)) return;\n onRowAction(row);\n }\n : undefined\n }\n className={cn(\n interactive &&\n 'outline-none focus-visible:bg-muted/60 focus-visible:shadow-[inset_2px_0_0_var(--ring)]',\n onRowAction && 'cursor-pointer',\n )}\n >\n {selectable && (\n <TableCell className=\"w-10\">\n <Checkbox\n checked={isSelected}\n onCheckedChange={() => toggle(id)}\n aria-label={`Select ${label}`}\n tabIndex={-1}\n />\n </TableCell>\n )}\n {tableColumns.map((col) => (\n <TableCell\n key={col.id}\n numeric={col.numeric}\n className={cn(\n col.hideBelow && hideBelowClass[col.hideBelow],\n col.className,\n )}\n >\n {col.cell(row)}\n </TableCell>\n ))}\n {rowActions && (\n <TableCell className=\"w-12 text-right\">{rowActions(row)}</TableCell>\n )}\n </TableRow>\n );\n })\n )}\n </TableBody>\n </Table>\n </div>\n )}\n {pager}\n </div>\n );\n}\n\nfunction ErrorMessage({ error, onRetry }: { error: React.ReactNode; onRetry?: () => void }) {\n return (\n <div role=\"alert\" className=\"grid justify-items-center gap-3 text-sm\">\n <span className=\"text-destructive\">{error}</span>\n {onRetry && (\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={onRetry}>\n Try again\n </Button>\n )}\n </div>\n );\n}\n\nconst cardLabelClass =\n 'text-[11px] font-semibold tracking-[0.06em] text-muted-foreground uppercase';\n\n/**\n * The phone layout: a list of articles, one per row, each named by its title.\n * The title is the row's open button (its hit area covers the card); the\n * checkbox, row actions and footer controls sit above it and keep their own\n * clicks. No roving focus: Tab walks the controls, as in any list of links.\n */\nfunction DataTableCards<T>({\n 'aria-label': ariaLabel,\n columns,\n rows,\n getRowId,\n getRowLabel,\n selectedSet,\n onToggle,\n onRowAction,\n rowActions,\n loading,\n error,\n onRetry,\n empty,\n}: {\n 'aria-label': string;\n columns: DataTableColumn<T>[];\n rows: T[];\n getRowId: (row: T) => string;\n getRowLabel?: (row: T) => string;\n selectedSet: Set<string>;\n onToggle?: (id: string) => void;\n onRowAction?: (row: T) => void;\n rowActions?: (row: T) => React.ReactNode;\n loading: boolean;\n error?: React.ReactNode;\n onRetry?: () => void;\n empty: React.ReactNode;\n}) {\n const uid = React.useId();\n if (error)\n return (\n <div className=\"rounded-lg border bg-surface px-4 py-8\">\n <ErrorMessage error={error} onRetry={onRetry} />\n </div>\n );\n if (rows.length === 0 && !loading)\n return (\n <div className=\"rounded-lg border bg-surface px-4 py-10 text-center text-sm text-muted-foreground\">\n {empty}\n </div>\n );\n\n const visible = columns.filter((c) => c.card !== 'hidden');\n // With no column marked as the title, the first unplaced column is it.\n const firstUnplaced = visible.some((c) => c.card === 'title')\n ? undefined\n : visible.find((c) => c.card === undefined);\n const slotOf = (c: DataTableColumn<T>): DataTableCardSlot =>\n c.card ?? (c === firstUnplaced ? 'title' : 'field');\n const inSlot = (s: DataTableCardSlot) => visible.filter((c) => slotOf(c) === s);\n const title = inSlot('title');\n const subtitle = inSlot('subtitle');\n const aside = inSlot('aside');\n const fields = inSlot('field');\n const footer = inSlot('footer');\n const show = (c: DataTableColumn<T>, row: T) => (c.cardCell ?? c.cell)(row);\n\n return (\n // role=\"list\": Safari drops list semantics from a list with no bullets.\n // eslint-disable-next-line jsx-a11y/no-redundant-roles\n <ul\n role=\"list\"\n aria-label={ariaLabel}\n aria-busy={loading || undefined}\n data-slot=\"data-table-cards\"\n className={cn('grid gap-2', loading && rows.length > 0 && 'opacity-60 transition-opacity')}\n >\n {loading && rows.length === 0\n ? Array.from({ length: 3 }, (_, r) => (\n <li\n key={`loading-${r}`}\n aria-hidden\n className=\"grid gap-3 rounded-lg border bg-surface p-3.5\"\n >\n <Skeleton className=\"h-4 w-1/2\" />\n <Skeleton className=\"h-3.5 w-3/4\" />\n </li>\n ))\n : rows.map((row, i) => {\n const id = getRowId(row);\n const label = getRowLabel?.(row) ?? id;\n const isSelected = selectedSet.has(id);\n const titleId = `${uid}-${i}`;\n return (\n <li key={id}>\n <article\n aria-labelledby={title.length ? titleId : undefined}\n aria-label={title.length ? undefined : label}\n data-slot=\"data-table-card\"\n data-state={isSelected ? 'selected' : undefined}\n className={cn(\n 'relative grid gap-3 rounded-lg border bg-surface p-3.5 shadow-xs transition-colors',\n onRowAction &&\n 'hover:bg-muted/40 has-[[data-row-action]:focus-visible]:border-ring has-[[data-row-action]:focus-visible]:inset-ring-1 has-[[data-row-action]:focus-visible]:inset-ring-ring',\n 'data-[state=selected]:border-brand/40 data-[state=selected]:bg-brand-soft/60',\n )}\n >\n <div className=\"flex items-start gap-3\">\n {onToggle && (\n // A 44px target around the 16px box.\n <label\n htmlFor={`${titleId}-select`}\n className=\"relative z-10 -mx-3.5 -my-3 grid size-11 shrink-0 cursor-pointer place-items-center\"\n >\n <Checkbox\n id={`${titleId}-select`}\n checked={isSelected}\n onCheckedChange={() => onToggle(id)}\n aria-label={`Select ${label}`}\n />\n </label>\n )}\n <div className=\"grid min-w-0 flex-1 gap-0.5\">\n {title.map((c, j) => (\n <div\n key={c.id}\n id={j === 0 ? titleId : undefined}\n className=\"min-w-0 text-[15px] leading-5 font-semibold [overflow-wrap:anywhere]\"\n >\n {onRowAction && j === 0 ? (\n <button\n type=\"button\"\n data-row-action=\"\"\n onClick={() => onRowAction(row)}\n className=\"text-left outline-hidden! after:absolute after:inset-0 after:rounded-lg\"\n >\n {show(c, row)}\n </button>\n ) : (\n show(c, row)\n )}\n </div>\n ))}\n </div>\n {aside.length > 0 && (\n <div className=\"relative z-10 flex shrink-0 flex-col items-end gap-1\">\n {aside.map((c) => (\n <div key={c.id}>{show(c, row)}</div>\n ))}\n </div>\n )}\n {rowActions && (\n <div className=\"relative z-10 -my-2.5 -mr-1.5 shrink-0\">\n {rowActions(row)}\n </div>\n )}\n </div>\n {subtitle.length > 0 && (\n // Under the header row, not beside the status, so it gets the card's width.\n <div className={cn('-mt-2 grid min-w-0 gap-0.5', onToggle && 'pl-7')}>\n {subtitle.map((c) => (\n <div\n key={c.id}\n className=\"min-w-0 text-sm text-muted-foreground [overflow-wrap:anywhere]\"\n >\n {show(c, row)}\n </div>\n ))}\n </div>\n )}\n {fields.length > 0 && (\n <dl className=\"grid grid-cols-2 gap-x-4 gap-y-2.5 text-sm\">\n {fields.map((c) => (\n <div key={c.id} className=\"min-w-0\">\n <dt className={cardLabelClass}>{c.cardLabel ?? c.header}</dt>\n <dd\n className={cn(\n 'mt-0.5 min-w-0 [overflow-wrap:anywhere]',\n c.numeric && 'tabular-nums',\n )}\n >\n {show(c, row)}\n </dd>\n </div>\n ))}\n </dl>\n )}\n {footer.map((c) => (\n <div key={c.id} className=\"relative z-10 flex flex-wrap gap-2\">\n {show(c, row)}\n </div>\n ))}\n </article>\n </li>\n );\n })}\n </ul>\n );\n}\n\nexport { DataTable };\n"],"mappings":";;;;;;;;;;;;;;;AAoIA,IAAM,cAAmD;CAAE,IAAI;CAAK,IAAI;CAAK,IAAI;CAAM,IAAI;AAAK;AAEhG,IAAM,iBAAsD;CAC1D,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;AAEA,IAAM,4BAA4B,KAAA;;;;;AAMlC,SAAS,gBAAgB,IAA0C;CACjE,MAAM,QAAQ,KAAK,eAAe,YAAY,MAAM,IAAK,OAAO;CAChE,MAAM,YAAY,MAAM,aACrB,aAAyB;EACxB,IAAI,CAAC,SAAS,OAAO,eAAe,YAAY,aAAa,KAAA;EAC7D,MAAM,KAAK,WAAW,KAAK;EAC3B,GAAG,mBAAmB,UAAU,QAAQ;EACxC,aAAa,GAAG,sBAAsB,UAAU,QAAQ;CAC1D,GACA,CAAC,KAAK,CACR;CACA,OAAO,MAAM,qBACX,QAAQ,YAAY,qBACd,QAAQ,SAAS,OAAO,eAAe,cAAc,WAAW,KAAK,CAAC,CAAC,OAAO,SAC9E,KACR;AACF;AAEA,IAAM,UAAU,SACd,OAAO,SAAS,YAAY,OAAO,SAAS,WAAW,OAAO,IAAI,IAAI,KAAA;AAExE,IAAM,YAAY,SAA2C,OAAqC;CAChG,IAAI,CAAC,WAAW,QAAQ,aAAa,IAAI,OAAO;EAAE,UAAU;EAAI,WAAW;CAAM;CACjF,OAAO,QAAQ,cAAc,QAAQ;EAAE,UAAU;EAAI,WAAW;CAAO,IAAI;AAC7E;AAGA,IAAM,cAAc;;;;;;;AAQpB,SAAS,UAAa,EACpB,cAAc,WACd,SACA,MAAM,UACN,UACA,aACA,MACA,cACA,QACA,gBACA,oBAAoB,UACpB,UACA,kBACA,aACA,YACA,MACA,WACA,cACA,aACA,UACA,iBACA,kBACA,WACA,UACA,SACA,UAAU,OACV,OACA,SACA,QAAQ,eACR,SACA,UAAU,WACV,cACA,aAAa,MACb,WACA,GAAG,SACiB;CACpB,MAAM,kBAAkB,aAAa,OAAO,CAAC,IAAI,YAAY,KAAA;CAC7D,MAAM,SAAS,cAAc,UAAU;EACrC,iBAAiB,iBAAiB;EAClC,iBAAiB,iBAAiB;EAClC,YAAY,iBAAiB;EAE7B,UAAU,GAAG,UAAU,GAAG,QAAQ,MAAM,YAAY,GAAG,QAAQ,MAAM,aAAa;CACpF,CAAC;CACD,MAAM,OAAO,kBAAkB,OAAO,OAAO;CAC7C,MAAM,QAAQ,gBAAgB,UAAU;CACxC,MAAM,MAAM,MAAM,MAAM;CAExB,MAAM,aAAa,QAAQ,gBAAgB;CAC3C,MAAM,cAAc,MAAM,cAAc,IAAI,IAAI,YAAY,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;CAC3E,MAAM,MAAM,KAAK,IAAI,QAAQ;CAC7B,MAAM,eAAe,IAAI,QAAQ,OAAO,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC;CAC7D,MAAM,WACJ,IAAI,SAAS,KAAK,iBAAiB,IAAI,SACnC,OACA,eAAe,IACb,kBACA;CACR,MAAM,eAAe,QAAQ,QAAQ,MAAM,CAAC,EAAE,QAAQ;CACtD,MAAM,WAAW,aAAa,UAAU,aAAa,IAAI,MAAM,aAAa,IAAI;CAChF,MAAM,cAAc,QAAQ,WAAW,KAAK;CAG5C,MAAM,CAAC,QAAQ,aAAa,MAAM,SAAS,CAAC;CAC5C,MAAM,WAAW,MAAM,OAAuB,IAAI;CAElD,MAAM,YAAY,KAAK,IAAI,QAAQ,KAAK,IAAI,GAAG,KAAK,SAAS,CAAC,CAAC;CAE/D,MAAM,UAAU,OAAe;EAC7B,IAAI,CAAC,kBAAkB;EACvB,MAAM,OAAO,IAAI,IAAI,WAAW;EAChC,IAAI,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,EAAE;OAC3B,KAAK,IAAI,EAAE;EAChB,iBAAiB,CAAC,GAAG,IAAI,CAAC;CAC5B;CACA,MAAM,kBAAkB;EACtB,IAAI,CAAC,kBAAkB;EACvB,MAAM,OAAO,IAAI,IAAI,WAAW;EAChC,IAAI,aAAa,MAAM,IAAI,SAAS,OAAO,KAAK,OAAO,EAAE,CAAC;OACrD,IAAI,SAAS,OAAO,KAAK,IAAI,EAAE,CAAC;EACrC,iBAAiB,CAAC,GAAG,IAAI,CAAC;CAC5B;CACA,MAAM,YAAY,MAAc;EAC9B,MAAM,UAAU,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC;EACxD,UAAU,OAAO;EACjB,SAAS,SACL,iBAA8B,wDAAoD,CAAC,CACpF,QAAQ,EAAE,MAAM;CACrB;CAEA,MAAM,gBAAgB,OAAiD,GAAW,QAAW;EAC3F,IAAI,MAAM,WAAW,MAAM,eAAe;EAC1C,QAAQ,MAAM,KAAd;GACE,KAAK;IACH,MAAM,eAAe;IACrB,SAAS,IAAI,CAAC;IACd;GACF,KAAK;IACH,MAAM,eAAe;IACrB,SAAS,IAAI,CAAC;IACd;GACF,KAAK;IACH,MAAM,eAAe;IACrB,SAAS,CAAC;IACV;GACF,KAAK;IACH,MAAM,eAAe;IACrB,SAAS,KAAK,SAAS,CAAC;IACxB;GACF,KAAK;IACH,IAAI,aAAa;KACf,MAAM,eAAe;KACrB,YAAY,GAAG;IACjB;IACA;GACF,KAAK,KACH,IAAI,YAAY;IACd,MAAM,eAAe;IACrB,OAAO,SAAS,GAAG,CAAC;GACtB;EAEJ;CACF;CAGA,MAAM,WAAW,eAAe,QAAQ,QAAQ,MAAM,EAAE,QAAQ,IAAI,CAAC;CACrE,MAAM,eAAe,MAA0B,EAAE,aAAa,OAAO,EAAE,MAAM,KAAK,EAAE;CACpF,MAAM,WACJ,SAAS,SAAS,SAAS,KAAK,eAC9B,qBAAC,QAAD;EACE,OAAO,OAAO,GAAG,KAAK,SAAS,GAAG,KAAK,cAAc;EACrD,gBAAgB,MAAM;GACpB,IAAI,MAAM,QAAQ,OAAO,aAAa,IAAI;GAC1C,MAAM,CAAC,WAAW,IAAI,aAAa,EAAE,MAAM,GAAG;GAC9C,aAAa;IAAE;IAAU,WAAW,cAAc,SAAS,SAAS;GAAM,CAAC;EAC7E;EANF,UAAA,CAQE,oBAAC,eAAD;GAAe,cAAW;GAAU,WAAU;GAC5C,UAAA,oBAAC,aAAD,CAAc,CAAA;EACD,CAAA,GACf,qBAAC,eAAD;GAAe,OAAM;GAArB,UAAA,CACE,oBAAC,YAAD;IAAY,OAAM;IAAO,UAAA;GAAyB,CAAA,GACjD,SAAS,SAAS,MAAM,CACvB,qBAAC,YAAD;IAAgC,OAAO,GAAG,EAAE,GAAG;IAA/C,UAAA,CACG,YAAY,CAAC,GAAE,YACN;GAFK,GAAA,GAAG,EAAE,GAAG,KAEb,GACZ,qBAAC,YAAD;IAAiC,OAAO,GAAG,EAAE,GAAG;IAAhD,UAAA,CACG,YAAY,CAAC,GAAE,aACN;GAFK,GAAA,GAAG,EAAE,GAAG,MAEb,CACd,CAAC,CACY;EACT,CAAA,CAAA;CACN,CAAA,IAAA;CACN,MAAM,gBACJ,SAAS,aACP,qBAAC,SAAD;EACE,SAAS,GAAG,IAAI;EAChB,WAAU;EAFZ,UAAA,CAIE,oBAAC,UAAD;GACE,IAAI,GAAG,IAAI;GACX,SAAS;GACT,iBAAiB;GACjB,UAAU,KAAK,WAAW;GAC1B,cAAW;EACZ,CAAA,GAAC,YAEG;CACL,CAAA,IAAA;CAEN,MAAM,cAAc,QAClB,kBAAkB,WAAY,cAAc,YAAY,QAAS,YAAY,aAC/E;CAEA,MAAM,YAAY,SAAiB,UACjC,WAAW,QAAQ,GAAG,QAAQ,GAAG,YAAY;CAC/C,IAAI,QAAyB;CAC7B,IAAI,iBACE;MAAA,OAAO,UACT,QACE,oBAAC,YAAD;GAAY,GAAI,OAAO;GAAiB,SAAS,SAAS,OAAO,SAAS,OAAO,KAAK;EAAI,CAAA;CAAA,OAEzF,IAAI,gBAAgB,SAAS,KAAA,KAAa,cAAc,KAAA,KAAa,YAAY,GACtF,QACE,oBAAC,YAAD;EACQ;EACK;EACG;EACd,SACE,gBACC,cAAc,KAAA,KAAa,WACxB,SACE,kBAAkB,WAAW;GAAE;GAAM;GAAU,OAAO;EAAU,CAAC,CAAC,GAClE,SACF,IACA,KAAA;EAEI;EACO;EACC;CACnB,CAAA;CAIL,MAAM,cAAc,eAClB,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACG,kBACC,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,QAAD;KACE,WAAU;KACV,eAAA;IACD,CAAA,GACD,oBAAC,OAAD;KACE,MAAK;KACL,OAAO,UAAU;KACjB,WAAW,MAAM,eAAe,EAAE,OAAO,KAAK;KAC9C,aAAa;KACb,cAAY,UAAU,UAAU,YAAY;KAC5C,WAAU;IACX,CAAA,CACE;;GAEN;GACA,cACC,oBAAC,QAAD;IAAM,aAAU;IAAS,WAAU;IAChC,UAAA,YAAY,OAAO,GAAG,YAAY,KAAK,aAAa;GACjD,CAAA;IAEN,WAAW,aACX,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACG,UACA,OACE;;EAEJ;;CAGP,OACE,qBAAC,OAAD;EACE,aAAU;EACV,eAAa,QAAQ,UAAU;EAG/B,WAAW,GAAG,gDAAgD,SAAS;EACvE,GAAI;EANN,UAAA;GAQG;GACA,QACC,oBAAC,gBAAD;IACE,cAAY;IACH;IACH;IACI;IACG;IACA;IACb,UAAU,aAAa,SAAS,KAAA;IACnB;IACD;IACH;IACF;IACE;IACF;GACR,CAAA,IAED,oBAAC,OAAD;IAAK,KAAK;IAAU,WAAU;IAC5B,UAAA,qBAAC,OAAD;KACE,cAAY;KACZ,aAAa;KACb,aAAW,WAAW,KAAA;KACb;KACK;KACd,WAAW,GAAG,WAAW,KAAK,SAAS,KAAK,+BAA+B;KAN7E,UAAA,CAQE,oBAAC,aAAD,EAAA,UACE,qBAAC,UAAD;MAAU,WAAU;MAApB,UAAA;OACG,cACC,oBAAC,WAAD;QAAW,WAAU;QACnB,UAAA,oBAAC,UAAD;SACE,SAAS;SACT,iBAAiB;SACjB,UAAU,KAAK,WAAW;SAC1B,cAAW;QACZ,CAAA;OACQ,CAAA;OAEZ,aAAa,KAAK,QAAQ;QACzB,MAAM,SAAS,MAAM,aAAa,IAAI,KAAK,KAAK,YAAY,KAAA;QAC5D,OACE,oBAAC,WAAD;SAEE,SAAS,IAAI;SACb,WAAW,GAAG,IAAI,aAAa,eAAe,IAAI,YAAY,IAAI,SAAS;SAC3E,aACE,IAAI,WACA,WAAW,QACT,cACA,WAAW,SACT,eACA,SACJ,KAAA;SAGL,UAAA,IAAI,YAAY,eACf,qBAAC,UAAD;UACE,MAAK;UACL,eAAe,aAAa,SAAS,MAAM,IAAI,EAAE,CAAC;UAClD,WAAW,GACT,gLACA,UAAU,mBACV,IAAI,WAAW,kBACjB;UAPF,UAAA,CASG,IAAI,QACJ,WAAW,QACV,oBAAC,SAAD;WAAS,WAAU;WAAW,eAAA;UAAa,CAAA,IACzC,WAAW,SACb,oBAAC,WAAD;WAAW,WAAU;WAAW,eAAA;UAAa,CAAA,IAE7C,oBAAC,gBAAD;WAAgB,WAAU;WAAsB,eAAA;UAAa,CAAA,CAEzD;SAER,CAAA,IAAA,IAAI;QAEG,GAnCJ,IAAI,EAmCA;OAEf,CAAC;OACA,cACC,oBAAC,WAAD;QAAW,WAAU;QACnB,UAAA,oBAAC,QAAD;SAAM,WAAU;SAAU,UAAA;QAAa,CAAA;OAC9B,CAAA;MAEL;KACC,CAAA,EAAA,CAAA,GACb,oBAAC,WAAD,EAAA,UACG,QACC,oBAAC,UAAD;MAAU,WAAU;MAClB,UAAA,oBAAC,WAAD;OACE,SAAS;OACT,WAAU;OAEV,UAAA,oBAAC,cAAD;QAAqB;QAAgB;OAAU,CAAA;MACtC,CAAA;KACH,CAAA,IACR,WAAW,KAAK,WAAW,IAC7B,MAAM,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,MAC5B,oBAAC,UAAD;MAA+B,WAAU;MAAuB,eAAA;MAC7D,UAAA,MAAM,KAAK,EAAE,QAAQ,SAAS,IAAI,GAAG,MACpC,oBAAC,WAAD,EAAA,UACE,oBAAC,UAAD,EAAU,WAAU,wBAAyB,CAAA,EACpC,GAFK,CAEL,CACZ;KACO,GANK,WAAW,GAMhB,CACX,IACC,KAAK,WAAW,IAClB,oBAAC,UAAD;MAAU,WAAU;MAClB,UAAA,oBAAC,WAAD;OACE,SAAS;OACT,WAAU;OAET,UAAA;MACQ,CAAA;KACH,CAAA,IAEV,KAAK,KAAK,KAAK,MAAM;MACnB,MAAM,KAAK,SAAS,GAAG;MACvB,MAAM,aAAa,YAAY,IAAI,EAAE;MACrC,MAAM,QAAQ,cAAc,GAAG,KAAK;MACpC,OACE,qBAAC,UAAD;OAEE,UAAU;OACV,UAAU,cAAe,MAAM,YAAY,IAAI,KAAM,KAAA;OACrD,eAAe,UAAU,CAAC;OAC1B,WAAW,eAAe,MAAM,aAAa,GAAG,GAAG,GAAG,IAAI,KAAA;OAC1D,SACE,eACK,MAAM;QACL,IAAK,EAAE,OAAuB,QAAQ,WAAW,GAAG;QACpD,YAAY,GAAG;OACjB,IACA,KAAA;OAEN,WAAW,GACT,eACE,2FACF,eAAe,gBACjB;OAlBF,UAAA;QAoBG,cACC,oBAAC,WAAD;SAAW,WAAU;SACnB,UAAA,oBAAC,UAAD;UACE,SAAS;UACT,uBAAuB,OAAO,EAAE;UAChC,cAAY,UAAU;UACtB,UAAU;SACX,CAAA;QACQ,CAAA;QAEZ,aAAa,KAAK,QACjB,oBAAC,WAAD;SAEE,SAAS,IAAI;SACb,WAAW,GACT,IAAI,aAAa,eAAe,IAAI,YACpC,IAAI,SACN;SAEC,UAAA,IAAI,KAAK,GAAG;QACJ,GARJ,IAAI,EAQA,CACZ;QACA,cACC,oBAAC,WAAD;SAAW,WAAU;SAAmB,UAAA,WAAW,GAAG;QAAa,CAAA;OAE7D;MA5CH,GAAA,EA4CG;KAEd,CAAC,EAEM,CAAA,CACN;;GACJ,CAAA;GAEN;EACE;;AAET;AAEA,SAAS,aAAa,EAAE,OAAO,WAA6D;CAC1F,OACE,qBAAC,OAAD;EAAK,MAAK;EAAQ,WAAU;EAA5B,UAAA,CACE,oBAAC,QAAD;GAAM,WAAU;GAAoB,UAAA;EAAY,CAAA,GAC/C,WACC,oBAAC,QAAD;GAAQ,MAAK;GAAS,MAAK;GAAK,SAAQ;GAAU,SAAS;GAAS,UAAA;EAE5D,CAAA,CAEP;;AAET;AAEA,IAAM,iBACJ;;;;;;;AAQF,SAAS,eAAkB,EACzB,cAAc,WACd,SACA,MACA,UACA,aACA,aACA,UACA,aACA,YACA,SACA,OACA,SACA,SAeC;CACD,MAAM,MAAM,MAAM,MAAM;CACxB,IAAI,OACF,OACE,oBAAC,OAAD;EAAK,WAAU;EACb,UAAA,oBAAC,cAAD;GAAqB;GAAgB;EAAU,CAAA;CAC5C,CAAA;CAET,IAAI,KAAK,WAAW,KAAK,CAAC,SACxB,OACE,oBAAC,OAAD;EAAK,WAAU;EACZ,UAAA;CACE,CAAA;CAGT,MAAM,UAAU,QAAQ,QAAQ,MAAM,EAAE,SAAS,QAAQ;CAEzD,MAAM,gBAAgB,QAAQ,MAAM,MAAM,EAAE,SAAS,OAAO,IACxD,KAAA,IACA,QAAQ,MAAM,MAAM,EAAE,SAAS,KAAA,CAAS;CAC5C,MAAM,UAAU,MACd,EAAE,SAAS,MAAM,gBAAgB,UAAU;CAC7C,MAAM,UAAU,MAAyB,QAAQ,QAAQ,MAAM,OAAO,CAAC,MAAM,CAAC;CAC9E,MAAM,QAAQ,OAAO,OAAO;CAC5B,MAAM,WAAW,OAAO,UAAU;CAClC,MAAM,QAAQ,OAAO,OAAO;CAC5B,MAAM,SAAS,OAAO,OAAO;CAC7B,MAAM,SAAS,OAAO,QAAQ;CAC9B,MAAM,QAAQ,GAAuB,SAAY,EAAE,YAAY,EAAE,KAAA,CAAM,GAAG;CAE1E,OAGE,oBAAC,MAAD;EACE,MAAK;EACL,cAAY;EACZ,aAAW,WAAW,KAAA;EACtB,aAAU;EACV,WAAW,GAAG,cAAc,WAAW,KAAK,SAAS,KAAK,+BAA+B;EAExF,UAAA,WAAW,KAAK,WAAW,IACxB,MAAM,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,MAC5B,qBAAC,MAAD;GAEE,eAAA;GACA,WAAU;GAHZ,UAAA,CAKE,oBAAC,UAAD,EAAU,WAAU,YAAa,CAAA,GACjC,oBAAC,UAAD,EAAU,WAAU,cAAe,CAAA,CACjC;EANG,GAAA,WAAW,GAMd,CACL,IACD,KAAK,KAAK,KAAK,MAAM;GACnB,MAAM,KAAK,SAAS,GAAG;GACvB,MAAM,QAAQ,cAAc,GAAG,KAAK;GACpC,MAAM,aAAa,YAAY,IAAI,EAAE;GACrC,MAAM,UAAU,GAAG,IAAI,GAAG;GAC1B,OACE,oBAAC,MAAD,EAAA,UACE,qBAAC,WAAD;IACE,mBAAiB,MAAM,SAAS,UAAU,KAAA;IAC1C,cAAY,MAAM,SAAS,KAAA,IAAY;IACvC,aAAU;IACV,cAAY,aAAa,aAAa,KAAA;IACtC,WAAW,GACT,sFACA,eACE,gLACF,8EACF;IAVF,UAAA;KAYE,qBAAC,OAAD;MAAK,WAAU;MAAf,UAAA;OACG,YAEC,oBAAC,SAAD;QACE,SAAS,GAAG,QAAQ;QACpB,WAAU;QAEV,UAAA,oBAAC,UAAD;SACE,IAAI,GAAG,QAAQ;SACf,SAAS;SACT,uBAAuB,SAAS,EAAE;SAClC,cAAY,UAAU;QACvB,CAAA;OACI,CAAA;OAET,oBAAC,OAAD;QAAK,WAAU;QACZ,UAAA,MAAM,KAAK,GAAG,MACb,oBAAC,OAAD;SAEE,IAAI,MAAM,IAAI,UAAU,KAAA;SACxB,WAAU;SAET,UAAA,eAAe,MAAM,IACpB,oBAAC,UAAD;UACE,MAAK;UACL,mBAAgB;UAChB,eAAe,YAAY,GAAG;UAC9B,WAAU;UAET,UAAA,KAAK,GAAG,GAAG;SACN,CAAA,IAER,KAAK,GAAG,GAAG;QAEV,GAhBE,EAAE,EAgBJ,CACN;OACE,CAAA;OACJ,MAAM,SAAS,KACd,oBAAC,OAAD;QAAK,WAAU;QACZ,UAAA,MAAM,KAAK,MACV,oBAAC,OAAD,EAAA,UAAiB,KAAK,GAAG,GAAG,EAAO,GAAzB,EAAE,EAAuB,CACpC;OACE,CAAA;OAEN,cACC,oBAAC,OAAD;QAAK,WAAU;QACZ,UAAA,WAAW,GAAG;OACZ,CAAA;MAEJ;;KACJ,SAAS,SAAS,KAEjB,oBAAC,OAAD;MAAK,WAAW,GAAG,8BAA8B,YAAY,MAAM;MAChE,UAAA,SAAS,KAAK,MACb,oBAAC,OAAD;OAEE,WAAU;OAET,UAAA,KAAK,GAAG,GAAG;MACT,GAJE,EAAE,EAIJ,CACN;KACE,CAAA;KAEN,OAAO,SAAS,KACf,oBAAC,MAAD;MAAI,WAAU;MACX,UAAA,OAAO,KAAK,MACX,qBAAC,OAAD;OAAgB,WAAU;OAA1B,UAAA,CACE,oBAAC,MAAD;QAAI,WAAW;QAAiB,UAAA,EAAE,aAAa,EAAE;OAAW,CAAA,GAC5D,oBAAC,MAAD;QACE,WAAW,GACT,2CACA,EAAE,WAAW,cACf;QAEC,UAAA,KAAK,GAAG,GAAG;OACV,CAAA,CACD;MAVK,GAAA,EAAE,EAUP,CACN;KACC,CAAA;KAEL,OAAO,KAAK,MACX,oBAAC,OAAD;MAAgB,WAAU;MACvB,UAAA,KAAK,GAAG,GAAG;KACT,GAFK,EAAE,EAEP,CACN;IACM;GACP,CAAA,EAAA,GAnGK,EAmGL;EAER,CAAC;CACH,CAAA;AAER"}
|
|
@@ -5,8 +5,13 @@ declare function DialogTrigger(props: React.ComponentProps<typeof DialogPrimitiv
|
|
|
5
5
|
declare function DialogClose(props: React.ComponentProps<typeof DialogPrimitive.Close>): React.JSX.Element;
|
|
6
6
|
declare function DialogPortal(props: React.ComponentProps<typeof DialogPrimitive.Portal>): React.JSX.Element;
|
|
7
7
|
declare function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>): React.JSX.Element;
|
|
8
|
-
declare function DialogContent({ className, children, showCloseButton, ...props }: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
8
|
+
declare function DialogContent({ className, children, showCloseButton, mobile, ...props }: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
9
9
|
showCloseButton?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* `sheet`: on phones (below 640px) the dialog is a bottom sheet, full
|
|
12
|
+
* width with a grab bar. Larger screens keep the centred window.
|
|
13
|
+
*/
|
|
14
|
+
mobile?: 'center' | 'sheet';
|
|
10
15
|
}): React.JSX.Element;
|
|
11
16
|
declare function DialogHeader({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
|
|
12
17
|
declare function DialogFooter({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
|
|
@@ -35,19 +35,30 @@ function DialogOverlay({ className, ...props }) {
|
|
|
35
35
|
...props
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
var bottomSheetClasses = "max-sm:top-auto max-sm:bottom-0 max-sm:left-0 max-sm:w-full max-sm:max-w-none max-sm:translate-x-0 max-sm:translate-y-0 max-sm:max-h-[92dvh] max-sm:rounded-b-none max-sm:border-x-0 max-sm:border-b-0 max-sm:pb-[max(1.25rem,env(safe-area-inset-bottom))] max-sm:[--bl-scale:1] max-sm:[--bl-ty:100%]";
|
|
39
|
+
function DialogContent({ className, children, showCloseButton = true, mobile = "center", ...props }) {
|
|
40
|
+
const sheet = mobile === "sheet";
|
|
39
41
|
return /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsxs(Dialog.Content, {
|
|
40
42
|
"data-slot": "dialog-content",
|
|
41
|
-
|
|
43
|
+
"data-mobile": mobile,
|
|
44
|
+
className: cn("fixed top-1/2 left-1/2 z-50 grid [--bl-scale:0.95] [--bl-ty:8px] w-[calc(100%-2rem)] max-w-lg max-h-[calc(100dvh-2rem)] overflow-y-auto overscroll-contain -translate-x-1/2 -translate-y-1/2 gap-5 rounded-xl border border-border-strong bg-surface-raised p-5 text-foreground shadow-[0_24px_80px_rgb(0_0_0_/_0.28)] outline-none data-[state=closed]:animate-out data-[state=open]:animate-in sm:p-6", sheet && bottomSheetClasses, className),
|
|
42
45
|
...props,
|
|
43
|
-
children: [
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
className: "
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
children: [
|
|
47
|
+
sheet && /* @__PURE__ */ jsx("div", {
|
|
48
|
+
"data-slot": "dialog-handle",
|
|
49
|
+
"aria-hidden": true,
|
|
50
|
+
className: "mx-auto -mt-2 -mb-3 hidden h-1 w-10 shrink-0 rounded-full bg-border-strong max-sm:block"
|
|
51
|
+
}),
|
|
52
|
+
children,
|
|
53
|
+
showCloseButton ? /* @__PURE__ */ jsxs(Dialog.Close, {
|
|
54
|
+
"data-slot": "dialog-close",
|
|
55
|
+
className: cn("absolute top-3.5 right-3.5 grid size-8 place-items-center rounded-md text-muted-foreground outline-none transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/20 [&_svg]:size-4", sheet && "max-sm:top-3 max-sm:right-3 pointer-coarse:max-sm:size-11"),
|
|
56
|
+
children: [/* @__PURE__ */ jsx(X, { "aria-hidden": true }), /* @__PURE__ */ jsx("span", {
|
|
57
|
+
className: "sr-only",
|
|
58
|
+
children: "Close"
|
|
59
|
+
})]
|
|
60
|
+
}) : null
|
|
61
|
+
]
|
|
51
62
|
})] });
|
|
52
63
|
}
|
|
53
64
|
function DialogHeader({ className, ...props }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog.js","names":["Dialog","DialogPrimitive"],"sources":["../../src/components/dialog.tsx"],"sourcesContent":["import X from '@burtson-labs/icons/react/x';\nimport { Dialog as DialogPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nfunction Dialog(props: React.ComponentProps<typeof DialogPrimitive.Root>) {\n return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger(props: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogClose(props: React.ComponentProps<typeof DialogPrimitive.Close>) {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogPortal(props: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogOverlay({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n return (\n <DialogPrimitive.Overlay\n data-slot=\"dialog-overlay\"\n className={cn(\n 'fixed inset-0 z-50 bg-black/55 backdrop-blur-[3px] data-[state=closed]:animate-fade-out data-[state=open]:animate-fade-in',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction DialogContent({\n className,\n children,\n showCloseButton = true,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
1
|
+
{"version":3,"file":"dialog.js","names":["Dialog","DialogPrimitive"],"sources":["../../src/components/dialog.tsx"],"sourcesContent":["import X from '@burtson-labs/icons/react/x';\nimport { Dialog as DialogPrimitive } from 'radix-ui';\nimport * as React from 'react';\n\nimport { cn } from '../lib/utils';\n\nfunction Dialog(props: React.ComponentProps<typeof DialogPrimitive.Root>) {\n return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger(props: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogClose(props: React.ComponentProps<typeof DialogPrimitive.Close>) {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogPortal(props: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogOverlay({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n return (\n <DialogPrimitive.Overlay\n data-slot=\"dialog-overlay\"\n className={cn(\n 'fixed inset-0 z-50 bg-black/55 backdrop-blur-[3px] data-[state=closed]:animate-fade-out data-[state=open]:animate-fade-in',\n className,\n )}\n {...props}\n />\n );\n}\n\n// Below 640px the dialog docks to the bottom edge, full width, and slides up:\n// thumbs reach its buttons and the keyboard does not cover a centred box.\nconst bottomSheetClasses =\n 'max-sm:top-auto max-sm:bottom-0 max-sm:left-0 max-sm:w-full max-sm:max-w-none max-sm:translate-x-0 max-sm:translate-y-0 max-sm:max-h-[92dvh] max-sm:rounded-b-none max-sm:border-x-0 max-sm:border-b-0 max-sm:pb-[max(1.25rem,env(safe-area-inset-bottom))] max-sm:[--bl-scale:1] max-sm:[--bl-ty:100%]';\n\nfunction DialogContent({\n className,\n children,\n showCloseButton = true,\n mobile = 'center',\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Content> & {\n showCloseButton?: boolean;\n /**\n * `sheet`: on phones (below 640px) the dialog is a bottom sheet, full\n * width with a grab bar. Larger screens keep the centred window.\n */\n mobile?: 'center' | 'sheet';\n}) {\n const sheet = mobile === 'sheet';\n return (\n <DialogPortal>\n <DialogOverlay />\n <DialogPrimitive.Content\n data-slot=\"dialog-content\"\n data-mobile={mobile}\n className={cn(\n 'fixed top-1/2 left-1/2 z-50 grid [--bl-scale:0.95] [--bl-ty:8px] w-[calc(100%-2rem)] max-w-lg max-h-[calc(100dvh-2rem)] overflow-y-auto overscroll-contain -translate-x-1/2 -translate-y-1/2 gap-5 rounded-xl border border-border-strong bg-surface-raised p-5 text-foreground shadow-[0_24px_80px_rgb(0_0_0_/_0.28)] outline-none data-[state=closed]:animate-out data-[state=open]:animate-in sm:p-6',\n sheet && bottomSheetClasses,\n className,\n )}\n {...props}\n >\n {sheet && (\n <div\n data-slot=\"dialog-handle\"\n aria-hidden\n className=\"mx-auto -mt-2 -mb-3 hidden h-1 w-10 shrink-0 rounded-full bg-border-strong max-sm:block\"\n />\n )}\n {children}\n {showCloseButton ? (\n <DialogPrimitive.Close\n data-slot=\"dialog-close\"\n className={cn(\n 'absolute top-3.5 right-3.5 grid size-8 place-items-center rounded-md text-muted-foreground outline-none transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/20 [&_svg]:size-4',\n // 44px on a touch screen when it is a sheet, like Sheet's.\n sheet && 'max-sm:top-3 max-sm:right-3 pointer-coarse:max-sm:size-11',\n )}\n >\n <X aria-hidden />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n ) : null}\n </DialogPrimitive.Content>\n </DialogPortal>\n );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div data-slot=\"dialog-header\" className={cn('grid gap-1.5 pr-7', className)} {...props} />\n );\n}\n\nfunction DialogFooter({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"dialog-footer\"\n className={cn(\n 'flex flex-col-reverse gap-2 border-t border-border pt-4 sm:flex-row sm:justify-end',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {\n return (\n <DialogPrimitive.Title\n data-slot=\"dialog-title\"\n className={cn('text-base font-semibold tracking-[-0.02em]', className)}\n {...props}\n />\n );\n}\n\nfunction DialogDescription({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n return (\n <DialogPrimitive.Description\n data-slot=\"dialog-description\"\n className={cn('text-sm leading-5 text-muted-foreground', className)}\n {...props}\n />\n );\n}\n\nexport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n};\n"],"mappings":";;;;;;AAMA,SAASA,SAAO,OAA0D;CACxE,OAAO,oBAACC,OAAgB,MAAjB;EAAsB,aAAU;EAAS,GAAI;CAAQ,CAAA;AAC9D;AAEA,SAAS,cAAc,OAA6D;CAClF,OAAO,oBAACA,OAAgB,SAAjB;EAAyB,aAAU;EAAiB,GAAI;CAAQ,CAAA;AACzE;AAEA,SAAS,YAAY,OAA2D;CAC9E,OAAO,oBAACA,OAAgB,OAAjB;EAAuB,aAAU;EAAe,GAAI;CAAQ,CAAA;AACrE;AAEA,SAAS,aAAa,OAA4D;CAChF,OAAO,oBAACA,OAAgB,QAAjB;EAAwB,aAAU;EAAgB,GAAI;CAAQ,CAAA;AACvE;AAEA,SAAS,cAAc,EACrB,WACA,GAAG,SACoD;CACvD,OACE,oBAACA,OAAgB,SAAjB;EACE,aAAU;EACV,WAAW,GACT,6HACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAIA,IAAM,qBACJ;AAEF,SAAS,cAAc,EACrB,WACA,UACA,kBAAkB,MAClB,SAAS,UACT,GAAG,SAQF;CACD,MAAM,QAAQ,WAAW;CACzB,OACE,qBAAC,cAAD,EAAA,UAAA,CACE,oBAAC,eAAD,CAAgB,CAAA,GAChB,qBAACA,OAAgB,SAAjB;EACE,aAAU;EACV,eAAa;EACb,WAAW,GACT,2YACA,SAAS,oBACT,SACF;EACA,GAAI;EARN,UAAA;GAUG,SACC,oBAAC,OAAD;IACE,aAAU;IACV,eAAA;IACA,WAAU;GACX,CAAA;GAEF;GACA,kBACC,qBAACA,OAAgB,OAAjB;IACE,aAAU;IACV,WAAW,GACT,qOAEA,SAAS,2DACX;IANF,UAAA,CAQE,oBAAC,GAAD,EAAG,eAAA,KAAa,CAAA,GAChB,oBAAC,QAAD;KAAM,WAAU;KAAU,UAAA;IAAW,CAAA,CAChB;GACrB,CAAA,IAAA;EACmB;CACb,CAAA,CAAA,EAAA,CAAA;AAElB;AAEA,SAAS,aAAa,EAAE,WAAW,GAAG,SAAsC;CAC1E,OACE,oBAAC,OAAD;EAAK,aAAU;EAAgB,WAAW,GAAG,qBAAqB,SAAS;EAAG,GAAI;CAAQ,CAAA;AAE9F;AAEA,SAAS,aAAa,EAAE,WAAW,GAAG,SAAsC;CAC1E,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,sFACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,SAA6D;CAChG,OACE,oBAACA,OAAgB,OAAjB;EACE,aAAU;EACV,WAAW,GAAG,8CAA8C,SAAS;EACrE,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,kBAAkB,EACzB,WACA,GAAG,SACwD;CAC3D,OACE,oBAACA,OAAgB,aAAjB;EACE,aAAU;EACV,WAAW,GAAG,2CAA2C,SAAS;EAClE,GAAI;CACL,CAAA;AAEL"}
|
|
@@ -4,15 +4,116 @@ import * as React from 'react';
|
|
|
4
4
|
* either side of the current one, and 'ellipsis' for the gaps.
|
|
5
5
|
*/
|
|
6
6
|
export declare function paginationRange(page: number, pageCount: number, siblings?: number): (number | 'ellipsis')[];
|
|
7
|
+
/** The page sizes offered by default: 25, 50 or 100 rows. */
|
|
8
|
+
export declare const DEFAULT_PAGE_SIZES: readonly number[];
|
|
9
|
+
export interface PageWindow {
|
|
10
|
+
/** 1-based, clamped to the pages that exist (1 when there are no rows). */
|
|
11
|
+
page: number;
|
|
12
|
+
/** At least 1, so "Page 1 of 1" reads sensibly for an empty list. */
|
|
13
|
+
pageCount: number;
|
|
14
|
+
pageSize: number;
|
|
15
|
+
total: number;
|
|
16
|
+
/** 1-based index of the first row on this page; 0 when there are none. */
|
|
17
|
+
from: number;
|
|
18
|
+
/** 1-based index of the last row on this page; 0 when there are none. */
|
|
19
|
+
to: number;
|
|
20
|
+
}
|
|
21
|
+
/** Pages needed for `total` rows, never less than 1. */
|
|
22
|
+
export declare function pageCountFor(total: number, pageSize: number): number;
|
|
23
|
+
/** A requested page clamped to 1…pageCount. Anything unreadable ("abc", NaN) is page 1. */
|
|
24
|
+
export declare function clampPage(page: unknown, pageCount: number): number;
|
|
25
|
+
/** Where page `page` of `total` rows starts and ends, for server-side paging. */
|
|
26
|
+
export declare function pageWindow({ page, pageSize, total, }: {
|
|
27
|
+
page: unknown;
|
|
28
|
+
pageSize: number;
|
|
29
|
+
total: number;
|
|
30
|
+
}): PageWindow;
|
|
31
|
+
/** One page of `rows`, with the numbers for the page controls. */
|
|
32
|
+
export declare function paginate<T>(rows: readonly T[], page: unknown, pageSize: number): PageWindow & {
|
|
33
|
+
rows: T[];
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* "1–25 of 312", "7 of 7" when everything fits, "0 of 0" when empty. Numbers
|
|
37
|
+
* use the locale's grouping ("1,025"); pass `locale` to pin it.
|
|
38
|
+
*/
|
|
39
|
+
export declare function paginationSummary({ from, to, total }: Pick<PageWindow, 'from' | 'to' | 'total'>, locale?: string): string;
|
|
40
|
+
export interface UsePaginationOptions {
|
|
41
|
+
/** Controlled page (e.g. read from the URL). Leave out to let the hook keep it. */
|
|
42
|
+
page?: number;
|
|
43
|
+
/** Starting page when uncontrolled. */
|
|
44
|
+
defaultPage?: number;
|
|
45
|
+
onPageChange?: (page: number) => void;
|
|
46
|
+
/** Controlled page size. */
|
|
47
|
+
pageSize?: number;
|
|
48
|
+
/** Starting page size when uncontrolled; the first option by default. */
|
|
49
|
+
defaultPageSize?: number;
|
|
50
|
+
onPageSizeChange?: (pageSize: number) => void;
|
|
51
|
+
/** Sizes offered by the page-size menu. */
|
|
52
|
+
pageSizeOptions?: readonly number[];
|
|
53
|
+
/**
|
|
54
|
+
* Whatever decides which rows are listed (search text, a filter, a tab).
|
|
55
|
+
* When it changes, paging goes back to page 1. The first render never
|
|
56
|
+
* resets, so a link to page 3 stays on page 3.
|
|
57
|
+
*/
|
|
58
|
+
resetKey?: unknown;
|
|
59
|
+
/** Remembers the chosen page size in localStorage under this key. */
|
|
60
|
+
storageKey?: string;
|
|
61
|
+
/**
|
|
62
|
+
* The full row count when the server pages: `rows` is then already the
|
|
63
|
+
* current page and is not sliced.
|
|
64
|
+
*/
|
|
65
|
+
total?: number;
|
|
66
|
+
/** Locale for the summary's numbers. */
|
|
67
|
+
locale?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface UsePaginationResult<T> extends PageWindow {
|
|
70
|
+
/** The rows to render on this page. */
|
|
71
|
+
rows: T[];
|
|
72
|
+
pageSizeOptions: readonly number[];
|
|
73
|
+
/** "1–25 of 312". */
|
|
74
|
+
summary: string;
|
|
75
|
+
/** More rows than the smallest page size: worth showing controls for. */
|
|
76
|
+
hasPages: boolean;
|
|
77
|
+
setPage: (page: number) => void;
|
|
78
|
+
/** Changes the size and goes back to page 1. */
|
|
79
|
+
setPageSize: (pageSize: number) => void;
|
|
80
|
+
/** Spread onto Pagination (or pass to DataTable's paging props). */
|
|
81
|
+
paginationProps: Pick<PaginationProps, 'page' | 'pageCount' | 'onPageChange' | 'pageSize' | 'pageSizeOptions' | 'onPageSizeChange'> & {
|
|
82
|
+
summary: string;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Headless paging for a list or table: slices `rows`, clamps the page,
|
|
87
|
+
* returns to page 1 when `resetKey` changes, and optionally remembers the
|
|
88
|
+
* page size. Controlled `page` / `pageSize` let an app keep them in the URL.
|
|
89
|
+
*/
|
|
90
|
+
export declare function usePagination<T>(rows: readonly T[], options?: UsePaginationOptions): UsePaginationResult<T>;
|
|
7
91
|
export interface PaginationProps extends Omit<React.ComponentProps<'nav'>, 'onChange'> {
|
|
8
92
|
/** 1-based current page. */
|
|
9
93
|
page: number;
|
|
10
94
|
pageCount: number;
|
|
11
95
|
onPageChange: (page: number) => void;
|
|
12
96
|
siblings?: number;
|
|
13
|
-
/**
|
|
97
|
+
/** Summary on the left, e.g. "1–25 of 312". Worked out from `total` when left out. */
|
|
14
98
|
summary?: React.ReactNode;
|
|
99
|
+
/** Total rows: with `pageSize`, gives the default summary. */
|
|
100
|
+
total?: number;
|
|
101
|
+
/** Rows per page, shown in the page-size menu. */
|
|
102
|
+
pageSize?: number;
|
|
103
|
+
/** Sizes in the page-size menu (25, 50, 100 by default). */
|
|
104
|
+
pageSizeOptions?: readonly number[];
|
|
105
|
+
/** Adds a rows-per-page menu. */
|
|
106
|
+
onPageSizeChange?: (pageSize: number) => void;
|
|
107
|
+
/** The page-size menu's accessible name. */
|
|
108
|
+
pageSizeLabel?: string;
|
|
109
|
+
/** An option's text, e.g. "25 per page". */
|
|
110
|
+
formatPageSize?: (size: number) => string;
|
|
15
111
|
}
|
|
16
|
-
/**
|
|
17
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Page controls for a table or list: a summary, the pages, and optionally a
|
|
114
|
+
* rows-per-page menu. Below 640px it becomes "Page 2 of 13" between large
|
|
115
|
+
* previous and next buttons. Buttons are real buttons, keyboard-reachable,
|
|
116
|
+
* and 44px on touch screens.
|
|
117
|
+
*/
|
|
118
|
+
declare function Pagination({ page: requestedPage, pageCount: requestedCount, onPageChange, siblings, summary, total, pageSize, pageSizeOptions, onPageSizeChange, pageSizeLabel, formatPageSize, className, ...props }: PaginationProps): React.JSX.Element;
|
|
18
119
|
export { Pagination };
|