@seekalfred/primitives 0.7.3 → 0.7.4

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.
Files changed (91) hide show
  1. package/README.md +60 -1
  2. package/dist/alert/alert.d.ts +2 -2
  3. package/dist/alert-dialog/alert-dialog.d.ts +14 -0
  4. package/dist/alert-dialog/alert-dialog.js +51 -26
  5. package/dist/alert-dialog/alert-dialog.js.map +1 -1
  6. package/dist/announcement-bar/announcement-bar.d.ts +15 -4
  7. package/dist/announcement-bar/announcement-bar.js +31 -6
  8. package/dist/announcement-bar/announcement-bar.js.map +1 -1
  9. package/dist/avatar/avatar.d.ts +7 -0
  10. package/dist/avatar/avatar.js +33 -12
  11. package/dist/avatar/avatar.js.map +1 -1
  12. package/dist/badge/badge.d.ts +1 -1
  13. package/dist/bar-chart/bar-chart-series.d.ts +75 -0
  14. package/dist/bar-chart/bar-chart-series.js +401 -0
  15. package/dist/bar-chart/bar-chart-series.js.map +1 -0
  16. package/dist/bar-chart/bar-chart.d.ts +25 -1
  17. package/dist/bar-chart/bar-chart.js +26 -1
  18. package/dist/bar-chart/bar-chart.js.map +1 -1
  19. package/dist/bar-chart/index.d.ts +2 -1
  20. package/dist/button/button.d.ts +2 -2
  21. package/dist/chip-group/chip-group.d.ts +11 -0
  22. package/dist/chip-group/chip-group.js +50 -34
  23. package/dist/chip-group/chip-group.js.map +1 -1
  24. package/dist/chip-group/index.d.ts +2 -2
  25. package/dist/copy-button/copy-button.d.ts +19 -3
  26. package/dist/copy-button/copy-button.js +7 -4
  27. package/dist/copy-button/copy-button.js.map +1 -1
  28. package/dist/file-drop-zone/file-drop-zone.d.ts +29 -3
  29. package/dist/file-drop-zone/file-drop-zone.js +26 -8
  30. package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
  31. package/dist/gauge/gauge.d.ts +18 -1
  32. package/dist/gauge/gauge.js +35 -21
  33. package/dist/gauge/gauge.js.map +1 -1
  34. package/dist/gauge/index.d.ts +2 -2
  35. package/dist/index.d.ts +8 -7
  36. package/dist/index.js +4 -4
  37. package/dist/input/input.d.ts +7 -0
  38. package/dist/input/input.js +3 -1
  39. package/dist/input/input.js.map +1 -1
  40. package/dist/lib/field-shell.js +2 -6
  41. package/dist/lib/field-shell.js.map +1 -1
  42. package/dist/lib/required-marker.js +20 -0
  43. package/dist/lib/required-marker.js.map +1 -0
  44. package/dist/line-chart/index.d.ts +2 -2
  45. package/dist/line-chart/line-chart.d.ts +105 -3
  46. package/dist/line-chart/line-chart.js +183 -44
  47. package/dist/line-chart/line-chart.js.map +1 -1
  48. package/dist/money-input/index.d.ts +2 -2
  49. package/dist/money-input/money-input.d.ts +14 -0
  50. package/dist/money-input/money-input.js +17 -7
  51. package/dist/money-input/money-input.js.map +1 -1
  52. package/dist/morph-select/morph-select.d.ts +8 -1
  53. package/dist/morph-select/morph-select.js +8 -5
  54. package/dist/morph-select/morph-select.js.map +1 -1
  55. package/dist/multi-select/multi-select.d.ts +31 -0
  56. package/dist/multi-select/multi-select.js +122 -5
  57. package/dist/multi-select/multi-select.js.map +1 -1
  58. package/dist/progress/index.d.ts +2 -2
  59. package/dist/progress/index.js +2 -2
  60. package/dist/progress/progress.d.ts +53 -2
  61. package/dist/progress/progress.js +146 -36
  62. package/dist/progress/progress.js.map +1 -1
  63. package/dist/radio-group/radio-group.d.ts +1 -1
  64. package/dist/scrub-input/scrub-input.d.ts +11 -1
  65. package/dist/scrub-input/scrub-input.js +22 -10
  66. package/dist/scrub-input/scrub-input.js.map +1 -1
  67. package/dist/select/select.d.ts +9 -0
  68. package/dist/select/select.js +3 -2
  69. package/dist/select/select.js.map +1 -1
  70. package/dist/sparkline/sparkline.d.ts +27 -2
  71. package/dist/sparkline/sparkline.js +115 -18
  72. package/dist/sparkline/sparkline.js.map +1 -1
  73. package/dist/stat-card/index.d.ts +2 -2
  74. package/dist/stat-card/index.js +2 -2
  75. package/dist/stat-card/stat-card.d.ts +32 -1
  76. package/dist/stat-card/stat-card.js +52 -9
  77. package/dist/stat-card/stat-card.js.map +1 -1
  78. package/dist/styles.css +1 -1
  79. package/dist/table/index.d.ts +2 -2
  80. package/dist/table/index.js +2 -2
  81. package/dist/table/table.d.ts +114 -4
  82. package/dist/table/table.js +374 -94
  83. package/dist/table/table.js.map +1 -1
  84. package/dist/text-shimmer/text-shimmer.d.ts +18 -2
  85. package/dist/text-shimmer/text-shimmer.js +5 -2
  86. package/dist/text-shimmer/text-shimmer.js.map +1 -1
  87. package/dist/textarea/textarea.d.ts +7 -0
  88. package/dist/textarea/textarea.js +3 -1
  89. package/dist/textarea/textarea.js.map +1 -1
  90. package/dist/toast/toast.d.ts +2 -2
  91. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"table.js","names":["Button","Table","AriaTable","AriaTableHeader","AriaColumn","Checkbox","AriaTableBody","AriaRow","AriaCell","AriaTableFooter","Select"],"sources":["../../src/table/table.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n forwardRef,\n useEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type ForwardedRef,\n type ReactElement,\n type ReactNode,\n type RefAttributes,\n type RefObject,\n} from \"react\";\nimport {\n Cell as AriaCell,\n Column as AriaColumn,\n Row as AriaRow,\n Table as AriaTable,\n TableBody as AriaTableBody,\n TableFooter as AriaTableFooter,\n TableHeader as AriaTableHeader,\n type Selection,\n type SortDescriptor,\n} from \"react-aria-components\";\nimport { flushSync } from \"react-dom\";\n\nimport { Button } from \"../button/button.js\";\nimport { Checkbox } from \"../checkbox/checkbox.js\";\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { useFlip } from \"../lib/flip.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport { Select, SelectItem } from \"../select/select.js\";\n\n/**\n * Table, ported from the legacy shadcn table onto React Aria's Table.\n *\n * The legacy component in the product app rendered NOTHING -- its body was\n * `return <>{children(renderProps)}</>` -- so all six of its consumers\n * hand-built `<table>`, `tableLayout: fixed`, a `<colgroup>` and a min-width,\n * and its config object was opt-OUT, so they wrote paragraphs of\n * `enabled: false`. Six consumers produced six different tables. This one\n * renders from `data` + `columns` + `getRowId`; everything else is opt-in.\n *\n * WIDTHS ARE CSS, and that is not a stylistic choice. React Aria's own `width`\n * on a `<Column>` is ignored outside a `ResizableTableContainer`, and its\n * dev-warning is dead code in 1.21.1 -- `for (let prop in ['width', ...])`\n * iterates the INDICES, so the guard tests `'0' in props` and never fires. A\n * width handed to React Aria disappears with no error and no console output:\n * `width={120}` measured 42.675px in a browser. So `Column.width` is emitted\n * as a style on the header cell, and any width puts the table into\n * `table-layout: fixed` -- without which a declared width is only a hint.\n *\n * THE ROW HIGHLIGHT IS PAINTED FROM `:hover` AS WELL AS `data-[hovered]`, and\n * that is a departure from the rule that state comes from React Aria data\n * attributes rather than bare pseudo-classes. React Aria stamps `data-hovered`\n * on a row ONLY when the row is interactive -- `onRowAction` or a selection\n * mode -- exactly as it gates `Column` hover on `allowsSorting`. Four of the\n * six legacy consumers are read-only tables that painted a hover fill, so\n * `data-[hovered]` alone would drop their pointer affordance in silence. The\n * rule exists because `:focus` fires on click where `data-[focus-visible]`\n * does not; `:hover` has no such twin, and on an interactive row the two\n * selectors match the same element with the same colour. The keyboard needs no\n * workaround: `data-focused` IS emitted on read-only rows.\n *\n * `leading-*` sits on the table rather than on the cell. tailwind-merge treats\n * a later `text-*` as owning line-height and silently strips a `leading-*`\n * that precedes it, and the cells carry `text-start`/`text-center`/`text-end`.\n * Line-height inherits, so putting it one level up removes the hazard instead\n * of policing it.\n */\n\n/**\n * The card. Every table in the product sits in one.\n *\n * The edge uses `separator`, NOT `border`, and that reverses what an earlier\n * revision of this file claimed. Measured against the running product, its card\n * is `border border-border` resolving to #e6e6e6 -- the same hairline as the row\n * dividers. The earlier argument was that the card edge is where WCAG 1.4.11\n * lands; on reflection it does not. 1.4.11 covers what is needed to identify\n * user interface COMPONENTS and their states, and a static table is not one:\n * the interactive parts inside it -- sortable headers, pagination buttons --\n * carry their own focus indicators and borders, which are still held to 3:1.\n *\n * overflow-hidden is load-bearing: the header fill runs to the table's edge and\n * would square off the rounded corners without it.\n *\n * THE EDGE SHADOWS. A table wider than its card scrolls inside it, and on a\n * trackpad the scrollbar only shows while scrolling, so a cut-off column looks\n * like the table's last one. The card carries `data-overflow-start` and\n * `data-overflow-end` while columns are hidden on that side (the Table sets\n * them from its scroll position), and each draws a shadow on that edge: the\n * text colour fading to nothing at 15%, so it follows the theme. With a sticky\n * first column the start is never hidden behind the card's edge, so the start\n * shadow stays off and the sticky column's own divider does that job.\n */\nexport const tableCardVariants = cva(\n [\n \"w-full box-border overflow-hidden relative isolate\",\n \"before:pointer-events-none before:absolute before:inset-y-0 before:start-0 before:z-2 before:w-6\",\n \"before:bg-linear-to-r rtl:before:bg-linear-to-l before:from-foreground before:to-transparent\",\n \"before:opacity-0 motion-safe:before:transition-opacity\",\n \"data-[overflow-start]:not-data-[sticky-first]:before:opacity-15\",\n \"after:pointer-events-none after:absolute after:inset-y-0 after:end-0 after:z-2 after:w-6\",\n \"after:bg-linear-to-l rtl:after:bg-linear-to-r after:from-foreground after:to-transparent\",\n \"after:opacity-0 motion-safe:after:transition-opacity\",\n \"data-[overflow-end]:after:opacity-15\",\n ],\n {\n variants: {\n frame: {\n card: \"rounded-panel border-(length:--border-width) border-separator bg-surface\",\n /*\n * No frame. Eleven of the product's tables either sit inside a card\n * already -- gaining a second nested border and radius from an\n * unconditional one -- or are deliberately bare on the page.\n */\n none: \"\",\n },\n },\n defaultVariants: { frame: \"card\" },\n },\n);\n\n/**\n * An absent value is an em-dash, not the word \"null\".\n *\n * The default renderer was `String(value)`, which paints the literal text\n * `null` into the cell -- a defect rather than a gap, and six of the product's\n * tables print a dash there. Zero and empty string are values and are left\n * alone; only null and undefined are absent.\n */\nfunction renderValue<T>(column: Column<T, unknown>, value: unknown, row: T): ReactNode {\n if (value === null || value === undefined) return \"—\";\n\n const formatted = column.format?.(value, row);\n if (formatted !== undefined) return formatted;\n\n /*\n * Only primitives have a sensible default rendering. String(someObject) is\n * \"[object Object]\", which is the same class of silent nonsense as the\n * literal \"null\" this function exists to stop -- so an object-valued column\n * renders nothing until it is given `format` or `cell`, which is loud enough\n * to notice and quiet enough not to lie.\n */\n if (typeof value === \"string\" || typeof value === \"number\") return value;\n if (typeof value === \"boolean\") return String(value);\n return null;\n}\n\n/** Reserved id for the chevron column, so it cannot collide with a real one. */\nconst CHEVRON_COLUMN_ID = \"__table_row_action\";\n\n/*\n * The chevron column is sized to its glyph, not to a share of the table.\n * Given a percentage AND the usual --density-row-pad-x on both sides it pushed\n * the total past 100% under table-layout: fixed, and a table that fits grew a\n * horizontal scrollbar. w-10 is the spacing scale rather than a bracket value,\n * which check:hardcoding would reject.\n */\nconst CHEVRON_CELL = \"w-10 px-0 text-center\";\n\n/** Reserved id for the checkbox column of a selectable table. */\nconst SELECT_COLUMN_ID = \"__table_row_select\";\n\n/** The checkbox column is as wide as its box needs, with the table's usual inset on its leading side. */\nconst SELECT_CELL = \"w-11 px-0 ps-(--density-row-pad-x)\";\n\n/** The horizontal scroll container. The table owns it; 5 of 6 consumers built one by hand. */\nexport const tableScrollVariants = cva([\"w-full overflow-x-auto box-border\"]);\n\nexport const tableVariants = cva([\n \"w-full border-collapse box-border\",\n // font-sans and the explicit colours are not boilerplate: the reset in\n // @seekalfred/styles is OPT-IN, so a consumer may load none and an unstyled\n // table would inherit the host's serif on a transparent ground.\n \"font-sans text-base leading-(--leading-normal)\",\n \"bg-surface text-foreground\",\n]);\n\n/**\n * The header is near-white, not a grey band.\n *\n * The product fills it with its `--neutral-200`, #fffcfc: white carrying three\n * counts of warmth in green and blue. An earlier revision of this file called\n * that \"not worth a token of its own\" and used `background`, which made the\n * header exactly the colour of the rows -- so the band vanished and the only\n * thing separating the header was the divider under it. The difference is\n * small on purpose; absent is not the same as small. It is `--color-header-fill`\n * now, and it inverts properly in dark rather than being pinned to white.\n */\nexport const tableHeaderVariants = cva([\"text-muted-foreground\"], {\n variants: { headerFill: { subtle: \"bg-header-fill\", none: \"bg-transparent\" } },\n defaultVariants: { headerFill: \"subtle\" },\n});\n\n/** The pagination bar. A sibling of the table, never a row inside it. */\nexport const tablePaginationVariants = cva([\n \"flex items-center justify-between gap-(--density-control-gap) box-border\",\n \"px-(--density-row-pad-x) py-(--density-row-pad-y)\",\n \"font-sans text-sm leading-(--leading-normal) text-muted-foreground\",\n]);\n\n/**\n * The bar under a selectable table: how many rows, or how many are selected,\n * and the action that clears them. A sibling of the table, like the pagination.\n */\nexport const tableSelectionBarVariants = cva([\n \"flex min-h-11 items-center justify-between gap-(--density-control-gap) box-border\",\n \"border-t-(length:--border-width) border-t-separator\",\n \"ps-(--density-row-pad-x) pe-2 font-sans text-sm leading-(--leading-normal)\",\n \"text-muted-foreground data-[active]:text-foreground\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-standard) motion-safe:ease-settle\",\n]);\n\n/**\n * What stands in for the rows when there are none: centred, muted, and tall\n * enough to read as a state rather than as a squashed row.\n */\nexport const tableEmptyVariants = cva([\n \"flex min-h-30 items-center justify-center px-(--density-row-pad-x) py-6\",\n \"box-border text-center text-sm text-muted-foreground wrap-anywhere\",\n]);\n\n/** The footer band. Heavier than a row rule, because it closes the table. */\nexport const tableFooterVariants = cva([\n \"bg-surface font-semibold text-foreground\",\n \"border-t-(length:--border-width) border-t-border\",\n]);\n\n/** Shared focus treatment. Authored once; both the column header and the row take it. */\nconst FOCUS_RING = [\n // outline-solid is load-bearing: outline-none sets --tw-outline-style to\n // none, and the width utility resolves its style from that same variable, so\n // a focus rule can apply a width and a colour and paint nothing.\n \"outline-none\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n];\n\nexport const tableColumnVariants = cva(\n [\n \"px-(--density-row-pad-x) py-(--density-row-pad-y)\",\n \"box-border align-middle text-sm font-semibold select-none\",\n \"data-[sorted]:bg-foreground/3 data-[sorted]:text-foreground\",\n ...FOCUS_RING,\n // The whole cell is the hit target, not just the label. At compact density\n // --density-row-pad-y is 7px, so a control wrapping only its text lands\n // under the 24x24 floor of WCAG 2.2 target size (2.5.8) -- which the story\n // harness enables explicitly on top of axe's defaults.\n \"data-[allows-sorting]:cursor-pointer\",\n ],\n {\n variants: {\n align: { start: \"text-start\", center: \"text-center\", end: \"text-end\" },\n /*\n * Eleven tables -- a plurality of the app -- label columns in uppercase\n * with tracking, and not one was reachable: the label was hardcoded\n * `text-sm font-semibold`, and `className` lands on the <table>.\n */\n headerType: {\n sentence: \"text-sm leading-none\",\n caps: \"text-xs uppercase tracking-wide leading-none\",\n },\n },\n defaultVariants: { align: \"start\", headerType: \"sentence\" },\n },\n);\n\nexport const tableRowVariants = cva(\n [\n // The group a sticky first cell reads the row's state from (tableStickyCellVariants).\n // bg-surface: opaque, so rows passing each other while a sort glides them\n // cover one another cleanly instead of blending their text.\n \"group/row box-border bg-surface\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\n // See the header comment: :hover is what covers a read-only row.\n \"hover:bg-muted data-[hovered]:bg-muted data-[focused]:bg-muted\",\n // A chosen row: the muted fill, a step darker under the pointer.\n \"data-[selected]:bg-muted data-[selected]:hover:bg-separator data-[selected]:data-[hovered]:bg-separator\",\n // After the state fills, so the marked row keeps its identity while hovered.\n \"data-[highlighted]:bg-primary-subtle\",\n ...FOCUS_RING,\n ],\n {\n variants: {\n /*\n * `grid` is gone. Across 25 real tables in the product not one drew\n * vertical rules between cells -- a variant nobody asked for, shipped\n * while the caps header eleven tables need was unreachable.\n */\n variant: {\n lined: \"border-t-(length:--border-width) border-t-separator\",\n plain: \"\",\n },\n /* rowClassName could not fake this: it gets the row but not its index. */\n striped: { true: \"[&:nth-child(even)]:bg-row-stripe\", false: \"\" },\n },\n defaultVariants: { variant: \"lined\", striped: false },\n },\n);\n\nexport const tableCellVariants = cva(\n [\n \"px-(--density-row-pad-x) py-(--density-row-pad-y)\",\n // wrap-anywhere: a long word or URL breaks inside its cell rather than\n // pushing the column wider than the card.\n \"box-border align-middle wrap-anywhere\",\n // The sorted column wears a still tint, which fills the gaps that open\n // while rows pass each other.\n \"data-[sorted]:bg-foreground/3\",\n ...FOCUS_RING,\n ],\n {\n variants: {\n align: { start: \"text-start\", center: \"text-center\", end: \"text-end\" },\n /* Ten tables set tabular figures -- the app's most consistent convention. */\n numeric: { true: \"tabular-nums whitespace-nowrap\", false: \"\" },\n },\n defaultVariants: { align: \"start\", numeric: false },\n },\n);\n\n/**\n * The first cell of every row, held in place while the rest scroll under it\n * (`stickyFirstColumn`).\n *\n * A sticky cell is opaque -- otherwise the scrolled cells show through it -- so\n * it repaints whatever fill its row has: the row's hover, focus and highlight\n * through `group/row`, the stripe when striped. A divider on its trailing edge\n * marks where the frozen column ends.\n */\nexport const tableStickyCellVariants = cva(\n [\n \"sticky start-0 z-1\",\n \"border-e-(length:--border-width) border-e-separator\",\n ],\n {\n variants: {\n part: {\n header: \"bg-header-fill\",\n bare: \"bg-surface\",\n body: [\n \"bg-surface\",\n \"group-hover/row:bg-muted group-data-[hovered]/row:bg-muted group-data-[focused]/row:bg-muted\",\n \"group-data-[highlighted]/row:bg-primary-subtle\",\n ],\n },\n striped: { true: \"\", false: \"\" },\n },\n compoundVariants: [\n { part: \"body\", striped: true, class: \"group-even/row:bg-row-stripe\" },\n ],\n defaultVariants: { part: \"body\", striped: false },\n },\n);\n\n/**\n * Which sides of a scroll container hide content right now.\n *\n * Read from the container on scroll and whenever it or the table resizes, so\n * a window resize or rows arriving later update it too. scrollLeft runs\n * negative in a right-to-left layout; the distance travelled is what counts.\n */\nfunction useScrollEdges(scroller: RefObject<HTMLDivElement | null>) {\n const [edges, setEdges] = useState({ start: false, end: false });\n useEffect(() => {\n const el = scroller.current;\n if (!el) return;\n const update = () => {\n const hidden = el.scrollWidth - el.clientWidth;\n const travelled = Math.abs(el.scrollLeft);\n const start = hidden > 1 && travelled > 1;\n const end = hidden > 1 && travelled < hidden - 1;\n setEdges((prev) => (prev.start === start && prev.end === end ? prev : { start, end }));\n };\n update();\n el.addEventListener(\"scroll\", update, { passive: true });\n const resize = typeof ResizeObserver === \"undefined\" ? undefined : new ResizeObserver(update);\n resize?.observe(el);\n if (el.firstElementChild) resize?.observe(el.firstElementChild);\n return () => {\n el.removeEventListener(\"scroll\", update);\n resize?.disconnect();\n };\n }, [scroller]);\n return edges;\n}\n\n/**\n * Identity for a row.\n *\n * Declared here rather than imported: React's own `Key` includes `bigint`,\n * which React Aria's collections do not accept, and importing React Aria's\n * would leak its types through the public API for no gain. This IS the set\n * React Aria takes.\n */\nexport type RowKey = string | number;\n\n/**\n * Both glyphs are aria-hidden and neither is focusable.\n *\n * The chevron says \"this row does something\"; the ROW is what React Aria makes\n * pressable and announces, so a second target would be a keyboard trap for no\n * gain. The caret says which way a column is sorted -- `aria-sort` already\n * tells assistive tech, but without a glyph the direction is carried by tint\n * alone, which is WCAG 1.4.1 use-of-colour.\n *\n * Drawn inline rather than taken as props: this package ships no icon set, and\n * Checkbox and Select already inline their single glyphs this way.\n */\n/**\n * Which page numbers to show, with gaps collapsed to an ellipsis.\n *\n * One sibling either side of the current page, plus the first and last, which\n * is what the product's own pager renders at its call sites: page 1 of 18 gives\n * `1 2 ... 18`. It is not a prop, because no consumer has asked for a second\n * width and a number nobody tunes is a number better inlined.\n */\nfunction pageWindow(current: number, total: number): (number | \"ellipsis\")[] {\n const pages: (number | \"ellipsis\")[] = [];\n const start = Math.max(1, current - 1);\n const end = Math.min(total, current + 1);\n\n if (start > 1) {\n pages.push(1);\n if (start > 2) pages.push(\"ellipsis\");\n }\n for (let page = start; page <= end; page++) pages.push(page);\n if (end < total) {\n if (end < total - 1) pages.push(\"ellipsis\");\n pages.push(total);\n }\n return pages;\n}\n\nfunction PageChevronGlyph({ direction }: { direction: \"previous\" | \"next\" }) {\n return (\n <svg\n data-slot=\"table-pagination-chevron\"\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"size-(--icon-sm) rtl:-scale-x-100\"\n >\n <path d={direction === \"next\" ? \"m6 3 5 5-5 5\" : \"m10 3-5 5 5 5\"} />\n </svg>\n );\n}\n\nfunction RowChevronGlyph() {\n return (\n <svg\n data-slot=\"table-row-chevron\"\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"size-(--icon-sm) text-muted-foreground\"\n >\n {/* Logical, so it mirrors in RTL with the rest of the table. */}\n <path d=\"m6 3 5 5-5 5\" className=\"rtl:-scale-x-100\" />\n </svg>\n );\n}\n\nfunction SortCaretGlyph({ direction }: { direction: \"ascending\" | \"descending\" | \"none\" }) {\n return (\n <svg\n data-slot=\"table-sort-caret\"\n data-direction={direction}\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"table-sort-caret size-(--icon-sm) ms-1 inline-block align-middle\"\n >\n {/*\n Both glyphs are always drawn and the direction decides which shows: the\n up-down pair says \"this sorts\" while the column is idle, and where the\n column becomes sorted it gives way to a single arrow that fades in and\n turns over when the direction flips (table-sort-caret, in styles.css).\n */}\n <g className=\"table-sort-idle\">\n <path d=\"m4 7 4-4 4 4\" />\n <path d=\"m4 9 4 4 4-4\" />\n </g>\n <g className=\"table-sort-arrow\">\n <path d=\"M8 13V3\" />\n <path d=\"m4.5 6.5 3.5-3.5 3.5 3.5\" />\n </g>\n </svg>\n );\n}\n\n/** One column of a `Table`. */\nexport type Column<T, V = unknown> = {\n /** Stable key. Becomes the column's id and the sort descriptor's `column`. */\n id: string;\n /** Header content. */\n header: ReactNode;\n /** Pulls this column's value out of a row. */\n accessor: (row: T) => V;\n /**\n * Renders the cell. Defaults to `format?.(value, row) ?? String(value)`.\n *\n * Method syntax, not an arrow property, and that is load-bearing. A table\n * holds columns of DIFFERENT value types in one array, so the array element\n * has to be `Column<T, unknown>` -- and under `strictFunctionTypes` an arrow\n * property is contravariant in its parameters, which would make\n * `Column<T, string>` unassignable to it. Method members are checked\n * bivariantly, which is precisely the semantics a heterogeneous collection\n * needs. The alternative is `Column<T, any>`, and this package has no\n * eslint-disable in it anywhere.\n */\n cell?(ctx: { value: V; row: T; index: number }): ReactNode;\n /**\n * Value to string. Deliberately separate from `cell`: this is what CSV\n * export and sort comparators consume, and a ReactNode is no use to either.\n */\n format?(value: V, row: T): string;\n /**\n * Logical alignment, applied to BOTH the column header and its cells --\n * React Aria keeps no link between the two, so the component owns it.\n */\n align?: \"start\" | \"center\" | \"end\";\n /**\n * Column width. Any width on any column puts the table into\n * `table-layout: fixed`.\n *\n * Emitted as CSS, NEVER forwarded to React Aria's own `width` prop. React\n * Aria ignores that one outside a `ResizableTableContainer`, and its\n * dev-warning is dead code in 1.21.1 (`for...in` over an array literal, so\n * the guard tests `'0' in props`). A width handed to it disappears with no\n * error and no console output.\n */\n width?: number | `${number}%`;\n /**\n * A column of figures: tabular numerals, end-aligned unless `align` says\n * otherwise. Ten of the product's tables set tabular figures -- the most\n * consistently applied convention in the app. Without it, proportional\n * digits leave a column of numbers ragged.\n */\n numeric?: boolean;\n /** Opts this column into sorting. Off by default. */\n sortable?: boolean;\n};\n\nexport interface TableProps<T> extends VariantProps<typeof tableRowVariants> {\n /** The rows to render. */\n data: readonly T[];\n /** The columns, in display order. */\n columns: readonly Column<T, unknown>[];\n /** Stable identity for a row. Used as its key and to resolve `onRowAction`. */\n getRowId: (row: T) => RowKey;\n /**\n * Required. React Aria's Table renders no `<caption>`, so this is the\n * accessible name and there is no other source for it.\n */\n \"aria-label\": string;\n /**\n * The sorted column and direction, CONTROLLED.\n *\n * Pass this and the table stops sorting: you own the row order, which is\n * what server-side sorting needs. Leave it off and the table sorts `data`\n * itself from `defaultSortDescriptor` and header clicks.\n */\n sortDescriptor?: SortDescriptor;\n /** The initial sort when uncontrolled. */\n defaultSortDescriptor?: SortDescriptor;\n /** Fires when a sortable column header is activated, controlled or not. */\n onSortChange?: (descriptor: SortDescriptor) => void;\n /**\n * Fires on click or Enter. Receives the ROW, where React Aria's own handler\n * receives a key -- every legacy consumer looked the row up again anyway.\n *\n * Setting it also makes rows interactive, which is what switches React\n * Aria's `data-hovered` on.\n */\n onRowAction?: (row: T) => void;\n /**\n * Marks a row as the viewer's own -- the product tints \"Alfred AI (You)\".\n *\n * The tint is not the message. A fill alone is WCAG 1.4.1 use-of-colour, so\n * the highlighted row is expected to say so in its own content, the way the\n * product's does. This tints and sets `data-highlighted`; it does not invent\n * meaning the row does not already carry.\n */\n isRowHighlighted?: (row: T) => boolean;\n /** Extra classes per row, for styling that depends on row DATA. */\n rowClassName?: (row: T) => string;\n /**\n * Rendered in place of the rows when `data` is empty.\n *\n * There is no default string on purpose: user-visible copy belongs in\n * `useMessages()`, a mechanism this package does not have yet, and it is the\n * one rule ARCHITECTURE says cannot be retrofitted.\n */\n empty?: ReactNode;\n /**\n * Minimum table width in px. Below it the wrapper scrolls horizontally, and\n * the card shows a shadow on each edge that hides columns.\n */\n minWidth?: number;\n /**\n * Keeps the first column in place while the rest scroll sideways. For a\n * register wider than its card whose first column says which row is which\n * (an invoice number, a person's name).\n */\n stickyFirstColumn?: boolean;\n /**\n * Rows per page. Omit it and every row renders with no pagination UI.\n *\n * The table holds the page itself and slices AFTER sorting, so a sort\n * reorders the whole set rather than shuffling the current page, and lands\n * you back on page 1.\n *\n * With `pageSizeOptions` this is the STARTING size and the table owns it\n * from then on; a later change to the prop does not move a reader who has\n * chosen their own. Without them it is simply the size.\n */\n pageSize?: number;\n /**\n * Rows-per-page choices. Given them, the bar grows a select on its trailing\n * side, which is the arrangement every paged table in the product uses:\n * page numbers leading, rows-per-page trailing.\n */\n pageSizeOptions?: readonly number[];\n /** Notified after the reader picks a new size. The table has already applied it. */\n onPageSizeChange?: (pageSize: number) => void;\n /**\n * Copy for the pagination controls.\n *\n * These carry English defaults, which is a known debt: ARCHITECTURE section\n * 7 rule 9 routes user-visible copy through a `useMessages()` hook that does\n * not exist in this package yet. They are props so a consumer is not stuck\n * waiting for it, and so the eventual mechanism has somewhere to land. The\n * navigation landmark takes its name from `aria-label` instead, which avoids\n * inventing a fourth string.\n */\n pageLabels?: {\n previous?: string;\n next?: string;\n /**\n * Accessible name for a page button.\n *\n * `isCurrent` exists because `aria-current` cannot reach the button.\n * React Aria's Button runs its props through `filterDOMProps`, whose\n * allowlist covers `id` and the labelling attributes and nothing else, so\n * an `aria-current` handed to it is dropped with no error -- the same\n * class of silent loss as `Column.width`. The current page therefore says\n * so in its name, which every screen reader announces.\n */\n page?: (page: number, isCurrent: boolean) => string;\n /** Option text in the rows-per-page select. */\n perPage?: (pageSize: number) => string;\n /** Accessible name for the rows-per-page select itself. */\n rowsPerPage?: string;\n };\n /**\n * Row selection. `multiple` adds a checkbox column (the library's Checkbox,\n * on React Aria's table selection) with a select-all box in the header that\n * shows a dash while only some rows are chosen, and a bar under the table\n * that counts the rows and clears the choice. Clicking a row's checkbox, or\n * the row itself, toggles it; Escape clears. `none` (the default) adds\n * nothing.\n */\n selectionMode?: \"none\" | \"multiple\";\n /**\n * The chosen rows, CONTROLLED: a set of `getRowId` values, or `\"all\"`.\n * Pass this and the table stops keeping the selection itself.\n */\n selectedKeys?: Selection;\n /** The rows chosen at the start, when uncontrolled. */\n defaultSelectedKeys?: Selection;\n /** Fires with the new selection (a set of ids, or `\"all\"`), controlled or not. */\n onSelectionChange?: (keys: Selection) => void;\n /**\n * Copy for the selection controls, with English defaults for the same reason\n * as `pageLabels`: a `useMessages()` mechanism does not exist in this package\n * yet.\n */\n selectionLabels?: {\n /** Accessible name of the header checkbox. React Aria's own, localized (\"Select All\"), when omitted. */\n selectAll?: string;\n /**\n * The word before a row's name in its checkbox's accessible name. React\n * Aria appends the text of the row's first column itself, so \"Select\"\n * becomes \"Select Meta\"; its own localized word is used when omitted.\n */\n selectRow?: string;\n /** What a row is called in the count: \"1 row\", \"5 rows\". */\n item?: { one: string; other: string };\n /** The word after the count while some rows are chosen: \"3 selected\". */\n selected?: string;\n /** The clear button's text. */\n clear?: string;\n };\n /**\n * Whether the table draws its own card. `none` for one that already sits\n * inside a card, or is meant to be bare on the page.\n */\n frame?: \"card\" | \"none\";\n /** Column label treatment: sentence case, or uppercase with tracking. */\n headerType?: \"sentence\" | \"caps\";\n /** Header background. `none` for report tables that carry no band. */\n headerFill?: \"subtle\" | \"none\";\n /** Zebra-stripe alternate rows. */\n striped?: boolean;\n /**\n * A footer row, in a real `<tfoot>`.\n *\n * Called once per column; return the cell content, or null for a blank. A\n * real footer rather than one more row of data, so it is announced as one.\n */\n totalRow?: (column: Column<T, unknown>) => ReactNode;\n /** Extra classes for the table. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\n/** 1 when the value grew, -1 when it shrank: the way a changed number swaps. Updated during render. */\nfunction useChangeDirection(value: number) {\n const [seen, setSeen] = useState({ value, dir: 1 });\n if (seen.value !== value) setSeen({ value, dir: value < seen.value ? -1 : 1 });\n return seen.dir;\n}\n\n/**\n * The bar under a selectable table. With nothing chosen it counts the rows\n * (\"12 rows\"); with some chosen it counts them (\"3 selected\") and offers the\n * way back. The number and the word swap in place as they change.\n */\nfunction SelectionBar({\n count,\n total,\n labels,\n onClear,\n}: {\n count: number;\n total: number;\n labels: {\n item: { one: string; other: string };\n selected: string;\n clear: string;\n };\n onClear: () => void;\n}) {\n const shown = count || total;\n const noun = count ? labels.selected : shown === 1 ? labels.item.one : labels.item.other;\n const direction = useChangeDirection(shown);\n return (\n <div\n data-slot=\"table-selection-bar\"\n data-active={count > 0 || undefined}\n className={cnAll(tableSelectionBarVariants())}\n >\n <span data-slot=\"table-selection-count\" className=\"inline-flex items-baseline gap-1\">\n <SwapText swapKey={String(shown)} dir={direction} className=\"inline-block tabular-nums\">\n {shown}\n </SwapText>{\" \"}\n <SwapText swapKey={noun} dir={count ? 1 : -1} className=\"inline-block\">\n {noun}\n </SwapText>\n </span>\n {count > 0 ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n data-slot=\"table-selection-clear\"\n className=\"motion-safe:animate-fade-in\"\n onPress={onClear}\n >\n {labels.clear}\n </Button>\n ) : null}\n </div>\n );\n}\n\n/** Builds columns that keep each column's own value type. */\nexport function createColumns<T>() {\n return {\n column: <V,>(definition: Column<T, V>): Column<T, V> => definition,\n };\n}\n\n/**\n * A data table.\n *\n * Maturity: **beta**. Row expansion, filtering and grouping are deliberately\n * absent -- each had one consumer or none across the six legacy tables, and the\n * stateful ones belong to DataGrid. Sorting glides: when a sort reorders the\n * rows, each slides to its new place instead of jumping (the lib/flip helper).\n * Selection is opt-in with `selectionMode=\"multiple\"`.\n *\n * @example\n * const { column } = createColumns<Channel>();\n * const columns = useMemo(() => [\n * column({ id: \"name\", header: \"Channel\", accessor: (r) => r.name }),\n * column({ id: \"cost\", header: \"Cost\", accessor: (r) => r.cost,\n * format: (v) => currency(v), align: \"end\", sortable: true }),\n * ], []);\n *\n * <Table aria-label=\"Channels\" data={rows} columns={columns} getRowId={(r) => r.id} />\n */\n/**\n * The orderable value behind a cell.\n *\n * Objects are NOT stringified. `String(someObject)` is \"[object Object]\", so a\n * column of objects would sort every row equal while looking like it worked --\n * and a column's own `format` already exists to turn its value into a string,\n * so that is what gets used instead of a blind cast.\n *\n * Numbers stay numbers even when the column formats them: sorting \"$1,220\"\n * against \"$12,480\" lexically puts the larger first.\n */\nfunction sortKey<T>(column: Column<T, unknown>, row: T): string | number {\n const value = column.accessor(row);\n if (typeof value === \"number\" || typeof value === \"string\") return value;\n if (typeof value === \"boolean\") return value ? 1 : 0;\n if (value instanceof Date) return value.getTime();\n if (value == null) return \"\";\n return column.format?.(value, row) ?? \"\";\n}\n\n/** Orders two cell keys. Numbers numerically, strings by locale. */\nfunction compareKeys(a: string | number, b: string | number): number {\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n // localeCompare so \"Item 10\" sorts after \"Item 9\", not before it.\n return String(a).localeCompare(String(b));\n}\n\nexport const Table = forwardRef(function Table<T>(\n {\n data,\n columns,\n getRowId,\n variant,\n sortDescriptor,\n defaultSortDescriptor,\n onSortChange,\n onRowAction,\n isRowHighlighted,\n rowClassName,\n selectionMode,\n selectedKeys,\n defaultSelectedKeys,\n onSelectionChange,\n selectionLabels,\n frame,\n headerType,\n headerFill,\n striped,\n totalRow,\n empty,\n minWidth,\n stickyFirstColumn,\n pageSize,\n pageSizeOptions,\n onPageSizeChange,\n pageLabels,\n className,\n ...props\n }: TableProps<T>,\n ref: ForwardedRef<HTMLTableElement>,\n) {\n /*\n * Uncontrolled sorting is the component's job, not the call site's.\n *\n * React Aria holds no sort state and never reorders rows -- it reports the\n * descriptor and stops. Leaving it there would mean `sortable: true` renders\n * a header that highlights, announces itself via aria-sort, and sorts\n * nothing until the consumer writes the state and the comparator by hand.\n * That is a demo that only works in a story.\n *\n * So: uncontrolled, the table holds the descriptor and sorts `data`.\n * Controlled (a `sortDescriptor` prop), it sorts nothing and the consumer\n * owns the order, which is what server-side sorting requires.\n */\n const isControlled = sortDescriptor !== undefined;\n const [uncontrolledSort, setUncontrolledSort] = useState(defaultSortDescriptor);\n const activeSort = isControlled ? sortDescriptor : uncontrolledSort;\n\n const rows = useMemo(() => {\n if (isControlled || !activeSort) return data;\n const sorted = columns.find((column) => column.id === activeSort.column);\n if (!sorted) return data;\n\n const direction = activeSort.direction === \"descending\" ? -1 : 1;\n return [...data].sort(\n (a, b) => direction * compareKeys(sortKey(sorted, a), sortKey(sorted, b)),\n );\n }, [data, columns, activeSort, isControlled]);\n\n /*\n * Paging happens AFTER sorting, which is the whole reason it lives in the\n * component. Slice first and a sort would only reshuffle the ten rows you\n * can see, so \"sort by cost ascending\" would not find the cheapest row in\n * the set -- the single most common way a hand-rolled table gets this wrong.\n */\n // A size the reader picked outranks the prop, and is held here rather than\n // synced from it by an effect -- an effect would snap them back to the\n // consumer's default on every unrelated re-render.\n const [chosenSize, setChosenSize] = useState<number>();\n const rowsPerPage = chosenSize ?? pageSize;\n\n const pageCount = rowsPerPage ? Math.max(1, Math.ceil(rows.length / rowsPerPage)) : 1;\n const [page, setPage] = useState(1);\n // Clamped rather than reset by an effect, so a shrinking `data` cannot strand\n // the view on a page that no longer exists.\n const currentPage = Math.min(page, pageCount);\n const pageStart = rowsPerPage ? (currentPage - 1) * rowsPerPage : 0;\n const visibleRows = rowsPerPage ? rows.slice(pageStart, pageStart + rowsPerPage) : rows;\n\n const labels = {\n previous: pageLabels?.previous ?? \"Go to previous page\",\n next: pageLabels?.next ?? \"Go to next page\",\n page:\n pageLabels?.page ??\n ((target: number, isCurrent: boolean) =>\n isCurrent ? `Page ${target}, current page` : `Go to page ${target}`),\n perPage: pageLabels?.perPage ?? ((size: number) => `${size} per page`),\n rowsPerPage: pageLabels?.rowsPerPage ?? \"Rows per page\",\n };\n\n const rowsByKey = new Map<RowKey, T>(data.map((row) => [getRowId(row), row]));\n\n /*\n * Selection is React Aria's: it owns the keyboard, the modifier clicks and the\n * announcements. The table only holds the set when uncontrolled, so the bar\n * below can count it, and renders the checkbox column.\n */\n const selectable = selectionMode === \"multiple\";\n const [uncontrolledSelection, setUncontrolledSelection] = useState<Selection>(\n defaultSelectedKeys ?? new Set(),\n );\n const selection = selectedKeys ?? uncontrolledSelection;\n const commitSelection = (keys: Selection) => {\n if (selectedKeys === undefined) setUncontrolledSelection(keys);\n onSelectionChange?.(keys);\n };\n const selectedCount =\n selection === \"all\"\n ? visibleRows.length\n : [...selection].filter((key) => rowsByKey.has(key)).length;\n const selectLabels = {\n item: selectionLabels?.item ?? { one: \"row\", other: \"rows\" },\n selected: selectionLabels?.selected ?? \"selected\",\n clear: selectionLabels?.clear ?? \"Clear selection\",\n };\n // A cell is \"sorted\" while its column is the one the rows are ordered by.\n const sortedColumn = activeSort?.column;\n const hasWidths = columns.some((column) => column.width !== undefined);\n\n const tableStyle: CSSProperties = {};\n if (minWidth !== undefined) tableStyle.minWidth = minWidth;\n // Declared widths only hold under a fixed layout; otherwise the browser\n // treats them as hints and the columns drift, which is why 5 of the 6 legacy\n // consumers set this by hand.\n if (hasWidths) tableStyle.tableLayout = \"fixed\";\n\n const scrollRef = useRef<HTMLDivElement>(null);\n const edges = useScrollEdges(scrollRef);\n /*\n * Rows glide to their new places when the order changes.\n *\n * The key is the order the rows have in the DOM, not the order of `data`.\n * React Aria builds its collection in a pass of its own, so when a sort\n * reorders `data` the rows are still in their old places for this render and\n * only move in a later one; looking at the data order would measure nothing\n * moving. A mutation observer sees the move the moment it happens and\n * re-renders synchronously, before the browser paints, so the rows are held\n * where they were and released on the transition with no flash of the new\n * order. Positions are read from the card's own corner, so scrolling the page\n * between two sorts is not mistaken for the rows moving.\n */\n const [rowOrder, setRowOrder] = useState(\"\");\n useEffect(() => {\n const root = scrollRef.current;\n if (!root) return;\n let last = \"\";\n const look = () => {\n const next = Array.from(root.querySelectorAll<HTMLElement>(\"tbody > [data-flip]\"))\n .map((row) => row.dataset.flip)\n .join(\"\\u0000\");\n if (next === last) return;\n last = next;\n flushSync(() => setRowOrder(next));\n };\n look();\n const observer = new MutationObserver(look);\n observer.observe(root, { childList: true, subtree: true });\n return () => observer.disconnect();\n }, []);\n useFlip(scrollRef, rowOrder, \"var(--duration-smooth)\", \"var(--ease-smooth)\", true);\n const stickyCell = (part: \"header\" | \"bare\" | \"body\") =>\n tableStickyCellVariants({ part, striped: part === \"body\" && !!striped });\n\n return (\n <div\n data-slot=\"table-card\"\n data-overflow-start={edges.start || undefined}\n data-overflow-end={edges.end || undefined}\n data-sticky-first={stickyFirstColumn || undefined}\n className={cnAll(tableCardVariants({ frame }))}\n >\n <div ref={scrollRef} data-slot=\"table-scroll\" className={cnAll(tableScrollVariants())}>\n <AriaTable\n // The ref goes on the table itself, never on the table-card or\n // table-scroll divs this component wraps it in -- callers need the\n // real HTMLTableElement (to measure it, scroll it into view, etc.),\n // not one of the two divs this component renders around it.\n ref={ref}\n {...props}\n data-slot=\"table\"\n className={cn(tableVariants(), className)}\n style={tableStyle}\n selectionMode={selectable ? \"multiple\" : undefined}\n selectedKeys={selectable ? selection : undefined}\n onSelectionChange={selectable ? commitSelection : undefined}\n sortDescriptor={activeSort}\n onSortChange={(descriptor) => {\n if (!isControlled) setUncontrolledSort(descriptor);\n // Back to the first page: the order changed under you, so staying on\n // page 3 shows rows you did not ask to see.\n setPage(1);\n onSortChange?.(descriptor);\n }}\n onRowAction={\n onRowAction\n ? (key) => {\n const row = rowsByKey.get(key);\n if (row !== undefined) onRowAction(row);\n }\n : undefined\n }\n >\n <AriaTableHeader\n data-slot=\"table-header\"\n className={cnAll(tableHeaderVariants({ headerFill }))}\n >\n {selectable ? (\n <AriaColumn\n id={SELECT_COLUMN_ID}\n data-slot=\"table-column\"\n className={cnAll(\n tableColumnVariants({ headerType }),\n SELECT_CELL,\n stickyFirstColumn && stickyCell(headerFill === \"none\" ? \"bare\" : \"header\"),\n stickyFirstColumn && \"border-e-0\",\n )}\n >\n {/* The header box selects every row, and shows a dash while only some are chosen. */}\n <Checkbox slot=\"selection\" aria-label={selectionLabels?.selectAll} />\n </AriaColumn>\n ) : null}\n {columns.map((column) => (\n <AriaColumn\n key={column.id}\n id={column.id}\n isRowHeader={column === columns[0]}\n allowsSorting={column.sortable}\n data-slot=\"table-column\"\n data-sorted={sortedColumn === column.id || undefined}\n className={cnAll(\n tableColumnVariants({\n align: column.align ?? (column.numeric ? \"end\" : undefined),\n headerType,\n }),\n stickyFirstColumn &&\n column === columns[0] &&\n stickyCell(headerFill === \"none\" ? \"bare\" : \"header\"),\n stickyFirstColumn && selectable && column === columns[0] && \"start-11\",\n )}\n style={column.width === undefined ? undefined : { width: column.width }}\n >\n {column.header}\n {/*\n * A sortable column says so BEFORE it is sorted. Rendering the\n * caret only once sorted left a sortable header looking exactly\n * like a fixed one, so the only way to discover sorting was to\n * click a header and see whether anything moved -- the same gap\n * the product closed in its own header, and the reason the\n * paired chevron exists rather than an arrow.\n */}\n {column.sortable ? (\n <SortCaretGlyph\n direction={activeSort?.column === column.id ? activeSort.direction : \"none\"}\n />\n ) : null}\n </AriaColumn>\n ))}\n {onRowAction ? (\n /*\n * The chevron's own column. Its header is intentionally EMPTY: a\n * visually-hidden label would be user-visible copy, and section 7\n * rule 9 has nowhere to put it until useMessages() exists.\n */\n <AriaColumn\n id={CHEVRON_COLUMN_ID}\n data-slot=\"table-column\"\n className={cnAll(tableColumnVariants({ headerType }), CHEVRON_CELL)}\n >\n {\"\"}\n </AriaColumn>\n ) : null}\n </AriaTableHeader>\n <AriaTableBody\n data-slot=\"table-body\"\n renderEmptyState={\n empty === undefined\n ? undefined\n : () => (\n <div data-slot=\"table-empty\" className={cnAll(tableEmptyVariants())}>\n {empty}\n </div>\n )\n }\n >\n {visibleRows.map((row, index) => (\n <AriaRow\n key={String(getRowId(row))}\n id={getRowId(row)}\n data-slot=\"table-row\"\n // The rows useFlip glides when a sort reorders them (the value is the key the order is read from).\n data-flip={String(getRowId(row))}\n data-highlighted={isRowHighlighted?.(row) || undefined}\n className={cn(\n cnAll(\n tableRowVariants({ variant, striped }),\n onRowAction && \"cursor-pointer\",\n ),\n rowClassName?.(row),\n )}\n >\n {selectable ? (\n <AriaCell\n data-slot=\"table-cell\"\n className={cnAll(\n tableCellVariants(),\n SELECT_CELL,\n stickyFirstColumn && stickyCell(\"body\"),\n stickyFirstColumn && \"border-e-0\",\n )}\n >\n <Checkbox slot=\"selection\" aria-label={selectionLabels?.selectRow} />\n </AriaCell>\n ) : null}\n {columns.map((column) => {\n const value: unknown = column.accessor(row);\n return (\n <AriaCell\n key={column.id}\n data-slot=\"table-cell\"\n data-sorted={sortedColumn === column.id || undefined}\n className={cnAll(\n tableCellVariants({\n align: column.align ?? (column.numeric ? \"end\" : undefined),\n numeric: column.numeric,\n }),\n stickyFirstColumn && column === columns[0] && stickyCell(\"body\"),\n stickyFirstColumn && selectable && column === columns[0] && \"start-11\",\n )}\n >\n {column.cell\n ? column.cell({ value, row, index })\n : renderValue(column, value, row)}\n </AriaCell>\n );\n })}\n {onRowAction ? (\n <AriaCell\n data-slot=\"table-cell\"\n className={cnAll(tableCellVariants(), CHEVRON_CELL)}\n >\n <RowChevronGlyph />\n </AriaCell>\n ) : null}\n </AriaRow>\n ))}\n </AriaTableBody>\n {totalRow ? (\n <AriaTableFooter data-slot=\"table-footer\" className={cnAll(tableFooterVariants())}>\n <AriaRow id=\"__table_total\">\n {selectable ? (\n <AriaCell\n data-slot=\"table-cell\"\n className={cnAll(\n tableCellVariants(),\n SELECT_CELL,\n stickyFirstColumn && stickyCell(\"bare\"),\n stickyFirstColumn && \"border-e-0\",\n )}\n >\n {\"\"}\n </AriaCell>\n ) : null}\n {columns.map((col) => (\n <AriaCell\n key={col.id}\n data-slot=\"table-cell\"\n className={cnAll(\n tableCellVariants({\n align: col.align ?? (col.numeric ? \"end\" : undefined),\n numeric: col.numeric,\n }),\n stickyFirstColumn && col === columns[0] && stickyCell(\"bare\"),\n stickyFirstColumn && selectable && col === columns[0] && \"start-11\",\n )}\n >\n {totalRow(col)}\n </AriaCell>\n ))}\n {onRowAction ? (\n <AriaCell\n data-slot=\"table-cell\"\n className={cnAll(tableCellVariants(), CHEVRON_CELL)}\n >\n {\"\"}\n </AriaCell>\n ) : null}\n </AriaRow>\n </AriaTableFooter>\n ) : null}\n </AriaTable>\n </div>\n {selectable ? (\n <SelectionBar\n count={selectedCount}\n total={data.length}\n labels={selectLabels}\n onClear={() => commitSelection(new Set())}\n />\n ) : null}\n {rowsPerPage === undefined || (pageCount <= 1 && !pageSizeOptions) ? null : (\n <nav\n data-slot=\"table-pagination\"\n aria-label={`${props[\"aria-label\"]} pagination`}\n className={cnAll(tablePaginationVariants())}\n >\n {/* Present even when empty, so the select stays on the trailing edge. */}\n <div data-slot=\"table-pagination-pages\" className=\"flex items-center gap-1\">\n {pageCount <= 1 ? null : (\n <>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-(--density-control-h-sm) px-0\"\n aria-label={labels.previous}\n isDisabled={currentPage <= 1}\n onPress={() => setPage(currentPage - 1)}\n >\n <PageChevronGlyph direction=\"previous\" />\n </Button>\n {pageWindow(currentPage, pageCount).map((entry, index) =>\n entry === \"ellipsis\" ? (\n <span\n // The gap is decoration; the pages either side of it are\n // what a reader navigates by, so this is not announced.\n key={`ellipsis-${String(index)}`}\n data-slot=\"table-pagination-ellipsis\"\n aria-hidden=\"true\"\n className=\"px-2 text-sm text-muted-foreground\"\n >\n &hellip;\n </span>\n ) : (\n <Button\n key={entry}\n variant={entry === currentPage ? \"primary\" : \"ghost\"}\n size=\"sm\"\n className=\"min-w-(--density-control-h-sm) px-2\"\n aria-label={labels.page(entry, entry === currentPage)}\n onPress={() => setPage(entry)}\n >\n {entry}\n </Button>\n ),\n )}\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-(--density-control-h-sm) px-0\"\n aria-label={labels.next}\n isDisabled={currentPage >= pageCount}\n onPress={() => setPage(currentPage + 1)}\n >\n <PageChevronGlyph direction=\"next\" />\n </Button>\n </>\n )}\n </div>\n {pageSizeOptions === undefined ? null : (\n <Select\n // Select is w-full by default, which in this flex row stretches\n // it across the whole bar and drags it back to the leading edge\n // the moment the page numbers retire.\n className=\"w-auto\"\n label={labels.rowsPerPage}\n labelHidden\n selectedKey={String(rowsPerPage)}\n onSelectionChange={(key) => {\n const size = Number(key);\n setChosenSize(size);\n // Page 4 of 18 is page 1 of 2 at a larger size; keeping the\n // number would land the reader past the end of the data.\n setPage(1);\n onPageSizeChange?.(size);\n }}\n >\n {pageSizeOptions.map((option) => (\n <SelectItem key={option} id={String(option)}>\n {labels.perPage(option)}\n </SelectItem>\n ))}\n </Select>\n )}\n </nav>\n )}\n </div>\n );\n}\n// forwardRef cannot carry a type parameter through a .tsx call expression, so\n// Table<T>'s generic signature is restored here with a type-only cast on the\n// export; it costs nothing at runtime and the inner function is still named\n// \"Table\" for DevTools.\n) as <T>(props: TableProps<T> & RefAttributes<HTMLTableElement>) => ReactElement | null;\n"],"mappings":"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"}
1
+ {"version":3,"file":"table.js","names":["Button","Table","AriaButton","AriaTable","AriaTableHeader","AriaColumn","Checkbox","AriaTableBody","AriaRow","AriaCell","AriaTableFooter","Select"],"sources":["../../src/table/table.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n forwardRef,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type ForwardedRef,\n type ReactElement,\n type ReactNode,\n type RefAttributes,\n type RefObject,\n} from \"react\";\nimport {\n Button as AriaButton,\n Cell as AriaCell,\n Column as AriaColumn,\n Row as AriaRow,\n Table as AriaTable,\n TableBody as AriaTableBody,\n TableFooter as AriaTableFooter,\n TableHeader as AriaTableHeader,\n type Selection,\n type SortDescriptor,\n} from \"react-aria-components\";\nimport { flushSync } from \"react-dom\";\n\nimport { Button } from \"../button/button.js\";\nimport { Checkbox } from \"../checkbox/checkbox.js\";\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { useFlip } from \"../lib/flip.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport { Select, SelectItem } from \"../select/select.js\";\n\n/**\n * Table, ported from the legacy shadcn table onto React Aria's Table.\n *\n * The legacy component in the product app rendered NOTHING -- its body was\n * `return <>{children(renderProps)}</>` -- so all six of its consumers\n * hand-built `<table>`, `tableLayout: fixed`, a `<colgroup>` and a min-width,\n * and its config object was opt-OUT, so they wrote paragraphs of\n * `enabled: false`. Six consumers produced six different tables. This one\n * renders from `data` + `columns` + `getRowId`; everything else is opt-in.\n *\n * WIDTHS ARE CSS, and that is not a stylistic choice. React Aria's own `width`\n * on a `<Column>` is ignored outside a `ResizableTableContainer`, and its\n * dev-warning is dead code in 1.21.1 -- `for (let prop in ['width', ...])`\n * iterates the INDICES, so the guard tests `'0' in props` and never fires. A\n * width handed to React Aria disappears with no error and no console output:\n * `width={120}` measured 42.675px in a browser. So `Column.width` is emitted\n * as a style on the header cell, and any width puts the table into\n * `table-layout: fixed` -- without which a declared width is only a hint.\n *\n * THE ROW HIGHLIGHT IS PAINTED FROM `:hover` AS WELL AS `data-[hovered]`, and\n * that is a departure from the rule that state comes from React Aria data\n * attributes rather than bare pseudo-classes. React Aria stamps `data-hovered`\n * on a row ONLY when the row is interactive -- `onRowAction` or a selection\n * mode -- exactly as it gates `Column` hover on `allowsSorting`. Four of the\n * six legacy consumers are read-only tables that painted a hover fill, so\n * `data-[hovered]` alone would drop their pointer affordance in silence. The\n * rule exists because `:focus` fires on click where `data-[focus-visible]`\n * does not; `:hover` has no such twin, and on an interactive row the two\n * selectors match the same element with the same colour. The keyboard needs no\n * workaround: `data-focused` IS emitted on read-only rows.\n *\n * `leading-*` sits on the table rather than on the cell. tailwind-merge treats\n * a later `text-*` as owning line-height and silently strips a `leading-*`\n * that precedes it, and the cells carry `text-start`/`text-center`/`text-end`.\n * Line-height inherits, so putting it one level up removes the hazard instead\n * of policing it.\n */\n\n/**\n * The card. Every table in the product sits in one.\n *\n * The edge uses `separator`, NOT `border`, and that reverses what an earlier\n * revision of this file claimed. Measured against the running product, its card\n * is `border border-border` resolving to #e6e6e6 -- the same hairline as the row\n * dividers. The earlier argument was that the card edge is where WCAG 1.4.11\n * lands; on reflection it does not. 1.4.11 covers what is needed to identify\n * user interface COMPONENTS and their states, and a static table is not one:\n * the interactive parts inside it -- sortable headers, pagination buttons --\n * carry their own focus indicators and borders, which are still held to 3:1.\n *\n * overflow-hidden is load-bearing: the header fill runs to the table's edge and\n * would square off the rounded corners without it.\n *\n * THE EDGE SHADOWS. A table wider than its card scrolls inside it, and on a\n * trackpad the scrollbar only shows while scrolling, so a cut-off column looks\n * like the table's last one. The card carries `data-overflow-start` and\n * `data-overflow-end` while columns are hidden on that side (the Table sets\n * them from its scroll position), and each draws a shadow on that edge: the\n * text colour fading to nothing at 15%, so it follows the theme. With a sticky\n * first column the start is never hidden behind the card's edge, so the start\n * shadow stays off and the sticky column's own divider does that job.\n */\nexport const tableCardVariants = cva(\n [\n \"w-full box-border overflow-hidden relative isolate\",\n \"before:pointer-events-none before:absolute before:inset-y-0 before:start-0 before:z-2 before:w-6\",\n \"before:bg-linear-to-r rtl:before:bg-linear-to-l before:from-foreground before:to-transparent\",\n \"before:opacity-0 motion-safe:before:transition-opacity\",\n \"data-[overflow-start]:not-data-[sticky-first]:before:opacity-15\",\n \"after:pointer-events-none after:absolute after:inset-y-0 after:end-0 after:z-2 after:w-6\",\n \"after:bg-linear-to-l rtl:after:bg-linear-to-r after:from-foreground after:to-transparent\",\n \"after:opacity-0 motion-safe:after:transition-opacity\",\n \"data-[overflow-end]:after:opacity-15\",\n ],\n {\n variants: {\n frame: {\n card: \"rounded-panel border-(length:--border-width) border-separator bg-surface\",\n /*\n * No frame. Eleven of the product's tables either sit inside a card\n * already -- gaining a second nested border and radius from an\n * unconditional one -- or are deliberately bare on the page.\n */\n none: \"\",\n },\n },\n defaultVariants: { frame: \"card\" },\n },\n);\n\n/**\n * An absent value is an em-dash, not the word \"null\".\n *\n * The default renderer was `String(value)`, which paints the literal text\n * `null` into the cell -- a defect rather than a gap, and six of the product's\n * tables print a dash there. Zero and empty string are values and are left\n * alone; only null and undefined are absent.\n */\nfunction renderValue<T>(column: Column<T, unknown>, value: unknown, row: T): ReactNode {\n if (value === null || value === undefined) return \"—\";\n\n const formatted = column.format?.(value, row);\n if (formatted !== undefined) return formatted;\n\n /*\n * Only primitives have a sensible default rendering. String(someObject) is\n * \"[object Object]\", which is the same class of silent nonsense as the\n * literal \"null\" this function exists to stop -- so an object-valued column\n * renders nothing until it is given `format` or `cell`, which is loud enough\n * to notice and quiet enough not to lie.\n */\n if (typeof value === \"string\" || typeof value === \"number\") return value;\n if (typeof value === \"boolean\") return String(value);\n return null;\n}\n\n/** Reserved id for the chevron column, so it cannot collide with a real one. */\nconst CHEVRON_COLUMN_ID = \"__table_row_action\";\n\n/*\n * The chevron column is sized to its glyph, not to a share of the table.\n * Given a percentage AND the usual --density-row-pad-x on both sides it pushed\n * the total past 100% under table-layout: fixed, and a table that fits grew a\n * horizontal scrollbar. w-10 is the spacing scale rather than a bracket value,\n * which check:hardcoding would reject.\n */\nconst CHEVRON_CELL = \"w-10 px-0 text-center\";\n\n/** Reserved id for the checkbox column of a selectable table. */\nconst SELECT_COLUMN_ID = \"__table_row_select\";\n\n/** The checkbox column is as wide as its box needs, with the table's usual inset on its leading side. */\nconst SELECT_CELL = \"w-11 px-0 ps-(--density-row-pad-x)\";\n\n/** The horizontal scroll container. The table owns it; 5 of 6 consumers built one by hand. */\nexport const tableScrollVariants = cva([\"w-full overflow-x-auto box-border\"]);\n\nexport const tableVariants = cva(\n [\n \"w-full border-collapse box-border\",\n // font-sans and the explicit colours are not boilerplate: the reset in\n // @seekalfred/styles is OPT-IN, so a consumer may load none and an unstyled\n // table would inherit the host's serif on a transparent ground.\n \"font-sans text-base leading-(--leading-normal)\",\n \"bg-surface text-foreground\",\n ],\n {\n variants: {\n /*\n * `compact` is the product's tight report table: `py-2` rows in small\n * type. The type steps down here and the padding on the cells, because a\n * density SCOPE (`data-density`) changes space and never type.\n */\n density: { default: \"\", compact: \"text-sm\" },\n },\n defaultVariants: { density: \"default\" },\n },\n);\n\n/**\n * The header is near-white, not a grey band.\n *\n * The product fills it with its `--neutral-200`, #fffcfc: white carrying three\n * counts of warmth in green and blue. An earlier revision of this file called\n * that \"not worth a token of its own\" and used `background`, which made the\n * header exactly the colour of the rows -- so the band vanished and the only\n * thing separating the header was the divider under it. The difference is\n * small on purpose; absent is not the same as small. It is `--color-header-fill`\n * now, and it inverts properly in dark rather than being pinned to white.\n */\nexport const tableHeaderVariants = cva([\"text-muted-foreground\"], {\n variants: { headerFill: { subtle: \"bg-header-fill\", none: \"bg-transparent\" } },\n defaultVariants: { headerFill: \"subtle\" },\n});\n\n/** The pagination bar. A sibling of the table, never a row inside it. */\nexport const tablePaginationVariants = cva([\n \"flex items-center justify-between gap-(--density-control-gap) box-border\",\n \"px-(--density-row-pad-x) py-(--density-row-pad-y)\",\n \"font-sans text-sm leading-(--leading-normal) text-muted-foreground\",\n]);\n\n/**\n * The bar under a selectable table: how many rows, or how many are selected,\n * and the action that clears them. A sibling of the table, like the pagination.\n */\nexport const tableSelectionBarVariants = cva([\n \"flex min-h-11 items-center justify-between gap-(--density-control-gap) box-border\",\n \"border-t-(length:--border-width) border-t-separator\",\n \"ps-(--density-row-pad-x) pe-2 font-sans text-sm leading-(--leading-normal)\",\n \"text-muted-foreground data-[active]:text-foreground\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-standard) motion-safe:ease-settle\",\n]);\n\n/**\n * What stands in for the rows when there are none: centred, muted, and tall\n * enough to read as a state rather than as a squashed row.\n */\nexport const tableEmptyVariants = cva([\n \"flex min-h-30 items-center justify-center px-(--density-row-pad-x) py-6\",\n \"box-border text-center text-sm text-muted-foreground wrap-anywhere\",\n]);\n\n/** The footer band. Heavier than a row rule, because it closes the table. */\nexport const tableFooterVariants = cva([\n \"bg-surface font-semibold text-foreground\",\n \"border-t-(length:--border-width) border-t-border\",\n]);\n\n/** Shared focus treatment. Authored once; both the column header and the row take it. */\nconst FOCUS_RING = [\n // outline-solid is load-bearing: outline-none sets --tw-outline-style to\n // none, and the width utility resolves its style from that same variable, so\n // a focus rule can apply a width and a colour and paint nothing.\n \"outline-none\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n];\n\nexport const tableColumnVariants = cva(\n [\n \"px-(--density-row-pad-x) py-(--density-row-pad-y)\",\n \"box-border align-middle text-sm font-semibold select-none\",\n \"data-[sorted]:bg-foreground/3 data-[sorted]:text-foreground\",\n ...FOCUS_RING,\n // The whole cell is the hit target, not just the label. At compact density\n // --density-row-pad-y is 7px, so a control wrapping only its text lands\n // under the 24x24 floor of WCAG 2.2 target size (2.5.8) -- which the story\n // harness enables explicitly on top of axe's defaults.\n \"data-[allows-sorting]:cursor-pointer\",\n ],\n {\n variants: {\n align: { start: \"text-start\", center: \"text-center\", end: \"text-end\" },\n /* Block padding of the product's tight tables (`py-2`), in place of the density token. */\n density: { default: \"\", compact: \"py-2\" },\n /*\n * A header that is there for assistive tech only (`headerHidden`). The\n * cell keeps its width, which is what lays the columns out, and loses its\n * block padding, so the row collapses to nothing while its label stays in\n * the accessibility tree. In the grid a reader can still arrow onto it,\n * so it opens up again while it holds keyboard focus.\n */\n hidden: {\n true: \"group/column py-0 data-[focus-visible]:py-(--density-row-pad-y)\",\n false: \"\",\n },\n /*\n * Eleven tables -- a plurality of the app -- label columns in uppercase\n * with tracking, and not one was reachable: the label was hardcoded\n * `text-sm font-semibold`, and `className` lands on the <table>.\n */\n headerType: {\n sentence: \"text-sm leading-none\",\n caps: \"text-xs uppercase tracking-wide leading-none\",\n },\n },\n compoundVariants: [{ density: \"compact\", hidden: true, class: \"data-[focus-visible]:py-2\" }],\n defaultVariants: { align: \"start\", headerType: \"sentence\", density: \"default\", hidden: false },\n },\n);\n\n/**\n * The disclosure button that opens a row's sub-rows (`getSubRows`).\n *\n * A real button in the first cell, named by that cell's own content and\n * carrying `aria-expanded`, so a reader hears \"Meta, button, collapsed\"\n * without the library inventing copy. 24px square: the WCAG 2.2 target-size\n * floor (2.5.8), and the step each level of sub-rows is indented by, so a\n * child's text lines up one button-width in from its parent's. The chevron\n * turns a quarter when the row opens; with reduced motion it simply flips.\n */\nexport const tableExpandButtonVariants = cva([\n \"inline-flex size-6 shrink-0 items-center justify-center rounded-sm cursor-pointer\",\n \"text-muted-foreground data-[hovered]:bg-muted data-[hovered]:text-foreground\",\n \"[&_svg]:motion-safe:transition-transform [&_svg]:motion-safe:duration-(--duration-snappy) [&_svg]:motion-safe:ease-snappy\",\n \"data-[expanded]:[&_svg]:rotate-90 rtl:data-[expanded]:[&_svg]:-rotate-90\",\n ...FOCUS_RING,\n]);\n\n/**\n * A group header row (`isGroupRow`): one cell spanning the row, naming the rows\n * under it (\"Mobile\" / \"Desktop\"). The header band's fill and label type, so it\n * reads as a heading of the rows that follow rather than as one of them.\n */\nexport const tableGroupRowVariants = cva([\n \"bg-header-fill hover:bg-header-fill\",\n \"[&>td]:text-sm [&>td]:font-semibold [&>td]:text-muted-foreground\",\n]);\n\n/**\n * The sort control of a column header in a plain table (`keyboardModel=\"table\"`).\n *\n * In the grid the header cell is the control; in a plain table a cell cannot\n * take focus, so the label sits in a real button that Tab reaches. It wears\n * the header's own type and colour, and is at least 24px tall so a compact\n * header still clears the WCAG 2.2 target size (2.5.8).\n */\nexport const tableSortButtonVariants = cva([\n \"inline-flex min-h-6 items-center cursor-pointer\",\n \"bg-transparent p-0 border-0 font-[inherit] text-[length:inherit] leading-[inherit] text-inherit\",\n \"outline-none focus-visible:outline-solid\",\n \"focus-visible:outline-(length:--focus-ring-width)\",\n \"focus-visible:outline-offset-(--focus-ring-offset)\",\n \"focus-visible:outline-(--color-ring)\",\n]);\n\nexport const tableRowVariants = cva(\n [\n // The group a sticky first cell reads the row's state from (tableStickyCellVariants).\n // bg-surface: opaque, so rows passing each other while a sort glides them\n // cover one another cleanly instead of blending their text.\n \"group/row box-border bg-surface\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\n // See the header comment: :hover is what covers a read-only row.\n \"hover:bg-muted data-[hovered]:bg-muted data-[focused]:bg-muted\",\n // A chosen row: the muted fill, a step darker under the pointer.\n \"data-[selected]:bg-muted data-[selected]:hover:bg-separator data-[selected]:data-[hovered]:bg-separator\",\n // After the state fills, so the marked row keeps its identity while hovered.\n \"data-[highlighted]:bg-primary-subtle\",\n ...FOCUS_RING,\n ],\n {\n variants: {\n /*\n * `grid` is gone. Across 25 real tables in the product not one drew\n * vertical rules between cells -- a variant nobody asked for, shipped\n * while the caps header eleven tables need was unreachable.\n */\n variant: {\n lined: \"border-t-(length:--border-width) border-t-separator\",\n plain: \"\",\n },\n /* rowClassName could not fake this: it gets the row but not its index. */\n striped: { true: \"[&:nth-child(even)]:bg-row-stripe\", false: \"\" },\n },\n defaultVariants: { variant: \"lined\", striped: false },\n },\n);\n\nexport const tableCellVariants = cva(\n [\n \"px-(--density-row-pad-x) py-(--density-row-pad-y)\",\n // wrap-anywhere: a long word or URL breaks inside its cell rather than\n // pushing the column wider than the card.\n \"box-border align-middle wrap-anywhere\",\n // The sorted column wears a still tint, which fills the gaps that open\n // while rows pass each other.\n \"data-[sorted]:bg-foreground/3\",\n ...FOCUS_RING,\n ],\n {\n variants: {\n align: { start: \"text-start\", center: \"text-center\", end: \"text-end\" },\n /* Ten tables set tabular figures -- the app's most consistent convention. */\n numeric: { true: \"tabular-nums whitespace-nowrap\", false: \"\" },\n /* See tableColumnVariants. */\n density: { default: \"\", compact: \"py-2\" },\n },\n defaultVariants: { align: \"start\", numeric: false, density: \"default\" },\n },\n);\n\n/**\n * The first cell of every row, held in place while the rest scroll under it\n * (`stickyFirstColumn`).\n *\n * A sticky cell is opaque -- otherwise the scrolled cells show through it -- so\n * it repaints whatever fill its row has: the row's hover, focus and highlight\n * through `group/row`, the stripe when striped. A divider on its trailing edge\n * marks where the frozen column ends.\n */\nexport const tableStickyCellVariants = cva(\n [\"sticky start-0 z-1\", \"border-e-(length:--border-width) border-e-separator\"],\n {\n variants: {\n part: {\n header: \"bg-header-fill\",\n bare: \"bg-surface\",\n body: [\n \"bg-surface\",\n \"group-hover/row:bg-muted group-data-[hovered]/row:bg-muted group-data-[focused]/row:bg-muted\",\n \"group-data-[highlighted]/row:bg-primary-subtle\",\n ],\n },\n striped: { true: \"\", false: \"\" },\n },\n compoundVariants: [{ part: \"body\", striped: true, class: \"group-even/row:bg-row-stripe\" }],\n defaultVariants: { part: \"body\", striped: false },\n },\n);\n\n/**\n * Which sides of a scroll container hide content right now.\n *\n * Read from the container on scroll and whenever it or the table resizes, so\n * a window resize or rows arriving later update it too. scrollLeft runs\n * negative in a right-to-left layout; the distance travelled is what counts.\n */\nfunction useScrollEdges(scroller: RefObject<HTMLDivElement | null>) {\n const [edges, setEdges] = useState({ start: false, end: false });\n useEffect(() => {\n const el = scroller.current;\n if (!el) return;\n const update = () => {\n const hidden = el.scrollWidth - el.clientWidth;\n const travelled = Math.abs(el.scrollLeft);\n const start = hidden > 1 && travelled > 1;\n const end = hidden > 1 && travelled < hidden - 1;\n setEdges((prev) => (prev.start === start && prev.end === end ? prev : { start, end }));\n };\n update();\n el.addEventListener(\"scroll\", update, { passive: true });\n const resize = typeof ResizeObserver === \"undefined\" ? undefined : new ResizeObserver(update);\n resize?.observe(el);\n if (el.firstElementChild) resize?.observe(el.firstElementChild);\n return () => {\n el.removeEventListener(\"scroll\", update);\n resize?.disconnect();\n };\n }, [scroller]);\n return edges;\n}\n\n/**\n * Identity for a row.\n *\n * Declared here rather than imported: React's own `Key` includes `bigint`,\n * which React Aria's collections do not accept, and importing React Aria's\n * would leak its types through the public API for no gain. This IS the set\n * React Aria takes.\n */\nexport type RowKey = string | number;\n\n/**\n * Both glyphs are aria-hidden and neither is focusable.\n *\n * The chevron says \"this row does something\"; the ROW is what React Aria makes\n * pressable and announces, so a second target would be a keyboard trap for no\n * gain. The caret says which way a column is sorted -- `aria-sort` already\n * tells assistive tech, but without a glyph the direction is carried by tint\n * alone, which is WCAG 1.4.1 use-of-colour.\n *\n * Drawn inline rather than taken as props: this package ships no icon set, and\n * Checkbox and Select already inline their single glyphs this way.\n */\n/**\n * Which page numbers to show, with gaps collapsed to an ellipsis.\n *\n * One sibling either side of the current page, plus the first and last, which\n * is what the product's own pager renders at its call sites: page 1 of 18 gives\n * `1 2 ... 18`. It is not a prop, because no consumer has asked for a second\n * width and a number nobody tunes is a number better inlined.\n */\nfunction pageWindow(current: number, total: number): (number | \"ellipsis\")[] {\n const pages: (number | \"ellipsis\")[] = [];\n const start = Math.max(1, current - 1);\n const end = Math.min(total, current + 1);\n\n if (start > 1) {\n pages.push(1);\n if (start > 2) pages.push(\"ellipsis\");\n }\n for (let page = start; page <= end; page++) pages.push(page);\n if (end < total) {\n if (end < total - 1) pages.push(\"ellipsis\");\n pages.push(total);\n }\n return pages;\n}\n\nfunction PageChevronGlyph({ direction }: { direction: \"previous\" | \"next\" }) {\n return (\n <svg\n data-slot=\"table-pagination-chevron\"\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"size-(--icon-sm) rtl:-scale-x-100\"\n >\n <path d={direction === \"next\" ? \"m6 3 5 5-5 5\" : \"m10 3-5 5 5 5\"} />\n </svg>\n );\n}\n\nfunction RowChevronGlyph() {\n return (\n <svg\n data-slot=\"table-row-chevron\"\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"size-(--icon-sm) text-muted-foreground\"\n >\n {/* Logical, so it mirrors in RTL with the rest of the table. */}\n <path d=\"m6 3 5 5-5 5\" className=\"rtl:-scale-x-100\" />\n </svg>\n );\n}\n\nfunction ExpandChevronGlyph() {\n return (\n <svg\n data-slot=\"table-row-expand-chevron\"\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"size-(--icon-sm) rtl:-scale-x-100\"\n >\n <path d=\"m6 3 5 5-5 5\" />\n </svg>\n );\n}\n\nfunction SortCaretGlyph({ direction }: { direction: \"ascending\" | \"descending\" | \"none\" }) {\n return (\n <svg\n data-slot=\"table-sort-caret\"\n data-direction={direction}\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"table-sort-caret size-(--icon-sm) ms-1 inline-block align-middle\"\n >\n {/*\n Both glyphs are always drawn and the direction decides which shows: the\n up-down pair says \"this sorts\" while the column is idle, and where the\n column becomes sorted it gives way to a single arrow that fades in and\n turns over when the direction flips (table-sort-caret, in styles.css).\n */}\n <g className=\"table-sort-idle\">\n <path d=\"m4 7 4-4 4 4\" />\n <path d=\"m4 9 4 4 4-4\" />\n </g>\n <g className=\"table-sort-arrow\">\n <path d=\"M8 13V3\" />\n <path d=\"m4.5 6.5 3.5-3.5 3.5 3.5\" />\n </g>\n </svg>\n );\n}\n\n/** One column of a `Table`. */\nexport type Column<T, V = unknown> = {\n /** Stable key. Becomes the column's id and the sort descriptor's `column`. */\n id: string;\n /** Header content. */\n header: ReactNode;\n /** Pulls this column's value out of a row. */\n accessor: (row: T) => V;\n /**\n * Renders the cell. Defaults to `format?.(value, row) ?? String(value)`.\n *\n * Method syntax, not an arrow property, and that is load-bearing. A table\n * holds columns of DIFFERENT value types in one array, so the array element\n * has to be `Column<T, unknown>` -- and under `strictFunctionTypes` an arrow\n * property is contravariant in its parameters, which would make\n * `Column<T, string>` unassignable to it. Method members are checked\n * bivariantly, which is precisely the semantics a heterogeneous collection\n * needs. The alternative is `Column<T, any>`, and this package has no\n * eslint-disable in it anywhere.\n *\n * `index` is the row's position among its siblings (on the current page, in\n * the current sort order), so with `getSubRows` a sub-row counts from 0 under\n * its parent and opening a row never renumbers the rows below it.\n */\n cell?(ctx: { value: V; row: T; index: number }): ReactNode;\n /**\n * Value to string. Deliberately separate from `cell`: this is what CSV\n * export and sort comparators consume, and a ReactNode is no use to either.\n */\n format?(value: V, row: T): string;\n /**\n * Logical alignment, applied to BOTH the column header and its cells --\n * React Aria keeps no link between the two, so the component owns it.\n */\n align?: \"start\" | \"center\" | \"end\";\n /**\n * Column width. Any width on any column puts the table into\n * `table-layout: fixed`.\n *\n * Emitted as CSS, NEVER forwarded to React Aria's own `width` prop. React\n * Aria ignores that one outside a `ResizableTableContainer`, and its\n * dev-warning is dead code in 1.21.1 (`for...in` over an array literal, so\n * the guard tests `'0' in props`). A width handed to it disappears with no\n * error and no console output.\n */\n width?: number | `${number}%`;\n /**\n * A column of figures: tabular numerals, end-aligned unless `align` says\n * otherwise. Ten of the product's tables set tabular figures -- the most\n * consistently applied convention in the app. Without it, proportional\n * digits leave a column of numbers ragged.\n */\n numeric?: boolean;\n /** Opts this column into sorting. Off by default. */\n sortable?: boolean;\n};\n\nexport interface TableProps<T> extends VariantProps<typeof tableRowVariants> {\n /** The rows to render. */\n data: readonly T[];\n /** The columns, in display order. */\n columns: readonly Column<T, unknown>[];\n /**\n * Stable identity for a row. Used as its key and to resolve `onRowAction`.\n * Pass a stable reference (a module-level function or `useCallback`): the row\n * index is memoised on it, so an inline arrow recomputes it every render.\n */\n getRowId: (row: T) => RowKey;\n /**\n * Required. React Aria's Table renders no `<caption>`, so this is the\n * accessible name and there is no other source for it.\n */\n \"aria-label\": string;\n /**\n * The sorted column and direction, CONTROLLED.\n *\n * Pass this and the table stops sorting: you own the row order, which is\n * what server-side sorting needs. Leave it off and the table sorts `data`\n * itself from `defaultSortDescriptor` and header clicks.\n */\n sortDescriptor?: SortDescriptor;\n /** The initial sort when uncontrolled. */\n defaultSortDescriptor?: SortDescriptor;\n /** Fires when a sortable column header is activated, controlled or not. */\n onSortChange?: (descriptor: SortDescriptor) => void;\n /**\n * Fires on click or Enter. Receives the ROW, where React Aria's own handler\n * receives a key -- every legacy consumer looked the row up again anyway.\n *\n * Setting it also makes rows interactive, which is what switches React\n * Aria's `data-hovered` on.\n */\n onRowAction?: (row: T) => void;\n /**\n * Marks a row as the viewer's own -- the product tints \"Alfred AI (You)\".\n *\n * The tint is not the message. A fill alone is WCAG 1.4.1 use-of-colour, so\n * the highlighted row is expected to say so in its own content, the way the\n * product's does. This tints and sets `data-highlighted`; it does not invent\n * meaning the row does not already carry.\n */\n isRowHighlighted?: (row: T) => boolean;\n /** Extra classes per row, for styling that depends on row DATA. */\n rowClassName?: (row: T) => string;\n /**\n * Rendered in place of the rows when `data` is empty.\n *\n * There is no default string on purpose: user-visible copy belongs in\n * `useMessages()`, a mechanism this package does not have yet, and it is the\n * one rule ARCHITECTURE says cannot be retrofitted.\n */\n empty?: ReactNode;\n /**\n * Minimum table width in px. Below it the wrapper scrolls horizontally, and\n * the card shows a shadow on each edge that hides columns.\n */\n minWidth?: number;\n /**\n * Keeps the first column in place while the rest scroll sideways. For a\n * register wider than its card whose first column says which row is which\n * (an invoice number, a person's name).\n */\n stickyFirstColumn?: boolean;\n /**\n * Rows per page. Omit it and every row renders with no pagination UI.\n *\n * The table holds the page itself and slices AFTER sorting, so a sort\n * reorders the whole set rather than shuffling the current page, and lands\n * you back on page 1.\n *\n * With `pageSizeOptions` this is the STARTING size and the table owns it\n * from then on; a later change to the prop does not move a reader who has\n * chosen their own. Without them it is simply the size.\n */\n pageSize?: number;\n /**\n * Rows-per-page choices. Given them, the bar grows a select on its trailing\n * side, which is the arrangement every paged table in the product uses:\n * page numbers leading, rows-per-page trailing.\n */\n pageSizeOptions?: readonly number[];\n /** Notified after the reader picks a new size. The table has already applied it. */\n onPageSizeChange?: (pageSize: number) => void;\n /**\n * Copy for the pagination controls.\n *\n * These carry English defaults, which is a known debt: ARCHITECTURE section\n * 7 rule 9 routes user-visible copy through a `useMessages()` hook that does\n * not exist in this package yet. They are props so a consumer is not stuck\n * waiting for it, and so the eventual mechanism has somewhere to land. The\n * navigation landmark takes its name from `aria-label` instead, which avoids\n * inventing a fourth string.\n */\n pageLabels?: {\n previous?: string;\n next?: string;\n /**\n * Accessible name for a page button.\n *\n * `isCurrent` exists because `aria-current` cannot reach the button.\n * React Aria's Button runs its props through `filterDOMProps`, whose\n * allowlist covers `id` and the labelling attributes and nothing else, so\n * an `aria-current` handed to it is dropped with no error -- the same\n * class of silent loss as `Column.width`. The current page therefore says\n * so in its name, which every screen reader announces.\n */\n page?: (page: number, isCurrent: boolean) => string;\n /** Option text in the rows-per-page select. */\n perPage?: (pageSize: number) => string;\n /** Accessible name for the rows-per-page select itself. */\n rowsPerPage?: string;\n };\n /**\n * Row selection. `multiple` adds a checkbox column (the library's Checkbox,\n * on React Aria's table selection) with a select-all box in the header that\n * shows a dash while only some rows are chosen, and a bar under the table\n * that counts the rows and clears the choice. Clicking a row's checkbox, or\n * the row itself, toggles it; Escape clears. `none` (the default) adds\n * nothing.\n */\n selectionMode?: \"none\" | \"multiple\";\n /**\n * The chosen rows, CONTROLLED: a set of `getRowId` values, or `\"all\"`.\n * Pass this and the table stops keeping the selection itself.\n */\n selectedKeys?: Selection;\n /** The rows chosen at the start, when uncontrolled. */\n defaultSelectedKeys?: Selection;\n /** Fires with the new selection (a set of ids, or `\"all\"`), controlled or not. */\n onSelectionChange?: (keys: Selection) => void;\n /**\n * Copy for the selection controls, with English defaults for the same reason\n * as `pageLabels`: a `useMessages()` mechanism does not exist in this package\n * yet.\n */\n selectionLabels?: {\n /** Accessible name of the header checkbox. React Aria's own, localized (\"Select All\"), when omitted. */\n selectAll?: string;\n /**\n * The word before a row's name in its checkbox's accessible name. React\n * Aria appends the text of the row's first column itself, so \"Select\"\n * becomes \"Select Meta\"; its own localized word is used when omitted.\n */\n selectRow?: string;\n /** What a row is called in the count: \"1 row\", \"5 rows\". */\n item?: { one: string; other: string };\n /** The word after the count while some rows are chosen: \"3 selected\". */\n selected?: string;\n /** The clear button's text. */\n clear?: string;\n };\n /**\n * Whether the table draws its own card. `none` for one that already sits\n * inside a card, or is meant to be bare on the page.\n */\n frame?: \"card\" | \"none\";\n /** Column label treatment: sentence case, or uppercase with tracking. */\n headerType?: \"sentence\" | \"caps\";\n /** Header background. `none` for report tables that carry no band. */\n headerFill?: \"subtle\" | \"none\";\n /** Zebra-stripe alternate rows. */\n striped?: boolean;\n /**\n * A footer row, in a real `<tfoot>`.\n *\n * Called once per column; return the cell content, or null for a blank. A\n * real footer rather than one more row of data, so it is announced as one.\n */\n totalRow?: (column: Column<T, unknown>) => ReactNode;\n /**\n * The rows nested under a row, shown while it is expanded. Return nothing\n * (or an empty array) for a row with none. Sub-rows are rows like any other\n * -- the same columns, `getRowId`, `onRowAction` and selection -- and may\n * have sub-rows of their own.\n *\n * A row with sub-rows gets a disclosure button in its first cell, named by\n * that cell's content and carrying `aria-expanded`; every row's first cell\n * is indented one step per level. Sorting orders each level on its own, so\n * sub-rows stay under their parent, and `pageSize` counts top-level rows,\n * so a group is never split across pages.\n *\n * Pass a stable reference (a module-level function or `useCallback`) so the\n * row index is memoised; an inline arrow function recomputes it on every\n * render.\n */\n getSubRows?: (row: T) => readonly T[] | null | undefined;\n /** The expanded rows, CONTROLLED: their `getRowId` values. */\n expandedKeys?: Iterable<RowKey>;\n /** The rows expanded at the start, when uncontrolled. Nothing is, by default. */\n defaultExpandedKeys?: Iterable<RowKey>;\n /** Fires with the new set of expanded rows, controlled or not. */\n onExpandedChange?: (keys: Set<RowKey>) => void;\n /**\n * Marks a row as a group header: its first column's content spans the\n * whole row, in the header's type, and its other cells are not rendered.\n * For grouped tables (\"Mobile\" / \"Desktop\") whose groups are headed rather\n * than summed; a group with totals is a row with `getSubRows` instead.\n * Combine the two for a group that opens and closes.\n *\n * A group header only heads the rows under it, so it is not actionable:\n * `onRowAction` does not fire for it and it shows no pointer cursor.\n */\n isGroupRow?: (row: T) => boolean;\n /**\n * Row height and type size. `default` takes its padding from the ambient\n * density scope (`--density-row-pad-y`) in body type; `compact` is the\n * product's tight report table, `py-2` rows in small type.\n */\n density?: \"default\" | \"compact\";\n /**\n * Hides the header row from sight and keeps it for assistive tech, for a\n * key/value table whose first column already says what each row is\n * (\"Plan\" | \"Growth\"). Every column still needs a `header`: it is the\n * column's accessible name, announced as a reader moves across a row.\n *\n * The header cells keep their widths, so `Column.width` still lays the\n * columns out. A hidden header has no sort control, so `sortable` is\n * ignored here (`defaultSortDescriptor` still orders the rows). In the grid\n * a header cell opens up while it holds keyboard focus; with\n * `keyboardModel=\"table\"` nothing in it is focusable.\n */\n headerHidden?: boolean;\n /**\n * How the keyboard moves through the table.\n *\n * `grid` (the default) is React Aria's grid: a composite widget with a\n * tab stop of its own, where the arrow keys move focus between rows and\n * cells and a screen reader switches to its application/grid handling.\n * Row selection and `onRowAction` need it, because the row itself is the\n * control.\n *\n * `table` renders a plain `role=\"table\"`: no row or cell takes focus, the\n * arrow keys are left to the browser and the screen reader's own table\n * commands, and Tab goes straight from one link or button in a cell to the\n * next, in reading order. For tables whose cells hold the actions. A\n * sortable header becomes a button in that Tab order, with `aria-sort` on\n * its column header. A row cannot be a control here, so `selectionMode`\n * and `onRowAction` are ignored: put a link or button in a cell instead.\n */\n keyboardModel?: \"grid\" | \"table\";\n /** Extra classes for the table. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\n/** 1 when the value grew, -1 when it shrank: the way a changed number swaps. Updated during render. */\nfunction useChangeDirection(value: number) {\n const [seen, setSeen] = useState({ value, dir: 1 });\n if (seen.value !== value) setSeen({ value, dir: value < seen.value ? -1 : 1 });\n return seen.dir;\n}\n\n/**\n * The bar under a selectable table. With nothing chosen it counts the rows\n * (\"12 rows\"); with some chosen it counts them (\"3 selected\") and offers the\n * way back. The number and the word swap in place as they change.\n */\nfunction SelectionBar({\n count,\n total,\n labels,\n onClear,\n}: {\n count: number;\n total: number;\n labels: {\n item: { one: string; other: string };\n selected: string;\n clear: string;\n };\n onClear: () => void;\n}) {\n const shown = count || total;\n const noun = count ? labels.selected : shown === 1 ? labels.item.one : labels.item.other;\n const direction = useChangeDirection(shown);\n return (\n <div\n data-slot=\"table-selection-bar\"\n data-active={count > 0 || undefined}\n className={cnAll(tableSelectionBarVariants())}\n >\n <span data-slot=\"table-selection-count\" className=\"inline-flex items-baseline gap-1\">\n <SwapText swapKey={String(shown)} dir={direction} className=\"inline-block tabular-nums\">\n {shown}\n </SwapText>{\" \"}\n <SwapText swapKey={noun} dir={count ? 1 : -1} className=\"inline-block\">\n {noun}\n </SwapText>\n </span>\n {count > 0 ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n data-slot=\"table-selection-clear\"\n className=\"motion-safe:animate-fade-in\"\n onPress={onClear}\n >\n {labels.clear}\n </Button>\n ) : null}\n </div>\n );\n}\n\n/** Builds columns that keep each column's own value type. */\nexport function createColumns<T>() {\n return {\n column: <V,>(definition: Column<T, V>): Column<T, V> => definition,\n };\n}\n\n/**\n * A data table.\n *\n * Maturity: **beta**. Filtering is deliberately absent -- it had one consumer\n * or none across the six legacy tables, and it belongs to DataGrid. Rows can\n * open onto sub-rows (`getSubRows`) and head a group (`isGroupRow`), which the\n * product's usage and page-speed reports needed. Sorting glides: when a sort reorders the\n * rows, each slides to its new place instead of jumping (the lib/flip helper).\n * Selection is opt-in with `selectionMode=\"multiple\"`.\n *\n * @example\n * const { column } = createColumns<Channel>();\n * const columns = useMemo(() => [\n * column({ id: \"name\", header: \"Channel\", accessor: (r) => r.name }),\n * column({ id: \"cost\", header: \"Cost\", accessor: (r) => r.cost,\n * format: (v) => currency(v), align: \"end\", sortable: true }),\n * ], []);\n *\n * <Table aria-label=\"Channels\" data={rows} columns={columns} getRowId={(r) => r.id} />\n */\n/**\n * The orderable value behind a cell.\n *\n * Objects are NOT stringified. `String(someObject)` is \"[object Object]\", so a\n * column of objects would sort every row equal while looking like it worked --\n * and a column's own `format` already exists to turn its value into a string,\n * so that is what gets used instead of a blind cast.\n *\n * Numbers stay numbers even when the column formats them: sorting \"$1,220\"\n * against \"$12,480\" lexically puts the larger first.\n */\nfunction sortKey<T>(column: Column<T, unknown>, row: T): string | number {\n const value = column.accessor(row);\n if (typeof value === \"number\" || typeof value === \"string\") return value;\n if (typeof value === \"boolean\") return value ? 1 : 0;\n if (value instanceof Date) return value.getTime();\n if (value == null) return \"\";\n return column.format?.(value, row) ?? \"\";\n}\n\n/** Orders two cell keys. Numbers numerically, strings by locale. */\nfunction compareKeys(a: string | number, b: string | number): number {\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n // localeCompare so \"Item 10\" sorts after \"Item 9\", not before it.\n return String(a).localeCompare(String(b));\n}\n\n/** `level` in the order `sort` asks for; unchanged when there is none or its column is gone. */\nfunction sortRows<T>(\n level: readonly T[],\n columns: readonly Column<T, unknown>[],\n sort: SortDescriptor | undefined,\n): readonly T[] {\n if (!sort) return level;\n const sorted = columns.find((column) => column.id === sort.column);\n if (!sorted) return level;\n\n const direction = sort.direction === \"descending\" ? -1 : 1;\n return [...level].sort((a, b) => direction * compareKeys(sortKey(sorted, a), sortKey(sorted, b)));\n}\n\nexport const Table = forwardRef(\n function Table<T>(\n {\n data,\n columns,\n getRowId,\n variant,\n sortDescriptor,\n defaultSortDescriptor,\n onSortChange,\n onRowAction,\n isRowHighlighted,\n rowClassName,\n selectionMode,\n selectedKeys,\n defaultSelectedKeys,\n onSelectionChange,\n selectionLabels,\n frame,\n headerType,\n headerFill,\n striped,\n totalRow,\n empty,\n minWidth,\n stickyFirstColumn,\n pageSize,\n pageSizeOptions,\n onPageSizeChange,\n pageLabels,\n getSubRows,\n expandedKeys,\n defaultExpandedKeys,\n onExpandedChange,\n isGroupRow,\n density,\n headerHidden,\n keyboardModel,\n className,\n ...props\n }: TableProps<T>,\n ref: ForwardedRef<HTMLTableElement>,\n ) {\n /*\n * Uncontrolled sorting is the component's job, not the call site's.\n *\n * React Aria holds no sort state and never reorders rows -- it reports the\n * descriptor and stops. Leaving it there would mean `sortable: true` renders\n * a header that highlights, announces itself via aria-sort, and sorts\n * nothing until the consumer writes the state and the comparator by hand.\n * That is a demo that only works in a story.\n *\n * So: uncontrolled, the table holds the descriptor and sorts `data`.\n * Controlled (a `sortDescriptor` prop), it sorts nothing and the consumer\n * owns the order, which is what server-side sorting requires.\n */\n const isControlled = sortDescriptor !== undefined;\n const [uncontrolledSort, setUncontrolledSort] = useState(defaultSortDescriptor);\n const activeSort = isControlled ? sortDescriptor : uncontrolledSort;\n\n // One level at a time: sub-rows are sorted among themselves, under their parent.\n const sortLevel = (level: readonly T[]) =>\n isControlled ? level : sortRows(level, columns, activeSort);\n const rows = useMemo(\n () => (isControlled ? data : sortRows(data, columns, activeSort)),\n [data, columns, activeSort, isControlled],\n );\n\n /*\n * Paging happens AFTER sorting, which is the whole reason it lives in the\n * component. Slice first and a sort would only reshuffle the ten rows you\n * can see, so \"sort by cost ascending\" would not find the cheapest row in\n * the set -- the single most common way a hand-rolled table gets this wrong.\n */\n // A size the reader picked outranks the prop, and is held here rather than\n // synced from it by an effect -- an effect would snap them back to the\n // consumer's default on every unrelated re-render.\n const [chosenSize, setChosenSize] = useState<number>();\n const rowsPerPage = chosenSize ?? pageSize;\n\n const pageCount = rowsPerPage ? Math.max(1, Math.ceil(rows.length / rowsPerPage)) : 1;\n const [page, setPage] = useState(1);\n // Clamped rather than reset by an effect, so a shrinking `data` cannot strand\n // the view on a page that no longer exists.\n const currentPage = Math.min(page, pageCount);\n const pageStart = rowsPerPage ? (currentPage - 1) * rowsPerPage : 0;\n const visibleRows = rowsPerPage ? rows.slice(pageStart, pageStart + rowsPerPage) : rows;\n\n const labels = {\n previous: pageLabels?.previous ?? \"Go to previous page\",\n next: pageLabels?.next ?? \"Go to next page\",\n page:\n pageLabels?.page ??\n ((target: number, isCurrent: boolean) =>\n isCurrent ? `Page ${target}, current page` : `Go to page ${target}`),\n perPage: pageLabels?.perPage ?? ((size: number) => `${size} per page`),\n rowsPerPage: pageLabels?.rowsPerPage ?? \"Rows per page\",\n };\n\n /*\n * Expansion. The table holds the open set when uncontrolled; the page's rows\n * are then walked depth-first, each open row followed by its own (sorted)\n * sub-rows, which is the order they are drawn in.\n */\n const [uncontrolledExpanded, setUncontrolledExpanded] = useState(\n () => new Set<RowKey>(defaultExpandedKeys ?? []),\n );\n const expanded = expandedKeys === undefined ? uncontrolledExpanded : new Set(expandedKeys);\n const toggleExpanded = (key: RowKey) => {\n const next = new Set(expanded);\n if (next.has(key)) next.delete(key);\n else next.add(key);\n if (expandedKeys === undefined) setUncontrolledExpanded(next);\n onExpandedChange?.(next);\n };\n const expandable = getSubRows !== undefined;\n /*\n * Every row at every depth, open or not, so an action or a selection on a\n * sub-row resolves; and each row's sub-rows, so `getSubRows` runs once per\n * row per `data` rather than on every render.\n */\n const { rowsByKey, subRowsByRow } = useMemo(() => {\n const byKey = new Map<RowKey, T>();\n const subRows = new Map<T, readonly T[]>();\n const index = (level: readonly T[]) => {\n for (const row of level) {\n byKey.set(getRowId(row), row);\n const children = getSubRows?.(row) ?? [];\n subRows.set(row, children);\n index(children);\n }\n };\n index(data);\n return { rowsByKey: byKey, subRowsByRow: subRows };\n }, [data, getSubRows, getRowId]);\n const subRowsOf = (row: T): readonly T[] => subRowsByRow.get(row) ?? [];\n\n type ShownRow = {\n row: T;\n depth: number;\n // Position among its siblings, which is what `Column.cell` receives as `index`.\n index: number;\n hasSubRows: boolean;\n isExpanded: boolean;\n };\n const shownRows: ShownRow[] = [];\n const walk = (level: readonly T[], depth: number) => {\n for (const [index, row] of level.entries()) {\n const subRows = subRowsOf(row);\n const isExpanded = subRows.length > 0 && expanded.has(getRowId(row));\n shownRows.push({ row, depth, index, hasSubRows: subRows.length > 0, isExpanded });\n if (isExpanded) walk(sortLevel(subRows), depth + 1);\n }\n };\n walk(visibleRows, 0);\n\n const idBase = useId();\n\n /*\n * Selection is React Aria's: it owns the keyboard, the modifier clicks and the\n * announcements. The table only holds the set when uncontrolled, so the bar\n * below can count it, and renders the checkbox column.\n */\n // A plain table has no focusable rows, so nothing a row-level control needs.\n const plain = keyboardModel === \"table\";\n const selectable = selectionMode === \"multiple\" && !plain;\n const rowAction = plain ? undefined : onRowAction;\n const [uncontrolledSelection, setUncontrolledSelection] = useState<Selection>(\n defaultSelectedKeys ?? new Set(),\n );\n const selection = selectedKeys ?? uncontrolledSelection;\n const commitSelection = (keys: Selection) => {\n if (selectedKeys === undefined) setUncontrolledSelection(keys);\n onSelectionChange?.(keys);\n };\n const selectedCount =\n selection === \"all\"\n ? shownRows.length\n : [...selection].filter((key) => rowsByKey.has(key)).length;\n const selectLabels = {\n item: selectionLabels?.item ?? { one: \"row\", other: \"rows\" },\n selected: selectionLabels?.selected ?? \"selected\",\n clear: selectionLabels?.clear ?? \"Clear selection\",\n };\n // A cell is \"sorted\" while its column is the one the rows are ordered by.\n const sortedColumn = activeSort?.column;\n const hasWidths = columns.some((column) => column.width !== undefined);\n\n const tableStyle: CSSProperties = {};\n if (minWidth !== undefined) tableStyle.minWidth = minWidth;\n // Declared widths only hold under a fixed layout; otherwise the browser\n // treats them as hints and the columns drift, which is why 5 of the 6 legacy\n // consumers set this by hand.\n if (hasWidths) tableStyle.tableLayout = \"fixed\";\n\n const scrollRef = useRef<HTMLDivElement>(null);\n const edges = useScrollEdges(scrollRef);\n /*\n * Rows glide to their new places when the order changes.\n *\n * The key is the order the rows have in the DOM, not the order of `data`.\n * React Aria builds its collection in a pass of its own, so when a sort\n * reorders `data` the rows are still in their old places for this render and\n * only move in a later one; looking at the data order would measure nothing\n * moving. A mutation observer sees the move the moment it happens and\n * re-renders synchronously, before the browser paints, so the rows are held\n * where they were and released on the transition with no flash of the new\n * order. Positions are read from the card's own corner, so scrolling the page\n * between two sorts is not mistaken for the rows moving.\n */\n const [rowOrder, setRowOrder] = useState(\"\");\n useEffect(() => {\n const root = scrollRef.current;\n if (!root) return;\n let last = \"\";\n const look = () => {\n const next = Array.from(root.querySelectorAll<HTMLElement>(\"tbody > [data-flip]\"))\n .map((row) => row.dataset.flip)\n .join(\"\\u0000\");\n if (next === last) return;\n last = next;\n flushSync(() => setRowOrder(next));\n };\n look();\n const observer = new MutationObserver(look);\n observer.observe(root, { childList: true, subtree: true });\n return () => observer.disconnect();\n }, []);\n useFlip(scrollRef, rowOrder, \"var(--duration-smooth)\", \"var(--ease-smooth)\", true);\n const stickyCell = (part: \"header\" | \"bare\" | \"body\") =>\n tableStickyCellVariants({ part, striped: part === \"body\" && !!striped });\n const headerPart = headerFill === \"none\" ? \"bare\" : \"header\";\n const alignOf = (column: Column<T, unknown>) =>\n column.align ?? (column.numeric ? \"end\" : undefined);\n // The first data column, pushed past the checkbox column when it is sticky.\n const stickyFirst = (column: Column<T, unknown>, part: \"header\" | \"bare\" | \"body\") => [\n stickyFirstColumn && column === columns[0] && stickyCell(part),\n stickyFirstColumn && selectable && column === columns[0] && \"start-11\",\n ];\n const columnClass = (column: Column<T, unknown>) =>\n cnAll(\n tableColumnVariants({ align: alignOf(column), headerType, density, hidden: headerHidden }),\n stickyFirst(column, headerPart),\n );\n const cellClass = (column: Column<T, unknown>, part: \"bare\" | \"body\") =>\n cnAll(\n tableCellVariants({ align: alignOf(column), numeric: column.numeric, density }),\n stickyFirst(column, part),\n );\n // A group header only heads the rows under it: it is not a row to act on, so no pointer either.\n const rowClass = (row: T, isGroup: boolean) =>\n cn(\n cnAll(\n tableRowVariants({ variant, striped }),\n isGroup && tableGroupRowVariants(),\n rowAction && !isGroup && \"cursor-pointer\",\n // No header rule above the first row, so it draws no edge of its own.\n headerHidden && \"first:border-t-0\",\n ),\n rowClassName?.(row),\n );\n const isSortable = (column: Column<T, unknown>) => !headerHidden && !!column.sortable;\n // The label, or the label kept for assistive tech while the header is hidden.\n const headerLabel = (column: Column<T, unknown>) => {\n const label = (\n <>\n {column.header}\n {/*\n * A sortable column says so BEFORE it is sorted. Rendering the\n * caret only once sorted left a sortable header looking exactly\n * like a fixed one, so the only way to discover sorting was to\n * click a header and see whether anything moved -- the same gap\n * the product closed in its own header, and the reason the\n * paired chevron exists rather than an arrow.\n */}\n {isSortable(column) ? (\n <SortCaretGlyph\n direction={activeSort?.column === column.id ? activeSort.direction : \"none\"}\n />\n ) : null}\n </>\n );\n return headerHidden ? (\n <span\n data-slot=\"table-column-label\"\n className=\"sr-only group-data-[focus-visible]/column:not-sr-only\"\n >\n {label}\n </span>\n ) : (\n label\n );\n };\n const changeSort = (descriptor: SortDescriptor) => {\n if (!isControlled) setUncontrolledSort(descriptor);\n // Back to the first page: the order changed under you, so staying on\n // page 3 shows rows you did not ask to see.\n setPage(1);\n onSortChange?.(descriptor);\n };\n const cellContent = (column: Column<T, unknown>, row: T, index: number) => {\n const value: unknown = column.accessor(row);\n return column.cell ? column.cell({ value, row, index }) : renderValue(column, value, row);\n };\n /*\n * The first cell of a table with `getSubRows`: the disclosure button (or a\n * spacer holding its place) beside the content, indented one step per level.\n * Shared by the grid and the plain table.\n */\n const indentClass = (column: Column<T, unknown>) =>\n // One button-width (and its gap) further in per level.\n expandable &&\n column === columns[0] &&\n \"ps-[calc(var(--density-row-pad-x)+var(--table-row-depth)*--spacing(7))]\";\n const depthStyle = (column: Column<T, unknown>, depth: number) =>\n expandable && column === columns[0]\n ? ({ \"--table-row-depth\": depth } as CSSProperties)\n : undefined;\n const firstCellContent = (\n column: Column<T, unknown>,\n content: ReactNode,\n { row, hasSubRows, isExpanded }: ShownRow,\n labelId: string,\n ) =>\n expandable && column === columns[0] ? (\n <span className=\"flex items-center gap-1\">\n {hasSubRows ? (\n <AriaButton\n data-slot=\"table-row-expand\"\n aria-expanded={isExpanded}\n aria-labelledby={labelId}\n className={cnAll(tableExpandButtonVariants())}\n onPress={() => toggleExpanded(getRowId(row))}\n >\n <ExpandChevronGlyph />\n </AriaButton>\n ) : (\n // Holds the button's place, so labels line up level by level.\n <span aria-hidden=\"true\" className=\"size-6 shrink-0\" />\n )}\n <span id={labelId} data-slot=\"table-row-label\" className=\"min-w-0\">\n {content}\n </span>\n </span>\n ) : (\n content\n );\n const columnStyle = (column: Column<T, unknown>) =>\n column.width === undefined ? undefined : { width: column.width };\n const emptyState =\n empty === undefined ? undefined : (\n <div data-slot=\"table-empty\" className={cnAll(tableEmptyVariants())}>\n {empty}\n </div>\n );\n\n return (\n <div\n data-slot=\"table-card\"\n data-overflow-start={edges.start || undefined}\n data-overflow-end={edges.end || undefined}\n data-sticky-first={stickyFirstColumn || undefined}\n className={cnAll(tableCardVariants({ frame }))}\n >\n <div ref={scrollRef} data-slot=\"table-scroll\" className={cnAll(tableScrollVariants())}>\n {plain ? (\n /*\n * The plain table is real table markup with nothing of React Aria's\n * keyboard model on it: role=\"table\", headers by `scope`, and the\n * browser's own Tab order through whatever the cells hold. Same\n * classes, slots and data attributes as the grid, so it looks the same.\n */\n <table\n ref={ref}\n {...props}\n data-slot=\"table\"\n data-keyboard-model=\"table\"\n className={cn(tableVariants({ density }), className)}\n style={tableStyle}\n >\n <thead\n data-slot=\"table-header\"\n className={cnAll(tableHeaderVariants({ headerFill }))}\n >\n <tr>\n {columns.map((column) => {\n const sorted =\n activeSort?.column === column.id ? activeSort.direction : undefined;\n return (\n <th\n key={column.id}\n scope=\"col\"\n data-slot=\"table-column\"\n data-sorted={sortedColumn === column.id || undefined}\n aria-sort={isSortable(column) ? (sorted ?? \"none\") : undefined}\n className={columnClass(column)}\n style={columnStyle(column)}\n >\n {isSortable(column) ? (\n <button\n type=\"button\"\n data-slot=\"table-sort-button\"\n className={cnAll(tableSortButtonVariants())}\n onClick={() =>\n changeSort({\n column: column.id,\n direction: sorted === \"ascending\" ? \"descending\" : \"ascending\",\n })\n }\n >\n {headerLabel(column)}\n </button>\n ) : (\n headerLabel(column)\n )}\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody data-slot=\"table-body\">\n {shownRows.length === 0 && emptyState ? (\n <tr>\n <td colSpan={columns.length}>{emptyState}</td>\n </tr>\n ) : (\n shownRows.map((shown, position) => {\n const { row, depth, index } = shown;\n const key = getRowId(row);\n const isGroup = isGroupRow?.(row) ?? false;\n // A group header's first cell spans every column.\n const shownColumns = isGroup ? columns.slice(0, 1) : columns;\n const labelId = `${idBase}-row-${String(position)}`;\n return (\n <tr\n key={String(key)}\n data-slot=\"table-row\"\n data-flip={String(key)}\n data-highlighted={isRowHighlighted?.(row) || undefined}\n data-depth={expandable ? depth : undefined}\n data-open={shown.isExpanded || undefined}\n data-group={isGroup || undefined}\n className={rowClass(row, isGroup)}\n >\n {shownColumns.map((column) => {\n const content = firstCellContent(\n column,\n cellContent(column, row, index),\n shown,\n labelId,\n );\n const cellProps = {\n \"data-slot\": \"table-cell\",\n \"data-sorted\": sortedColumn === column.id || undefined,\n colSpan: isGroup ? columns.length : undefined,\n style: depthStyle(column, depth),\n };\n // The first column names the row, as the grid's rowheader does.\n return column === columns[0] ? (\n <th\n key={column.id}\n scope=\"row\"\n {...cellProps}\n className={cnAll(\n cellClass(column, \"body\"),\n indentClass(column),\n \"font-normal\",\n )}\n >\n {content}\n </th>\n ) : (\n <td\n key={column.id}\n {...cellProps}\n className={cellClass(column, \"body\")}\n >\n {content}\n </td>\n );\n })}\n </tr>\n );\n })\n )}\n </tbody>\n {totalRow ? (\n <tfoot data-slot=\"table-footer\" className={cnAll(tableFooterVariants())}>\n <tr>\n {columns.map((column) => (\n <td\n key={column.id}\n data-slot=\"table-cell\"\n className={cellClass(column, \"bare\")}\n >\n {totalRow(column)}\n </td>\n ))}\n </tr>\n </tfoot>\n ) : null}\n </table>\n ) : (\n <AriaTable\n // The ref goes on the table itself, never on the table-card or\n // table-scroll divs this component wraps it in -- callers need the\n // real HTMLTableElement (to measure it, scroll it into view, etc.),\n // not one of the two divs this component renders around it.\n ref={ref}\n {...props}\n data-slot=\"table\"\n className={cn(tableVariants({ density }), className)}\n style={tableStyle}\n selectionMode={selectable ? \"multiple\" : undefined}\n selectedKeys={selectable ? selection : undefined}\n onSelectionChange={selectable ? commitSelection : undefined}\n sortDescriptor={activeSort}\n onSortChange={changeSort}\n onRowAction={\n rowAction\n ? (key) => {\n const row = rowsByKey.get(key);\n // A group header heads its rows; it is not one to act on.\n if (row !== undefined && !isGroupRow?.(row)) rowAction(row);\n }\n : undefined\n }\n >\n <AriaTableHeader\n data-slot=\"table-header\"\n className={cnAll(tableHeaderVariants({ headerFill }))}\n >\n {selectable ? (\n <AriaColumn\n id={SELECT_COLUMN_ID}\n data-slot=\"table-column\"\n className={cnAll(\n tableColumnVariants({ headerType, density, hidden: headerHidden }),\n SELECT_CELL,\n stickyFirstColumn && stickyCell(headerPart),\n stickyFirstColumn && \"border-e-0\",\n )}\n >\n {/* The header box selects every row, and shows a dash while only some are chosen. */}\n {/* A hidden header keeps the box for assistive tech and shows it only while the cell holds focus. */}\n <span\n className={\n headerHidden\n ? \"sr-only group-data-[focus-visible]/column:not-sr-only\"\n : undefined\n }\n >\n <Checkbox slot=\"selection\" aria-label={selectionLabels?.selectAll} />\n </span>\n </AriaColumn>\n ) : null}\n {columns.map((column) => (\n <AriaColumn\n key={column.id}\n id={column.id}\n isRowHeader={column === columns[0]}\n allowsSorting={isSortable(column)}\n data-slot=\"table-column\"\n data-sorted={sortedColumn === column.id || undefined}\n className={columnClass(column)}\n style={columnStyle(column)}\n >\n {headerLabel(column)}\n </AriaColumn>\n ))}\n {rowAction ? (\n /*\n * The chevron's own column. Its header is intentionally EMPTY: a\n * visually-hidden label would be user-visible copy, and section 7\n * rule 9 has nowhere to put it until useMessages() exists.\n */\n <AriaColumn\n id={CHEVRON_COLUMN_ID}\n data-slot=\"table-column\"\n className={cnAll(\n tableColumnVariants({ headerType, density, hidden: headerHidden }),\n CHEVRON_CELL,\n )}\n >\n {\"\"}\n </AriaColumn>\n ) : null}\n </AriaTableHeader>\n <AriaTableBody\n data-slot=\"table-body\"\n renderEmptyState={emptyState === undefined ? undefined : () => emptyState}\n >\n {shownRows.map((shown, position) => {\n const { row, depth, index } = shown;\n const key = getRowId(row);\n const isGroup = isGroupRow?.(row) ?? false;\n // A group header's first cell spans the data columns and the chevron's.\n const shownColumns = isGroup ? columns.slice(0, 1) : columns;\n const labelId = `${idBase}-row-${String(position)}`;\n return (\n <AriaRow\n key={String(key)}\n id={key}\n data-slot=\"table-row\"\n // The rows useFlip glides when a sort reorders them (the value is the key the order is read from).\n data-flip={String(key)}\n data-highlighted={isRowHighlighted?.(row) || undefined}\n data-depth={expandable ? depth : undefined}\n // Not data-expanded: React Aria writes that one on every row itself.\n data-open={shown.isExpanded || undefined}\n data-group={isGroup || undefined}\n className={rowClass(row, isGroup)}\n >\n {selectable ? (\n <AriaCell\n data-slot=\"table-cell\"\n className={cnAll(\n tableCellVariants({ density }),\n SELECT_CELL,\n stickyFirstColumn && stickyCell(\"body\"),\n stickyFirstColumn && \"border-e-0\",\n )}\n >\n <Checkbox slot=\"selection\" aria-label={selectionLabels?.selectRow} />\n </AriaCell>\n ) : null}\n {shownColumns.map((column) => (\n <AriaCell\n key={column.id}\n data-slot=\"table-cell\"\n data-sorted={sortedColumn === column.id || undefined}\n colSpan={isGroup ? columns.length + (rowAction ? 1 : 0) : undefined}\n className={cnAll(cellClass(column, \"body\"), indentClass(column))}\n style={depthStyle(column, depth)}\n >\n {firstCellContent(\n column,\n cellContent(column, row, index),\n shown,\n labelId,\n )}\n </AriaCell>\n ))}\n {rowAction && !isGroup ? (\n <AriaCell\n data-slot=\"table-cell\"\n className={cnAll(tableCellVariants({ density }), CHEVRON_CELL)}\n >\n <RowChevronGlyph />\n </AriaCell>\n ) : null}\n </AriaRow>\n );\n })}\n </AriaTableBody>\n {totalRow ? (\n <AriaTableFooter data-slot=\"table-footer\" className={cnAll(tableFooterVariants())}>\n <AriaRow id=\"__table_total\">\n {selectable ? (\n <AriaCell\n data-slot=\"table-cell\"\n className={cnAll(\n tableCellVariants({ density }),\n SELECT_CELL,\n stickyFirstColumn && stickyCell(\"bare\"),\n stickyFirstColumn && \"border-e-0\",\n )}\n >\n {\"\"}\n </AriaCell>\n ) : null}\n {columns.map((col) => (\n <AriaCell\n key={col.id}\n data-slot=\"table-cell\"\n className={cellClass(col, \"bare\")}\n >\n {totalRow(col)}\n </AriaCell>\n ))}\n {rowAction ? (\n <AriaCell\n data-slot=\"table-cell\"\n className={cnAll(tableCellVariants({ density }), CHEVRON_CELL)}\n >\n {\"\"}\n </AriaCell>\n ) : null}\n </AriaRow>\n </AriaTableFooter>\n ) : null}\n </AriaTable>\n )}\n </div>\n {selectable ? (\n <SelectionBar\n count={selectedCount}\n total={data.length}\n labels={selectLabels}\n onClear={() => commitSelection(new Set())}\n />\n ) : null}\n {rowsPerPage === undefined || (pageCount <= 1 && !pageSizeOptions) ? null : (\n <nav\n data-slot=\"table-pagination\"\n aria-label={`${props[\"aria-label\"]} pagination`}\n className={cnAll(tablePaginationVariants())}\n >\n {/* Present even when empty, so the select stays on the trailing edge. */}\n <div data-slot=\"table-pagination-pages\" className=\"flex items-center gap-1\">\n {pageCount <= 1 ? null : (\n <>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-(--density-control-h-sm) px-0\"\n aria-label={labels.previous}\n isDisabled={currentPage <= 1}\n onPress={() => setPage(currentPage - 1)}\n >\n <PageChevronGlyph direction=\"previous\" />\n </Button>\n {pageWindow(currentPage, pageCount).map((entry, index) =>\n entry === \"ellipsis\" ? (\n <span\n // The gap is decoration; the pages either side of it are\n // what a reader navigates by, so this is not announced.\n key={`ellipsis-${String(index)}`}\n data-slot=\"table-pagination-ellipsis\"\n aria-hidden=\"true\"\n className=\"px-2 text-sm text-muted-foreground\"\n >\n &hellip;\n </span>\n ) : (\n <Button\n key={entry}\n variant={entry === currentPage ? \"primary\" : \"ghost\"}\n size=\"sm\"\n className=\"min-w-(--density-control-h-sm) px-2\"\n aria-label={labels.page(entry, entry === currentPage)}\n onPress={() => setPage(entry)}\n >\n {entry}\n </Button>\n ),\n )}\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-(--density-control-h-sm) px-0\"\n aria-label={labels.next}\n isDisabled={currentPage >= pageCount}\n onPress={() => setPage(currentPage + 1)}\n >\n <PageChevronGlyph direction=\"next\" />\n </Button>\n </>\n )}\n </div>\n {pageSizeOptions === undefined ? null : (\n <Select\n // Select is w-full by default, which in this flex row stretches\n // it across the whole bar and drags it back to the leading edge\n // the moment the page numbers retire.\n className=\"w-auto\"\n label={labels.rowsPerPage}\n labelHidden\n selectedKey={String(rowsPerPage)}\n onSelectionChange={(key) => {\n const size = Number(key);\n setChosenSize(size);\n // Page 4 of 18 is page 1 of 2 at a larger size; keeping the\n // number would land the reader past the end of the data.\n setPage(1);\n onPageSizeChange?.(size);\n }}\n >\n {pageSizeOptions.map((option) => (\n <SelectItem key={option} id={String(option)}>\n {labels.perPage(option)}\n </SelectItem>\n ))}\n </Select>\n )}\n </nav>\n )}\n </div>\n );\n },\n // forwardRef cannot carry a type parameter through a .tsx call expression, so\n // Table<T>'s generic signature is restored here with a type-only cast on the\n // export; it costs nothing at runtime and the inner function is still named\n // \"Table\" for DevTools.\n) as <T>(props: TableProps<T> & RefAttributes<HTMLTableElement>) => ReactElement | null;\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { ReactNode } from "react";
1
2
  //#region src/text-shimmer/text-shimmer.d.ts
2
3
  /**
3
4
  * TextShimmer: a calm light sweep across a short status line, such as
@@ -10,10 +11,25 @@
10
11
  * over a short transition. A changed label rises in place. Under reduced motion
11
12
  * nothing moves and the text stays in its muted base colour. The component sets
12
13
  * `aria-busy`; announce completion from your own live region.
14
+ *
15
+ * The label can carry inline markup, such as a `<strong>` name or a count: the
16
+ * sweep is clipped to all the text inside it. Keep that markup to plain inline
17
+ * text (no icons, backgrounds, transforms or positioning, which the text clip
18
+ * does not reach). Give non-string children a `swapKey` so a changed label still
19
+ * rises in place.
13
20
  */
14
21
  export interface TextShimmerProps {
15
- /** The status text. Keep it to one short line. */
16
- children: string;
22
+ /**
23
+ * The status text. Keep it to one short line. A string, or inline text with
24
+ * markup such as `<strong>` or a count; the sweep paints over all of it.
25
+ */
26
+ children: ReactNode;
27
+ /**
28
+ * Identifies the label, so a changed label rises in place. A string or number
29
+ * child is its own key. Other children have no key unless you pass one, and
30
+ * then update in place without the rise. An empty string counts as unset.
31
+ */
32
+ swapKey?: string;
17
33
  /** Sweep while work is ongoing. @default true */
18
34
  active?: boolean;
19
35
  /** Seconds for one pass of the band across the text (the pause between passes comes on top). @default 1.8 */
@@ -4,9 +4,12 @@ import { SwapText } from "../lib/swap-text.js";
4
4
  import { createElement, forwardRef } from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  //#region src/text-shimmer/text-shimmer.tsx
7
+ /** The key for children that bring none of their own: it never changes, so they never swap. */
8
+ const STILL_KEY = "text-shimmer";
7
9
  /** The sweep takes this share of one cycle; the rest is the rest between passes. */
8
10
  const SWEEP_SHARE = .76;
9
- const TextShimmer = forwardRef(function TextShimmer({ children, active = true, duration = 1.8, as = "span", className, id }, ref) {
11
+ const TextShimmer = forwardRef(function TextShimmer({ children, swapKey, active = true, duration = 1.8, as = "span", className, id }, ref) {
12
+ const key = swapKey || (typeof children === "string" || typeof children === "number" ? String(children) : STILL_KEY);
10
13
  return createElement(as, {
11
14
  ref,
12
15
  id,
@@ -16,7 +19,7 @@ const TextShimmer = forwardRef(function TextShimmer({ children, active = true, d
16
19
  style: { "--text-shimmer-cycle": `${duration / SWEEP_SHARE}s` },
17
20
  className: cn("shimmer-text", className)
18
21
  }, /* @__PURE__ */ jsx(SwapText, {
19
- swapKey: children,
22
+ swapKey: key,
20
23
  children: /* @__PURE__ */ jsx("span", {
21
24
  className: "shimmer-text-label",
22
25
  children
@@ -1 +1 @@
1
- {"version":3,"file":"text-shimmer.js","names":[],"sources":["../../src/text-shimmer/text-shimmer.tsx"],"sourcesContent":["\"use client\";\n\nimport { createElement, forwardRef, type CSSProperties } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * TextShimmer: a calm light sweep across a short status line, such as\n * \"Thinking\" or \"Generating summary\", to show that work is ongoing.\n *\n * The band is the current text colour over a muted base, painted by CSS alone\n * (a gradient clipped to the text, its position driven by a registered custom\n * property), so nothing re-renders per frame. A pause of about a quarter of the\n * cycle follows each pass. When `active` turns false the text settles to solid\n * over a short transition. A changed label rises in place. Under reduced motion\n * nothing moves and the text stays in its muted base colour. The component sets\n * `aria-busy`; announce completion from your own live region.\n */\nexport interface TextShimmerProps {\n /** The status text. Keep it to one short line. */\n children: string;\n /** Sweep while work is ongoing. @default true */\n active?: boolean;\n /** Seconds for one pass of the band across the text (the pause between passes comes on top). @default 1.8 */\n duration?: number;\n /** The element to render. @default \"span\" */\n as?: \"span\" | \"p\" | \"div\" | \"h2\" | \"h3\" | \"h4\";\n /** Extra classes for the element. Merged last. */\n className?: string;\n id?: string;\n}\n\n/** The sweep takes this share of one cycle; the rest is the rest between passes. */\nconst SWEEP_SHARE = 0.76;\n\nexport const TextShimmer = forwardRef<HTMLElement, TextShimmerProps>(function TextShimmer(\n { children, active = true, duration = 1.8, as = \"span\", className, id },\n ref,\n) {\n return createElement(\n as,\n {\n ref,\n id,\n \"data-slot\": \"text-shimmer\",\n \"data-state\": active ? \"active\" : \"idle\",\n \"aria-busy\": active || undefined,\n style: { \"--text-shimmer-cycle\": `${duration / SWEEP_SHARE}s` } as CSSProperties,\n className: cn(\"shimmer-text\", className),\n },\n <SwapText swapKey={children}>\n <span className=\"shimmer-text-label\">{children}</span>\n </SwapText>,\n );\n});\n"],"mappings":";;;;;;;AAkCA,MAAM,cAAc;AAEpB,MAAa,cAAc,WAA0C,SAAS,YAC5E,EAAE,UAAU,SAAS,MAAM,WAAW,KAAK,KAAK,QAAQ,WAAW,MACnE,KACA;CACA,OAAO,cACL,IACA;EACE;EACA;EACA,aAAa;EACb,cAAc,SAAS,WAAW;EAClC,aAAa,UAAU,KAAA;EACvB,OAAO,EAAE,wBAAwB,GAAG,WAAW,YAAY,GAAG;EAC9D,WAAW,GAAG,gBAAgB,SAAS;CACzC,GACA,oBAAC,UAAD;EAAU,SAAS;EACjB,UAAA,oBAAC,QAAD;GAAM,WAAU;GAAsB;EAAe,CAAA;CAC7C,CAAA,CACZ;AACF,CAAC"}
1
+ {"version":3,"file":"text-shimmer.js","names":[],"sources":["../../src/text-shimmer/text-shimmer.tsx"],"sourcesContent":["\"use client\";\n\nimport { createElement, forwardRef, type CSSProperties, type ReactNode } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * TextShimmer: a calm light sweep across a short status line, such as\n * \"Thinking\" or \"Generating summary\", to show that work is ongoing.\n *\n * The band is the current text colour over a muted base, painted by CSS alone\n * (a gradient clipped to the text, its position driven by a registered custom\n * property), so nothing re-renders per frame. A pause of about a quarter of the\n * cycle follows each pass. When `active` turns false the text settles to solid\n * over a short transition. A changed label rises in place. Under reduced motion\n * nothing moves and the text stays in its muted base colour. The component sets\n * `aria-busy`; announce completion from your own live region.\n *\n * The label can carry inline markup, such as a `<strong>` name or a count: the\n * sweep is clipped to all the text inside it. Keep that markup to plain inline\n * text (no icons, backgrounds, transforms or positioning, which the text clip\n * does not reach). Give non-string children a `swapKey` so a changed label still\n * rises in place.\n */\nexport interface TextShimmerProps {\n /**\n * The status text. Keep it to one short line. A string, or inline text with\n * markup such as `<strong>` or a count; the sweep paints over all of it.\n */\n children: ReactNode;\n /**\n * Identifies the label, so a changed label rises in place. A string or number\n * child is its own key. Other children have no key unless you pass one, and\n * then update in place without the rise. An empty string counts as unset.\n */\n swapKey?: string;\n /** Sweep while work is ongoing. @default true */\n active?: boolean;\n /** Seconds for one pass of the band across the text (the pause between passes comes on top). @default 1.8 */\n duration?: number;\n /** The element to render. @default \"span\" */\n as?: \"span\" | \"p\" | \"div\" | \"h2\" | \"h3\" | \"h4\";\n /** Extra classes for the element. Merged last. */\n className?: string;\n id?: string;\n}\n\n/** The key for children that bring none of their own: it never changes, so they never swap. */\nconst STILL_KEY = \"text-shimmer\";\n\n/** The sweep takes this share of one cycle; the rest is the rest between passes. */\nconst SWEEP_SHARE = 0.76;\n\nexport const TextShimmer = forwardRef<HTMLElement, TextShimmerProps>(function TextShimmer(\n { children, swapKey, active = true, duration = 1.8, as = \"span\", className, id },\n ref,\n) {\n const key =\n swapKey ||\n (typeof children === \"string\" || typeof children === \"number\" ? String(children) : STILL_KEY);\n return createElement(\n as,\n {\n ref,\n id,\n \"data-slot\": \"text-shimmer\",\n \"data-state\": active ? \"active\" : \"idle\",\n \"aria-busy\": active || undefined,\n style: { \"--text-shimmer-cycle\": `${duration / SWEEP_SHARE}s` } as CSSProperties,\n className: cn(\"shimmer-text\", className),\n },\n <SwapText swapKey={key}>\n <span className=\"shimmer-text-label\">{children}</span>\n </SwapText>,\n );\n});\n"],"mappings":";;;;;;;AAiDA,MAAM,YAAY;;AAGlB,MAAM,cAAc;AAEpB,MAAa,cAAc,WAA0C,SAAS,YAC5E,EAAE,UAAU,SAAS,SAAS,MAAM,WAAW,KAAK,KAAK,QAAQ,WAAW,MAC5E,KACA;CACA,MAAM,MACJ,YACC,OAAO,aAAa,YAAY,OAAO,aAAa,WAAW,OAAO,QAAQ,IAAI;CACrF,OAAO,cACL,IACA;EACE;EACA;EACA,aAAa;EACb,cAAc,SAAS,WAAW;EAClC,aAAa,UAAU,KAAA;EACvB,OAAO,EAAE,wBAAwB,GAAG,WAAW,YAAY,GAAG;EAC9D,WAAW,GAAG,gBAAgB,SAAS;CACzC,GACA,oBAAC,UAAD;EAAU,SAAS;EACjB,UAAA,oBAAC,QAAD;GAAM,WAAU;GAAsB;EAAe,CAAA;CAC7C,CAAA,CACZ;AACF,CAAC"}
@@ -44,6 +44,13 @@ export interface TextareaProps extends Omit<TextAreaProps, "className" | "style"
44
44
  * `undefined` value closes the row.
45
45
  */
46
46
  error?: string;
47
+ /**
48
+ * The field must be filled: the textarea is `required` (announced by screen
49
+ * readers, and checked by native form validation) and the label shows the
50
+ * required marker. The same prop as PasswordField's.
51
+ * @default false
52
+ */
53
+ isRequired?: boolean;
47
54
  }
48
55
  export declare const Textarea: import("react").ForwardRefExoticComponent<TextareaProps & import("react").RefAttributes<HTMLTextAreaElement>>;
49
56
  //#endregion
@@ -27,7 +27,7 @@ const textareaVariants = cva([
27
27
  "leading-(--leading-normal)"
28
28
  ]);
29
29
  const Textarea = forwardRef(function Textarea(allProps, ref) {
30
- const { className, label, description, error, ...props } = allProps;
30
+ const { className, label, description, error, isRequired, ...props } = allProps;
31
31
  const field = useFieldWiring({
32
32
  enabled: hasFieldProps(allProps),
33
33
  id: props.id,
@@ -39,6 +39,7 @@ const Textarea = forwardRef(function Textarea(allProps, ref) {
39
39
  const textarea = /* @__PURE__ */ jsx(TextArea, {
40
40
  ref,
41
41
  ...props,
42
+ ...isRequired ? { required: true } : {},
42
43
  ...field.controlProps,
43
44
  "data-slot": "textarea",
44
45
  className: cn(textareaVariants(), className)
@@ -51,6 +52,7 @@ const Textarea = forwardRef(function Textarea(allProps, ref) {
51
52
  hintId: field.hintId,
52
53
  error,
53
54
  errorId: field.errorId,
55
+ required: isRequired,
54
56
  wordExit: "quick",
55
57
  children: textarea
56
58
  });
@@ -1 +1 @@
1
- {"version":3,"file":"textarea.js","names":["AriaTextArea"],"sources":["../../src/textarea/textarea.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n TextArea as AriaTextArea,\n type TextAreaProps as AriaTextAreaProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { FieldShell, hasFieldProps, useFieldWiring } from \"../lib/field-shell.js\";\nimport { fieldSurfaceVariants } from \"../lib/field-surface.js\";\n\n/**\n * Textarea, ported from the legacy component onto React Aria's TextArea.\n *\n * Census: 3 files, 3 uses -- `value`, `onChange`, `placeholder`, `id`,\n * `ref`. All native props, all still native here.\n *\n * The surface comes from `fieldSurfaceVariants`, shared with Input. The two\n * legacy files styled it separately and had already drifted -- different\n * background, different focus ring, invalid styling on one and not the\n * other -- and none of those differences was a decision anyone made.\n */\nexport const textareaVariants = cva([\n fieldSurfaceVariants(),\n\n // min-h-20 is 80px, the legacy min-h-[80px] -- but as a spacing-scale step\n // rather than a bracket value, which check:hardcoding rejects.\n \"min-h-20\",\n \"px-(--density-control-pad-md) py-(--density-control-pad-sm)\",\n \"text-base\",\n\n // A textarea is resizable by the user agent, and horizontal resize breaks\n // any layout it sits in. Vertical only.\n \"resize-y\",\n // Height is content, not a control slot, so the line-height has to be set\n // explicitly -- unset it computes to `normal`, which varies with the\n // font's own metrics.\n \"leading-(--leading-normal)\",\n]);\n\n/**\n * A multi-line text field.\n *\n * @example\n * <Textarea placeholder=\"Tell us more\" value={v} onChange={e => set(e.target.value)} />\n */\nexport interface TextareaProps extends Omit<AriaTextAreaProps, \"className\" | \"style\"> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n /**\n * A visible label above the field, tied to it with `htmlFor`. Passing any of\n * `label`, `description` or `error` (even as `undefined`) wraps the field;\n * with none of them the DOM is exactly the bare textarea. Do not also pass\n * `aria-label`: it wins over the label (WCAG 2.5.3). Ignored inside a React\n * Aria `TextField`, which supplies its own label and messages.\n */\n label?: ReactNode;\n /**\n * Helper text under the field, named by `aria-describedby`. A count in it,\n * such as \"12/200 characters\", rolls digit by digit as it changes. Opens on a\n * spring when it appears after mount; nothing animates on first render, and\n * reduced motion swaps it instantly. A string, because the copy is animated\n * word by word.\n */\n description?: string;\n /**\n * Error text under the field. Sets `aria-invalid`, so the field takes its\n * invalid border, and is announced as an alert when it appears. An empty or\n * `undefined` value closes the row.\n */\n error?: string;\n}\n\nexport const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(\n function Textarea(allProps, ref) {\n const { className, label, description, error, ...props } = allProps;\n const field = useFieldWiring({\n enabled: hasFieldProps(allProps),\n id: props.id,\n describedBy: props[\"aria-describedby\"],\n invalid: props[\"aria-invalid\"],\n description,\n error,\n });\n const textarea = (\n <AriaTextArea\n ref={ref}\n {...props}\n {...field.controlProps}\n data-slot=\"textarea\"\n className={cn(textareaVariants(), className)}\n />\n );\n if (!field.active) return textarea;\n return (\n <FieldShell\n label={label}\n controlId={field.controlId}\n description={description}\n hintId={field.hintId}\n error={error}\n errorId={field.errorId}\n wordExit=\"quick\"\n >\n {textarea}\n </FieldShell>\n );\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,mBAAmB,IAAI;CAClC,qBAAqB;CAIrB;CACA;CACA;CAIA;CAIA;AACF,CAAC;AAmCD,MAAa,WAAW,WACtB,SAAS,SAAS,UAAU,KAAK;CAC/B,MAAM,EAAE,WAAW,OAAO,aAAa,OAAO,GAAG,UAAU;CAC3D,MAAM,QAAQ,eAAe;EAC3B,SAAS,cAAc,QAAQ;EAC/B,IAAI,MAAM;EACV,aAAa,MAAM;EACnB,SAAS,MAAM;EACf;EACA;CACF,CAAC;CACD,MAAM,WACJ,oBAACA,UAAD;EACO;EACL,GAAI;EACJ,GAAI,MAAM;EACV,aAAU;EACV,WAAW,GAAG,iBAAiB,GAAG,SAAS;CAC5C,CAAA;CAEH,IAAI,CAAC,MAAM,QAAQ,OAAO;CAC1B,OACE,oBAAC,YAAD;EACS;EACP,WAAW,MAAM;EACJ;EACb,QAAQ,MAAM;EACP;EACP,SAAS,MAAM;EACf,UAAS;EAER,UAAA;CACS,CAAA;AAEhB,CACF"}
1
+ {"version":3,"file":"textarea.js","names":["AriaTextArea"],"sources":["../../src/textarea/textarea.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n TextArea as AriaTextArea,\n type TextAreaProps as AriaTextAreaProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { FieldShell, hasFieldProps, useFieldWiring } from \"../lib/field-shell.js\";\nimport { fieldSurfaceVariants } from \"../lib/field-surface.js\";\n\n/**\n * Textarea, ported from the legacy component onto React Aria's TextArea.\n *\n * Census: 3 files, 3 uses -- `value`, `onChange`, `placeholder`, `id`,\n * `ref`. All native props, all still native here.\n *\n * The surface comes from `fieldSurfaceVariants`, shared with Input. The two\n * legacy files styled it separately and had already drifted -- different\n * background, different focus ring, invalid styling on one and not the\n * other -- and none of those differences was a decision anyone made.\n */\nexport const textareaVariants = cva([\n fieldSurfaceVariants(),\n\n // min-h-20 is 80px, the legacy min-h-[80px] -- but as a spacing-scale step\n // rather than a bracket value, which check:hardcoding rejects.\n \"min-h-20\",\n \"px-(--density-control-pad-md) py-(--density-control-pad-sm)\",\n \"text-base\",\n\n // A textarea is resizable by the user agent, and horizontal resize breaks\n // any layout it sits in. Vertical only.\n \"resize-y\",\n // Height is content, not a control slot, so the line-height has to be set\n // explicitly -- unset it computes to `normal`, which varies with the\n // font's own metrics.\n \"leading-(--leading-normal)\",\n]);\n\n/**\n * A multi-line text field.\n *\n * @example\n * <Textarea placeholder=\"Tell us more\" value={v} onChange={e => set(e.target.value)} />\n */\nexport interface TextareaProps extends Omit<AriaTextAreaProps, \"className\" | \"style\"> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n /**\n * A visible label above the field, tied to it with `htmlFor`. Passing any of\n * `label`, `description` or `error` (even as `undefined`) wraps the field;\n * with none of them the DOM is exactly the bare textarea. Do not also pass\n * `aria-label`: it wins over the label (WCAG 2.5.3). Ignored inside a React\n * Aria `TextField`, which supplies its own label and messages.\n */\n label?: ReactNode;\n /**\n * Helper text under the field, named by `aria-describedby`. A count in it,\n * such as \"12/200 characters\", rolls digit by digit as it changes. Opens on a\n * spring when it appears after mount; nothing animates on first render, and\n * reduced motion swaps it instantly. A string, because the copy is animated\n * word by word.\n */\n description?: string;\n /**\n * Error text under the field. Sets `aria-invalid`, so the field takes its\n * invalid border, and is announced as an alert when it appears. An empty or\n * `undefined` value closes the row.\n */\n error?: string;\n /**\n * The field must be filled: the textarea is `required` (announced by screen\n * readers, and checked by native form validation) and the label shows the\n * required marker. The same prop as PasswordField's.\n * @default false\n */\n isRequired?: boolean;\n}\n\nexport const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(\n function Textarea(allProps, ref) {\n const { className, label, description, error, isRequired, ...props } = allProps;\n const field = useFieldWiring({\n enabled: hasFieldProps(allProps),\n id: props.id,\n describedBy: props[\"aria-describedby\"],\n invalid: props[\"aria-invalid\"],\n description,\n error,\n });\n const textarea = (\n <AriaTextArea\n ref={ref}\n {...props}\n {...(isRequired ? { required: true } : {})}\n {...field.controlProps}\n data-slot=\"textarea\"\n className={cn(textareaVariants(), className)}\n />\n );\n if (!field.active) return textarea;\n return (\n <FieldShell\n label={label}\n controlId={field.controlId}\n description={description}\n hintId={field.hintId}\n error={error}\n errorId={field.errorId}\n required={isRequired}\n wordExit=\"quick\"\n >\n {textarea}\n </FieldShell>\n );\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,mBAAmB,IAAI;CAClC,qBAAqB;CAIrB;CACA;CACA;CAIA;CAIA;AACF,CAAC;AA0CD,MAAa,WAAW,WACtB,SAAS,SAAS,UAAU,KAAK;CAC/B,MAAM,EAAE,WAAW,OAAO,aAAa,OAAO,YAAY,GAAG,UAAU;CACvE,MAAM,QAAQ,eAAe;EAC3B,SAAS,cAAc,QAAQ;EAC/B,IAAI,MAAM;EACV,aAAa,MAAM;EACnB,SAAS,MAAM;EACf;EACA;CACF,CAAC;CACD,MAAM,WACJ,oBAACA,UAAD;EACO;EACL,GAAI;EACJ,GAAK,aAAa,EAAE,UAAU,KAAK,IAAI,CAAC;EACxC,GAAI,MAAM;EACV,aAAU;EACV,WAAW,GAAG,iBAAiB,GAAG,SAAS;CAC5C,CAAA;CAEH,IAAI,CAAC,MAAM,QAAQ,OAAO;CAC1B,OACE,oBAAC,YAAD;EACS;EACP,WAAW,MAAM;EACJ;EACb,QAAQ,MAAM;EACP;EACP,SAAS,MAAM;EACf,UAAU;EACV,UAAS;EAER,UAAA;CACS,CAAA;AAEhB,CACF"}
@@ -34,11 +34,11 @@ import { UNSTABLE_ToastQueue } from "react-aria-components";
34
34
  /** Where the region sits. Fixed, above everything, out of the way of content. */
35
35
  export declare const toastRegionVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
36
36
  export declare const toastVariants: (props?: ({
37
- tone?: "success" | "warning" | "danger" | "neutral" | "info" | null | undefined;
37
+ tone?: "neutral" | "warning" | "success" | "info" | "danger" | null | undefined;
38
38
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
39
39
  /** The status icon's circle: the tone's light fill with its AA text colour. */
40
40
  export declare const toastIconVariants: (props?: ({
41
- tone?: "success" | "warning" | "danger" | "neutral" | "info" | null | undefined;
41
+ tone?: "neutral" | "warning" | "success" | "info" | "danger" | null | undefined;
42
42
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
43
43
  export declare const toastCloseVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
44
44
  /** What a toast carries. `title` is required: an unnamed toast announces nothing. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seekalfred/primitives",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Accessible, styled primitives built on React Aria Components.",
@@ -395,7 +395,7 @@
395
395
  "@lexical/rich-text": "0.52.0",
396
396
  "@lexical/selection": "0.52.0",
397
397
  "@lexical/utils": "0.52.0",
398
- "@seekalfred/tokens": "^0.7.3",
398
+ "@seekalfred/tokens": "^0.7.4",
399
399
  "class-variance-authority": "0.7.1",
400
400
  "clsx": "2.1.1",
401
401
  "lexical": "0.52.0",
@@ -404,7 +404,7 @@
404
404
  "devDependencies": {
405
405
  "@arethetypeswrong/cli": "0.18.5",
406
406
  "@internationalized/date": "3.12.4",
407
- "@seekalfred/styles": "^0.7.3",
407
+ "@seekalfred/styles": "^0.7.4",
408
408
  "@seekalfred/tsconfig": "0.0.0",
409
409
  "@size-limit/preset-small-lib": "13.0.3",
410
410
  "@storybook/react-vite": "10.6.0",