@seekalfred/primitives 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accordion/accordion.d.ts +5 -5
- package/dist/accordion/accordion.js +13 -8
- package/dist/accordion/accordion.js.map +1 -1
- package/dist/alert/alert.d.ts +1 -1
- package/dist/alert/alert.js +4 -2
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert-dialog/alert-dialog.d.ts +1 -1
- package/dist/alert-dialog/alert-dialog.js +4 -2
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/avatar/avatar.d.ts +1 -1
- package/dist/avatar/avatar.js +6 -4
- package/dist/avatar/avatar.js.map +1 -1
- package/dist/badge/badge.d.ts +3 -3
- package/dist/badge/badge.js +4 -2
- package/dist/badge/badge.js.map +1 -1
- package/dist/breadcrumb/breadcrumb.d.ts +3 -3
- package/dist/breadcrumb/breadcrumb.js +7 -4
- package/dist/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/button/button.d.ts +3 -3
- package/dist/button/button.js +4 -2
- package/dist/button/button.js.map +1 -1
- package/dist/calendar/calendar.d.ts +2 -2
- package/dist/calendar/calendar.js +7 -5
- package/dist/calendar/calendar.js.map +1 -1
- package/dist/card/card.d.ts +6 -6
- package/dist/card/card.js +19 -12
- package/dist/card/card.js.map +1 -1
- package/dist/checkbox/checkbox.d.ts +2 -2
- package/dist/checkbox/checkbox.js +4 -2
- package/dist/checkbox/checkbox.js.map +1 -1
- package/dist/dialog/dialog.d.ts +3 -3
- package/dist/dialog/dialog.js +7 -4
- package/dist/dialog/dialog.js.map +1 -1
- package/dist/input/input.d.ts +1 -1
- package/dist/input/input.js +1 -1
- package/dist/label/label.d.ts +1 -1
- package/dist/label/label.js +4 -2
- package/dist/label/label.js.map +1 -1
- package/dist/lib/merge-refs.js +22 -0
- package/dist/lib/merge-refs.js.map +1 -0
- package/dist/menu/menu.d.ts +5 -5
- package/dist/menu/menu.js +10 -6
- package/dist/menu/menu.js.map +1 -1
- package/dist/popover/popover.d.ts +2 -2
- package/dist/popover/popover.js +4 -2
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/progress.d.ts +2 -2
- package/dist/progress/progress.js +4 -2
- package/dist/progress/progress.js.map +1 -1
- package/dist/radio-group/radio-group.d.ts +3 -3
- package/dist/radio-group/radio-group.js +7 -4
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/scroll-area/scroll-area.d.ts +7 -6
- package/dist/scroll-area/scroll-area.js +6 -4
- package/dist/scroll-area/scroll-area.js.map +1 -1
- package/dist/select/select.d.ts +3 -3
- package/dist/select/select.js +7 -4
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +1 -1
- package/dist/separator/separator.js +4 -2
- package/dist/separator/separator.js.map +1 -1
- package/dist/sheet/sheet.d.ts +1 -1
- package/dist/sheet/sheet.js +4 -2
- package/dist/sheet/sheet.js.map +1 -1
- package/dist/skeleton/skeleton.d.ts +1 -1
- package/dist/skeleton/skeleton.js +4 -2
- package/dist/skeleton/skeleton.js.map +1 -1
- package/dist/slider/slider.d.ts +2 -2
- package/dist/slider/slider.js +4 -2
- package/dist/slider/slider.js.map +1 -1
- package/dist/spinner/spinner.d.ts +1 -1
- package/dist/spinner/spinner.js +4 -2
- package/dist/spinner/spinner.js.map +1 -1
- package/dist/switch/switch.d.ts +2 -2
- package/dist/switch/switch.js +4 -2
- package/dist/switch/switch.js.map +1 -1
- package/dist/table/table.d.ts +4 -4
- package/dist/table/table.js +4 -3
- package/dist/table/table.js.map +1 -1
- package/dist/tabs/tabs.d.ts +4 -4
- package/dist/tabs/tabs.js +13 -8
- package/dist/tabs/tabs.js.map +1 -1
- package/dist/textarea/textarea.d.ts +1 -1
- package/dist/textarea/textarea.js +4 -2
- package/dist/textarea/textarea.js.map +1 -1
- package/dist/toast/toast.d.ts +2 -2
- package/dist/toast/toast.js +4 -2
- package/dist/toast/toast.js.map +1 -1
- package/dist/tooltip/tooltip.d.ts +2 -2
- package/dist/tooltip/tooltip.js +4 -2
- package/dist/tooltip/tooltip.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spinner.js","names":[],"sources":["../../src/spinner/spinner.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type
|
|
1
|
+
{"version":3,"file":"spinner.js","names":[],"sources":["../../src/spinner/spinner.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, type ComponentPropsWithoutRef } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Spinner.\n *\n * There is no React Aria base: RAC ships no busy indicator, and a rotating\n * ring has no interaction model to inherit. Because it is not RAC-backed its\n * props follow ARCHITECTURE section 7 rule 1 -- VariantProps &\n * ComponentPropsWithoutRef -- rather than the Button's Omit-from-React-Aria\n * departure, which exists only to stop RAC-ignored props being advertised.\n *\n * Every string below is a STATIC literal. Tailwind scans source text, so a\n * class assembled at runtime is invisible to it and would never be emitted.\n *\n * THE LEGACY WOULD HAVE SHIPPED INVISIBLE. Its ring is\n * `border-gray-300 border-t-blue-600` and its rotation is `animate-spin`.\n * All three of those classes emit NO RULE in this repo: `--color-*: initial`\n * deleted Tailwind's default palette, and this package imports only\n * tailwindcss/utilities.css, never the default theme, so `--animate-spin` and\n * `@keyframes spin` did not exist either. A literal port would have compiled,\n * linted and type-checked into a static, colourless box.\n */\nexport const spinnerVariants = cva(\n [\n // inline-block so it sits in a line of text or inside a button without a\n // wrapper; shrink-0 so a flex parent cannot squash the circle into an oval.\n \"inline-block shrink-0\",\n // relative is load-bearing: the visually-hidden label below is positioned\n // against this box, and without a positioned ancestor it would anchor to\n // the page and drag the layout around.\n \"relative\",\n // The reset in @seekalfred/styles is OPT-IN, so a consumer may load none,\n // and content-box would add the border on top of the size asked for --\n // turning a 16px `sm` into 20px.\n \"box-border\",\n // --radius-circle is 50%, already on the scale. The legacy's rounded-full\n // is 9999px, which is the same thing said less precisely.\n \"rounded-circle\",\n // A ring with one coloured quarter: no SVG, and the same silhouette the\n // legacy had, so migrating is a visual no-op.\n //\n // --border-width rather than border-2: this system has ONE border\n // thickness and the token exists precisely so 121 components do not each\n // pick their own. The arc reads thinner than the legacy's 2px; that is the\n // cost of the contract.\n //\n // border-border is held to the 3:1 non-text floor and primary-strong to\n // 4.5:1 on both the page and a card -- both are in the build-failing\n // contrast contract, so neither can drift into invisibility.\n //\n // FOUR LONGHANDS, NOT `border-border` PLUS `border-t-primary-strong`.\n // That shorter pairing is a shorthand followed by a longhand, and it only\n // works while our stylesheet is the last one to define `.border-border`.\n // It is not: Storybook runs its own Tailwind over the story files, emits\n // `.border-border{border-color:...}` into the SAME cascade layer after\n // ours, and the shorthand resets all four sides -- the arc silently became\n // the track colour. Caught in a real browser; `pnpm test` did not see it,\n // because the Vitest harness does not load that second stylesheet. A\n // consumer running their own Tailwind is in exactly Storybook's position,\n // so this is a real defect and not a dev-server quirk. Per-side longhands\n // cannot be clobbered by a shorthand that lands later.\n \"border-(length:--border-width)\",\n \"border-s-border border-e-border border-b-border border-t-primary-strong\",\n // motion-safe:, because motion.css's header says every component using a\n // keyframe must gate it. The trade-off is real and named: a reduced-motion\n // user gets a static ring, which is exactly why role=\"status\" and the\n // required label do the actual work of saying \"busy\" -- the animation is\n // never the only signal.\n \"motion-safe:animate-spin\",\n ],\n {\n variants: {\n size: {\n sm: \"size-(--icon-sm)\",\n md: \"size-(--icon-md)\",\n lg: \"size-(--icon-lg)\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n/**\n * A busy indicator.\n *\n * Announces itself. Use it for an operation the user is waiting on; for\n * content-shaped placeholders during a page load, reach for `Skeleton`\n * instead, which is deliberately silent because thirty-three empty boxes\n * announcing themselves is noise.\n *\n * @example\n * <Spinner label=\"Loading campaigns\" />\n * <Spinner label=\"Saving\" size=\"sm\" />\n */\nexport interface SpinnerProps\n extends Omit<ComponentPropsWithoutRef<\"span\">, \"children\" | \"role\" | \"aria-label\">,\n VariantProps<typeof spinnerVariants> {\n /**\n * What is loading. Becomes the spinner's accessible name.\n *\n * REQUIRED, so a nameless spinner is a type error rather than an omission.\n * The legacy component has no role, no name and no announced text, and it is\n * only about 1% of the spinning in that app -- there are also 63 lucide\n * `Loader2` spins and 8 hand-rolled divs, none of which announce either.\n * A required name is what makes that unrepeatable here.\n *\n * A `string` and not a `ReactNode` because it becomes an `aria-label`, which\n * is a DOM string attribute. `Progress.label` is a ReactNode only because\n * React Aria renders it; the difference is forced by the mechanism rather\n * than chosen.\n *\n * There is no `labelHidden` counterpart: not one legacy call site renders\n * text through the spinner, and the one component that shows a message\n * composes its own heading beside one. That is a layout, not a prop.\n */\n label: string;\n /**\n * Diameter, from the icon scale.\n *\n * All three steps are used by the legacy. The values come from\n * `--icon-sm/md/lg` rather than being restated, because that app already has\n * two spinners sharing this exact union and disagreeing on every pixel --\n * `h-4/6/8` against `h-8/12/16`.\n *\n * @default \"md\"\n */\n size?: VariantProps<typeof spinnerVariants>[\"size\"];\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport const Spinner = forwardRef<HTMLSpanElement, SpinnerProps>(function Spinner(\n { className, size, label, ...props },\n ref,\n) {\n return (\n <span\n ref={ref}\n {...props}\n // Both the attribute and the text, deliberately. role=\"status\" maps to\n // ARIA `status`, whose name-from is AUTHOR ONLY -- its content does not\n // name it -- so aria-label is what turns the required prop into an\n // actual accessible name. The text is separately necessary: a live\n // region inserted empty has nothing to announce.\n role=\"status\"\n aria-label={label}\n data-slot=\"spinner\"\n className={cn(spinnerVariants({ size }), className)}\n >\n {/*\n Clipped rather than `sr-only` from a stylesheet: this package's CSS is\n scanned from source, but relying on a utility that only ever appears\n here is how a class silently fails to compile. Inline styles cannot.\n absolute + 1px + clip is the standard visually-hidden recipe, and it\n keeps the ring square -- the story asserts that.\n */}\n <span\n style={{\n position: \"absolute\",\n width: \"1px\",\n height: \"1px\",\n margin: \"-1px\",\n padding: 0,\n overflow: \"hidden\",\n clipPath: \"inset(50%)\",\n whiteSpace: \"nowrap\",\n borderWidth: 0,\n }}\n >\n {label}\n </span>\n </span>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,MAAa,kBAAkB,IAC7B;CAGE;CAIA;CAIA;CAGA;CAwBA;CACA;CAMA;AACF,GACA;CACE,UAAU,EACR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;CACN,EACF;CACA,iBAAiB,EAAE,MAAM,KAAK;AAChC,CACF;AAmDA,MAAa,UAAU,WAA0C,SAAS,QACxE,EAAE,WAAW,MAAM,OAAO,GAAG,SAC7B,KACA;CACA,OACE,oBAAC,QAAD;EACO;EACL,GAAI;EAMJ,MAAK;EACL,cAAY;EACZ,aAAU;EACV,WAAW,GAAG,gBAAgB,EAAE,KAAK,CAAC,GAAG,SAAS;EASlD,UAAA,oBAAC,QAAD;GACE,OAAO;IACL,UAAU;IACV,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,UAAU;IACV,UAAU;IACV,YAAY;IACZ,aAAa;GACf;GAEC,UAAA;EACG,CAAA;CACF,CAAA;AAEV,CAAC"}
|
package/dist/switch/switch.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { SwitchProps } from "react-aria-components";
|
|
2
1
|
import { ReactNode } from "react";
|
|
2
|
+
import { SwitchProps } from "react-aria-components";
|
|
3
3
|
//#region src/switch/switch.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Switch, ported from the legacy shadcn/Radix component onto React Aria.
|
|
@@ -44,7 +44,7 @@ interface SwitchProps$1 extends Omit<SwitchProps, "className" | "style" | "child
|
|
|
44
44
|
/** Extra classes for the label. Merged last, so they win conflicts. */
|
|
45
45
|
className?: string;
|
|
46
46
|
}
|
|
47
|
-
declare
|
|
47
|
+
declare const Switch$1: import("react").ForwardRefExoticComponent<SwitchProps$1 & import("react").RefAttributes<HTMLInputElement>>;
|
|
48
48
|
//#endregion
|
|
49
49
|
export { Switch$1 as Switch, SwitchProps$1 as SwitchProps };
|
|
50
50
|
//# sourceMappingURL=switch.d.ts.map
|
package/dist/switch/switch.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
3
|
import { selectionLabelVariants, selectionRingVariants } from "../lib/selection-control.js";
|
|
4
4
|
import { cva } from "class-variance-authority";
|
|
5
|
+
import { forwardRef } from "react";
|
|
5
6
|
import { Switch } from "react-aria-components";
|
|
6
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
8
|
//#region src/switch/switch.tsx
|
|
@@ -41,9 +42,10 @@ const switchThumbVariants = cva([
|
|
|
41
42
|
"group-data-[selected]:translate-x-4",
|
|
42
43
|
"rtl:group-data-[selected]:-translate-x-4"
|
|
43
44
|
]);
|
|
44
|
-
|
|
45
|
+
const Switch$1 = forwardRef(function Switch$2({ className, children, ...props }, ref) {
|
|
45
46
|
return /* @__PURE__ */ jsxs(Switch, {
|
|
46
47
|
...props,
|
|
48
|
+
inputRef: ref,
|
|
47
49
|
"data-slot": "switch",
|
|
48
50
|
className: cn(selectionLabelVariants(), className),
|
|
49
51
|
children: [/* @__PURE__ */ jsx("span", {
|
|
@@ -59,7 +61,7 @@ function Switch$1({ className, children, ...props }) {
|
|
|
59
61
|
children
|
|
60
62
|
})]
|
|
61
63
|
});
|
|
62
|
-
}
|
|
64
|
+
});
|
|
63
65
|
//#endregion
|
|
64
66
|
export { Switch$1 as Switch, switchThumbVariants, switchTrackVariants };
|
|
65
67
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"switch.js","names":["Switch","AriaSwitch"],"sources":["../../src/switch/switch.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport type
|
|
1
|
+
{"version":3,"file":"switch.js","names":["Switch","AriaSwitch"],"sources":["../../src/switch/switch.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport { Switch as AriaSwitch, type SwitchProps as AriaSwitchProps } from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { selectionLabelVariants, selectionRingVariants } from \"../lib/selection-control.js\";\n\n/**\n * Switch, ported from the legacy shadcn/Radix component onto React Aria.\n *\n * Census: 7 files, 16 uses. `checked` 16, `onCheckedChange` 16, `id` 9,\n * `disabled` 7, `className` 4, `aria-label` 3.\n *\n * WORTH RAISING: only 3 of the 16 pass `aria-label` and 9 pass `id`. A\n * Radix Switch renders a bare `<button role=\"switch\">` with no text, so any\n * use with neither has no accessible name at all. Here the label is\n * `children`, so the common case names itself.\n *\n * THE COLOURS ARE NOT THE LEGACY'S, AND COULD NOT BE. The naive design --\n * a white thumb on a `--color-primary` track -- is 2.44:1, and a white\n * thumb on `--color-muted` is 1.13:1. Both fail WCAG 1.4.11, which is the\n * criterion that applies to the parts of a graphic you need to see to read\n * its state. So the track is `border` unchecked and `primary-strong`\n * checked, which puts every pair over 3:1 in both themes (5.25 / 5.20\n * light, 3.73 / 9.65 dark) and needs no border on the thumb.\n *\n * Position still carries the state as well as colour: the thumb moves.\n * That is the non-colour cue WCAG 1.4.1 asks for.\n */\nexport const switchTrackVariants = cva([\n \"relative inline-flex h-5 w-9 shrink-0 items-center\",\n \"rounded-full box-border p-0.5\",\n \"bg-border\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\n \"group-data-[selected]:bg-primary-strong\",\n selectionRingVariants(),\n]);\n\nexport const switchThumbVariants = cva([\n \"block size-4 rounded-full bg-background shadow-surface\",\n // translate-x-4, not the legacy translate-x-[calc(100%-2px)]: a bracket\n // value that check:hardcoding rejects. The track is w-9 (36px) with p-0.5\n // (2px) each side and a size-4 (16px) thumb, so the travel is exactly\n // 36 - 4 - 16 = 16px.\n \"motion-safe:transition-transform motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\n \"group-data-[selected]:translate-x-4\",\n // RTL: the thumb must travel the other way, or the switch reads\n // backwards. `rtl:` is a first-class Tailwind variant.\n \"rtl:group-data-[selected]:-translate-x-4\",\n]);\n\n/**\n * A switch.\n *\n * @example\n * <Switch isSelected={on} onChange={setOn}>Email notifications</Switch>\n * <Switch aria-label=\"Mute\" isSelected={muted} onChange={setMuted} />\n */\nexport interface SwitchProps extends Omit<AriaSwitchProps, \"className\" | \"style\" | \"children\"> {\n /**\n * The label, and the control's accessible name.\n *\n * Omit it only for a genuinely label-less switch, and pass `aria-label`\n * instead. A Radix Switch with neither -- which the census suggests\n * exists -- has no accessible name at all.\n */\n children?: ReactNode;\n /** Extra classes for the label. Merged last, so they win conflicts. */\n className?: string;\n}\n\nexport const Switch = forwardRef<HTMLInputElement, SwitchProps>(function Switch(\n { className, children, ...props },\n ref,\n) {\n return (\n <AriaSwitch\n {...props}\n // inputRef, not ref: AriaSwitch's own ref targets the label it\n // renders around a visually-hidden native input, and a label cannot\n // be focused -- ref.current.focus() would silently no-op. inputRef is\n // React Aria's own escape hatch to the real control.\n inputRef={ref}\n data-slot=\"switch\"\n className={cn(selectionLabelVariants(), className)}\n >\n {/*\n aria-hidden: the real input already carries the state. A decorative\n track announced as well would say everything twice.\n */}\n <span aria-hidden=\"true\" data-slot=\"switch-track\" className={switchTrackVariants()}>\n <span data-slot=\"switch-thumb\" className={switchThumbVariants()} />\n </span>\n {children == null ? null : <span data-slot=\"switch-label\">{children}</span>}\n </AriaSwitch>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,sBAAsB,IAAI;CACrC;CACA;CACA;CACA;CACA;CACA,sBAAsB;AACxB,CAAC;AAED,MAAa,sBAAsB,IAAI;CACrC;CAKA;CACA;CAGA;AACF,CAAC;AAsBD,MAAaA,WAAS,WAA0C,SAASA,SACvE,EAAE,WAAW,UAAU,GAAG,SAC1B,KACA;CACA,OACE,qBAACC,QAAD;EACE,GAAI;EAKJ,UAAU;EACV,aAAU;EACV,WAAW,GAAG,uBAAuB,GAAG,SAAS;EARnD,UAAA,CAcE,oBAAC,QAAD;GAAM,eAAY;GAAO,aAAU;GAAe,WAAW,oBAAoB;GAC/E,UAAA,oBAAC,QAAD;IAAM,aAAU;IAAe,WAAW,oBAAoB;GAAI,CAAA;EAC9D,CAAA,GACL,YAAY,OAAO,OAAO,oBAAC,QAAD;GAAM,aAAU;GAAgB;EAAe,CAAA,CAChE;;AAEhB,CAAC"}
|
package/dist/table/table.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { VariantProps } from "class-variance-authority";
|
|
2
|
+
import { ReactElement, ReactNode, RefAttributes } from "react";
|
|
2
3
|
import { SortDescriptor } from "react-aria-components";
|
|
3
|
-
import { ReactNode } from "react";
|
|
4
4
|
//#region src/table/table.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Table, ported from the legacy shadcn table onto React Aria's Table.
|
|
@@ -79,7 +79,7 @@ export declare const tablePaginationVariants: (props?: import("class-variance-au
|
|
|
79
79
|
/** The footer band. Heavier than a row rule, because it closes the table. */
|
|
80
80
|
export declare const tableFooterVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
81
81
|
export declare const tableColumnVariants: (props?: ({
|
|
82
|
-
align?: "
|
|
82
|
+
align?: "center" | "start" | "end" | null | undefined;
|
|
83
83
|
headerType?: "sentence" | "caps" | null | undefined;
|
|
84
84
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
85
85
|
export declare const tableRowVariants: (props?: ({
|
|
@@ -87,7 +87,7 @@ export declare const tableRowVariants: (props?: ({
|
|
|
87
87
|
striped?: boolean | null | undefined;
|
|
88
88
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
89
89
|
export declare const tableCellVariants: (props?: ({
|
|
90
|
-
align?: "
|
|
90
|
+
align?: "center" | "start" | "end" | null | undefined;
|
|
91
91
|
numeric?: boolean | null | undefined;
|
|
92
92
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
93
93
|
/**
|
|
@@ -282,7 +282,7 @@ export interface TableProps<T> extends VariantProps<typeof tableRowVariants> {
|
|
|
282
282
|
export declare function createColumns<T>(): {
|
|
283
283
|
column: <V>(definition: Column$1<T, V>) => Column$1<T, V>;
|
|
284
284
|
};
|
|
285
|
-
declare
|
|
285
|
+
declare const Table$1: <T>(props: TableProps<T> & RefAttributes<HTMLTableElement>) => ReactElement | null;
|
|
286
286
|
//#endregion
|
|
287
287
|
export { Column$1 as Column, Table$1 as Table };
|
|
288
288
|
//# sourceMappingURL=table.d.ts.map
|
package/dist/table/table.js
CHANGED
|
@@ -3,9 +3,9 @@ import { cn, cnAll } from "../lib/cn.js";
|
|
|
3
3
|
import { Button as Button$1 } from "../button/button.js";
|
|
4
4
|
import { Select as Select$1, SelectItem } from "../select/select.js";
|
|
5
5
|
import { cva } from "class-variance-authority";
|
|
6
|
+
import { forwardRef, useMemo, useState } from "react";
|
|
6
7
|
import { Cell, Column, Row, Table, TableBody, TableFooter, TableHeader } from "react-aria-components";
|
|
7
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
-
import { useMemo, useState } from "react";
|
|
9
9
|
//#region src/table/table.tsx
|
|
10
10
|
/**
|
|
11
11
|
* Table, ported from the legacy shadcn table onto React Aria's Table.
|
|
@@ -317,7 +317,7 @@ function compareKeys(a, b) {
|
|
|
317
317
|
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
318
318
|
return String(a).localeCompare(String(b));
|
|
319
319
|
}
|
|
320
|
-
|
|
320
|
+
const Table$1 = forwardRef(function Table$2({ data, columns, getRowId, variant, sortDescriptor, defaultSortDescriptor, onSortChange, onRowAction, isRowHighlighted, rowClassName, frame, headerType, headerFill, striped, totalRow, empty, minWidth, pageSize, pageSizeOptions, onPageSizeChange, pageLabels, className, ...props }, ref) {
|
|
321
321
|
const isControlled = sortDescriptor !== void 0;
|
|
322
322
|
const [uncontrolledSort, setUncontrolledSort] = useState(defaultSortDescriptor);
|
|
323
323
|
const activeSort = isControlled ? sortDescriptor : uncontrolledSort;
|
|
@@ -359,6 +359,7 @@ function Table$1({ data, columns, getRowId, variant, sortDescriptor, defaultSort
|
|
|
359
359
|
"data-slot": "table-scroll",
|
|
360
360
|
className: cnAll(tableScrollVariants()),
|
|
361
361
|
children: /* @__PURE__ */ jsxs(Table, {
|
|
362
|
+
ref,
|
|
362
363
|
...props,
|
|
363
364
|
"data-slot": "table",
|
|
364
365
|
className: cn(tableVariants(), className),
|
|
@@ -506,7 +507,7 @@ function Table$1({ data, columns, getRowId, variant, sortDescriptor, defaultSort
|
|
|
506
507
|
})]
|
|
507
508
|
})]
|
|
508
509
|
});
|
|
509
|
-
}
|
|
510
|
+
});
|
|
510
511
|
//#endregion
|
|
511
512
|
export { Table$1 as Table, createColumns, tableCardVariants, tableCellVariants, tableColumnVariants, tableFooterVariants, tableHeaderVariants, tablePaginationVariants, tableRowVariants, tableScrollVariants, tableVariants };
|
|
512
513
|
|
package/dist/table/table.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.js","names":["Table","AriaTable","AriaTableHeader","AriaColumn","AriaTableBody","AriaRow","AriaCell","AriaTableFooter","Button","Select"],"sources":["../../src/table/table.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { useMemo, useState, type CSSProperties, type ReactNode } 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 SortDescriptor,\n} from \"react-aria-components\";\n\nimport { Button } from \"../button/button.js\";\nimport { cn, cnAll } from \"../lib/cn.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 */\nexport const tableCardVariants = cva([\"w-full box-border overflow-hidden\"], {\n variants: {\n frame: {\n card: \"rounded-md border-(length:--border-width) border-separator bg-background\",\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 * 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/** 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-background 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/** The footer band. Heavier than a row rule, because it closes the table. */\nexport const tableFooterVariants = cva([\n \"bg-background 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 ...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 \"box-border\",\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 // 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 [\"px-(--density-row-pad-x) py-(--density-row-pad-y)\", \"box-border align-middle\", ...FOCUS_RING],\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\", false: \"\" },\n },\n defaultVariants: { align: \"start\", numeric: false },\n },\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=\"size-(--icon-sm) ms-1 inline-block align-middle\"\n >\n {direction === \"none\" ? (\n <>\n <path d=\"m4 7 4-4 4 4\" />\n <path d=\"m4 9 4 4 4-4\" />\n </>\n ) : (\n <path d={direction === \"ascending\" ? \"m4 10 4-4 4 4\" : \"m4 6 4 4 4-4\"} />\n )}\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 /** Minimum table width in px. Below it the wrapper scrolls horizontally. */\n minWidth?: number;\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 * 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/** 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**. Selection, row expansion, filtering, grouping, the total\n * row and pagination are deliberately absent -- each had one consumer or none\n * across the six legacy tables, and the stateful ones belong to DataGrid.\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 function Table<T>({\n data,\n columns,\n getRowId,\n variant,\n sortDescriptor,\n defaultSortDescriptor,\n onSortChange,\n onRowAction,\n isRowHighlighted,\n rowClassName,\n frame,\n headerType,\n headerFill,\n striped,\n totalRow,\n empty,\n minWidth,\n pageSize,\n pageSizeOptions,\n onPageSizeChange,\n pageLabels,\n className,\n ...props\n}: TableProps<T>) {\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 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 return (\n <div data-slot=\"table-card\" className={cnAll(tableCardVariants({ frame }))}>\n <div data-slot=\"table-scroll\" className={cnAll(tableScrollVariants())}>\n <AriaTable\n {...props}\n data-slot=\"table\"\n className={cn(tableVariants(), className)}\n style={tableStyle}\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 {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 className={cnAll(\n tableColumnVariants({\n align: column.align ?? (column.numeric ? \"end\" : undefined),\n headerType,\n }),\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={empty === undefined ? undefined : () => empty}\n >\n {visibleRows.map((row, index) => (\n <AriaRow\n key={String(getRowId(row))}\n id={getRowId(row)}\n data-slot=\"table-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 {columns.map((column) => {\n const value: unknown = column.accessor(row);\n return (\n <AriaCell\n key={column.id}\n data-slot=\"table-cell\"\n className={cnAll(\n tableCellVariants({\n align: column.align ?? (column.numeric ? \"end\" : undefined),\n numeric: column.numeric,\n }),\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 {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 )}\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 {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 …\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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"table.js","names":["Table","AriaTable","AriaTableHeader","AriaColumn","AriaTableBody","AriaRow","AriaCell","AriaTableFooter","Button","Select"],"sources":["../../src/table/table.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n forwardRef,\n useMemo,\n useState,\n type CSSProperties,\n type ForwardedRef,\n type ReactElement,\n type ReactNode,\n type RefAttributes,\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 SortDescriptor,\n} from \"react-aria-components\";\n\nimport { Button } from \"../button/button.js\";\nimport { cn, cnAll } from \"../lib/cn.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 */\nexport const tableCardVariants = cva([\"w-full box-border overflow-hidden\"], {\n variants: {\n frame: {\n card: \"rounded-md border-(length:--border-width) border-separator bg-background\",\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 * 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/** 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-background 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/** The footer band. Heavier than a row rule, because it closes the table. */\nexport const tableFooterVariants = cva([\n \"bg-background 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 ...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 \"box-border\",\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 // 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 [\"px-(--density-row-pad-x) py-(--density-row-pad-y)\", \"box-border align-middle\", ...FOCUS_RING],\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\", false: \"\" },\n },\n defaultVariants: { align: \"start\", numeric: false },\n },\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=\"size-(--icon-sm) ms-1 inline-block align-middle\"\n >\n {direction === \"none\" ? (\n <>\n <path d=\"m4 7 4-4 4 4\" />\n <path d=\"m4 9 4 4 4-4\" />\n </>\n ) : (\n <path d={direction === \"ascending\" ? \"m4 10 4-4 4 4\" : \"m4 6 4 4 4-4\"} />\n )}\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 /** Minimum table width in px. Below it the wrapper scrolls horizontally. */\n minWidth?: number;\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 * 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/** 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**. Selection, row expansion, filtering, grouping, the total\n * row and pagination are deliberately absent -- each had one consumer or none\n * across the six legacy tables, and the stateful ones belong to DataGrid.\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 frame,\n headerType,\n headerFill,\n striped,\n totalRow,\n empty,\n minWidth,\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 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 return (\n <div data-slot=\"table-card\" className={cnAll(tableCardVariants({ frame }))}>\n <div 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 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 {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 className={cnAll(\n tableColumnVariants({\n align: column.align ?? (column.numeric ? \"end\" : undefined),\n headerType,\n }),\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={empty === undefined ? undefined : () => empty}\n >\n {visibleRows.map((row, index) => (\n <AriaRow\n key={String(getRowId(row))}\n id={getRowId(row)}\n data-slot=\"table-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 {columns.map((column) => {\n const value: unknown = column.accessor(row);\n return (\n <AriaCell\n key={column.id}\n data-slot=\"table-cell\"\n className={cnAll(\n tableCellVariants({\n align: column.align ?? (column.numeric ? \"end\" : undefined),\n numeric: column.numeric,\n }),\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 {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 )}\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 {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 …\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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"}
|
package/dist/tabs/tabs.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ interface TabsProps$1 extends Omit<TabsProps, "className" | "style"> {
|
|
|
53
53
|
/** Extra classes. Merged last, so they win conflicts with ours. */
|
|
54
54
|
className?: string;
|
|
55
55
|
}
|
|
56
|
-
declare
|
|
56
|
+
declare const Tabs$1: import("react").ForwardRefExoticComponent<TabsProps$1 & import("react").RefAttributes<HTMLDivElement>>;
|
|
57
57
|
/**
|
|
58
58
|
* The row of tabs.
|
|
59
59
|
*
|
|
@@ -63,17 +63,17 @@ declare function Tabs$1({ className, ...props }: TabsProps$1): import("react").J
|
|
|
63
63
|
interface TabListProps$1 extends Omit<TabListProps<object>, "className" | "style"> {
|
|
64
64
|
className?: string;
|
|
65
65
|
}
|
|
66
|
-
declare
|
|
66
|
+
declare const TabList$1: import("react").ForwardRefExoticComponent<TabListProps$1 & import("react").RefAttributes<HTMLDivElement>>;
|
|
67
67
|
/** One tab. Its `id` pairs it with the panel of the same `id`. */
|
|
68
68
|
interface TabProps$1 extends Omit<TabProps, "className" | "style"> {
|
|
69
69
|
className?: string;
|
|
70
70
|
}
|
|
71
|
-
declare
|
|
71
|
+
declare const Tab$1: import("react").ForwardRefExoticComponent<TabProps$1 & import("react").RefAttributes<HTMLDivElement>>;
|
|
72
72
|
/** One panel. Its `id` pairs it with the tab of the same `id`. */
|
|
73
73
|
interface TabPanelProps$1 extends Omit<TabPanelProps, "className" | "style"> {
|
|
74
74
|
className?: string;
|
|
75
75
|
}
|
|
76
|
-
declare
|
|
76
|
+
declare const TabPanel$1: import("react").ForwardRefExoticComponent<TabPanelProps$1 & import("react").RefAttributes<HTMLDivElement>>;
|
|
77
77
|
//#endregion
|
|
78
78
|
export { Tab$1 as Tab, TabList$1 as TabList, TabListProps$1 as TabListProps, TabPanel$1 as TabPanel, TabPanelProps$1 as TabPanelProps, TabProps$1 as TabProps, Tabs$1 as Tabs, TabsProps$1 as TabsProps };
|
|
79
79
|
//# sourceMappingURL=tabs.d.ts.map
|
package/dist/tabs/tabs.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
|
+
import { forwardRef } from "react";
|
|
4
5
|
import { Tab, TabList, TabPanel, Tabs } from "react-aria-components";
|
|
5
6
|
import { jsx } from "react/jsx-runtime";
|
|
6
7
|
//#region src/tabs/tabs.tsx
|
|
@@ -69,34 +70,38 @@ const tabPanelVariants = cva([
|
|
|
69
70
|
"data-[focus-visible]:outline-offset-(--focus-ring-offset)",
|
|
70
71
|
"data-[focus-visible]:outline-(--color-ring)"
|
|
71
72
|
]);
|
|
72
|
-
|
|
73
|
+
const Tabs$1 = forwardRef(function Tabs$2({ className, ...props }, ref) {
|
|
73
74
|
return /* @__PURE__ */ jsx(Tabs, {
|
|
75
|
+
ref,
|
|
74
76
|
...props,
|
|
75
77
|
"data-slot": "tabs",
|
|
76
78
|
className: cn(tabsVariants(), className)
|
|
77
79
|
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
+
});
|
|
81
|
+
const TabList$1 = forwardRef(function TabList$2({ className, ...props }, ref) {
|
|
80
82
|
return /* @__PURE__ */ jsx(TabList, {
|
|
83
|
+
ref,
|
|
81
84
|
...props,
|
|
82
85
|
"data-slot": "tab-list",
|
|
83
86
|
className: cn(tabListVariants(), className)
|
|
84
87
|
});
|
|
85
|
-
}
|
|
86
|
-
|
|
88
|
+
});
|
|
89
|
+
const Tab$1 = forwardRef(function Tab$2({ className, ...props }, ref) {
|
|
87
90
|
return /* @__PURE__ */ jsx(Tab, {
|
|
91
|
+
ref,
|
|
88
92
|
...props,
|
|
89
93
|
"data-slot": "tab",
|
|
90
94
|
className: cn(tabVariants(), className)
|
|
91
95
|
});
|
|
92
|
-
}
|
|
93
|
-
|
|
96
|
+
});
|
|
97
|
+
const TabPanel$1 = forwardRef(function TabPanel$2({ className, ...props }, ref) {
|
|
94
98
|
return /* @__PURE__ */ jsx(TabPanel, {
|
|
99
|
+
ref,
|
|
95
100
|
...props,
|
|
96
101
|
"data-slot": "tab-panel",
|
|
97
102
|
className: cn(tabPanelVariants(), className)
|
|
98
103
|
});
|
|
99
|
-
}
|
|
104
|
+
});
|
|
100
105
|
//#endregion
|
|
101
106
|
export { Tab$1 as Tab, TabList$1 as TabList, TabPanel$1 as TabPanel, Tabs$1 as Tabs, tabListVariants, tabPanelVariants, tabVariants, tabsVariants };
|
|
102
107
|
|
package/dist/tabs/tabs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs.js","names":["Tabs","AriaTabs","TabList","AriaTabList","Tab","AriaTab","TabPanel","AriaTabPanel"],"sources":["../../src/tabs/tabs.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport {\n Tab as AriaTab,\n TabList as AriaTabList,\n TabPanel as AriaTabPanel,\n Tabs as AriaTabs,\n type TabListProps as AriaTabListProps,\n type TabPanelProps as AriaTabPanelProps,\n type TabProps as AriaTabProps,\n type TabsProps as AriaTabsProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Tabs, ported from the legacy shadcn/Radix component.\n *\n * The largest legacy surface in this migration: 18 files, `TabsTrigger`\n * 51, `TabsContent` 31, `Tabs` 17, `TabsList` 17. Props: `value` 88,\n * `className` 76, `defaultValue` 12, `onValueChange` 10, `disabled` 6.\n *\n * The rename is mechanical but it touches every one of those:\n * `value` -> `selectedKey`, `defaultValue` -> `defaultSelectedKey`,\n * `onValueChange` -> `onSelectionChange`, and the `value` on each trigger\n * and panel becomes `id`. React Aria pairs a Tab to its TabPanel by that\n * id, exactly as Radix paired them by value.\n *\n * THE SELECTED TAB GAINS A BORDER, and that is a contrast fix rather than\n * a decoration. The legacy marks the selected tab with `bg-background` on\n * a `bg-muted` list -- 1.13:1, which is not a boundary anybody can see --\n * and leans on `shadow-sm` to carry the rest. Under WCAG 1.4.11 the thing\n * telling you which tab is active has to clear 3:1. `border-border` on\n * `muted` is 4.65:1 and is already in the contrast contract.\n *\n * Two bracket values are gone because check:hardcoding rejects them:\n * `p-[3px]` on the list and `h-[calc(100%-1px)]` on the trigger. The list\n * is `p-1` and the trigger sizes to the list rather than to a value one\n * pixel short of it.\n *\n * ONE BEHAVIOUR DIFFERS AND IS WORTH KNOWING. Radix keeps every mounted\n * TabsContent in the DOM and hides the inactive ones; React Aria renders\n * ONLY the selected panel. A panel that holds uncommitted form state, a\n * running video or a scroll position will lose it on tab change. React\n * Aria's `shouldForceMount` on TabPanel restores the old behaviour where\n * a call site needs it.\n */\nexport const tabsVariants = cva([\"flex flex-col gap-2 box-border font-sans\"]);\n\nexport const tabListVariants = cva([\n \"inline-flex w-fit items-center justify-center gap-1\",\n // p-1, not the legacy p-[3px]: a bracket value check:hardcoding rejects.\n \"rounded-lg p-1\",\n \"bg-muted text-muted-foreground\",\n \"box-border\",\n]);\n\nexport const tabVariants = cva([\n \"inline-flex flex-1 items-center justify-center gap-1.5 whitespace-nowrap\",\n \"rounded-md px-2 py-1\",\n \"text-sm leading-(--leading-normal) font-medium text-foreground\",\n \"cursor-pointer select-none box-border\",\n // A transparent border at rest so selecting one does not move the text.\n \"border-(length:--border-width) border-transparent\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\n\n // The selected tab. bg-background on a muted list is 1.13:1 -- invisible\n // as a boundary -- so the border is what satisfies WCAG 1.4.11 at\n // 4.65:1. The fill and the shadow are the polish on top of it.\n \"data-[selected]:bg-background data-[selected]:border-border\",\n \"data-[selected]:shadow-surface\",\n\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,\n // so 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 \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"[&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n]);\n\nexport const tabPanelVariants = cva([\n \"flex-1 box-border font-sans text-foreground\",\n // React Aria gives the panel tabindex=0 when it holds no focusable\n // child, so it can take a ring it does not want at rest.\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\n/**\n * A set of panels, one visible at a time.\n *\n * @example\n * <Tabs defaultSelectedKey=\"overview\">\n * <TabList aria-label=\"Sections\">\n * <Tab id=\"overview\">Overview</Tab>\n * <Tab id=\"activity\">Activity</Tab>\n * </TabList>\n * <TabPanel id=\"overview\">...</TabPanel>\n * <TabPanel id=\"activity\">...</TabPanel>\n * </Tabs>\n */\nexport interface TabsProps extends Omit<AriaTabsProps, \"className\" | \"style\"> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport function Tabs({ className, ...props }
|
|
1
|
+
{"version":3,"file":"tabs.js","names":["Tabs","AriaTabs","TabList","AriaTabList","Tab","AriaTab","TabPanel","AriaTabPanel"],"sources":["../../src/tabs/tabs.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef } from \"react\";\nimport {\n Tab as AriaTab,\n TabList as AriaTabList,\n TabPanel as AriaTabPanel,\n Tabs as AriaTabs,\n type TabListProps as AriaTabListProps,\n type TabPanelProps as AriaTabPanelProps,\n type TabProps as AriaTabProps,\n type TabsProps as AriaTabsProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Tabs, ported from the legacy shadcn/Radix component.\n *\n * The largest legacy surface in this migration: 18 files, `TabsTrigger`\n * 51, `TabsContent` 31, `Tabs` 17, `TabsList` 17. Props: `value` 88,\n * `className` 76, `defaultValue` 12, `onValueChange` 10, `disabled` 6.\n *\n * The rename is mechanical but it touches every one of those:\n * `value` -> `selectedKey`, `defaultValue` -> `defaultSelectedKey`,\n * `onValueChange` -> `onSelectionChange`, and the `value` on each trigger\n * and panel becomes `id`. React Aria pairs a Tab to its TabPanel by that\n * id, exactly as Radix paired them by value.\n *\n * THE SELECTED TAB GAINS A BORDER, and that is a contrast fix rather than\n * a decoration. The legacy marks the selected tab with `bg-background` on\n * a `bg-muted` list -- 1.13:1, which is not a boundary anybody can see --\n * and leans on `shadow-sm` to carry the rest. Under WCAG 1.4.11 the thing\n * telling you which tab is active has to clear 3:1. `border-border` on\n * `muted` is 4.65:1 and is already in the contrast contract.\n *\n * Two bracket values are gone because check:hardcoding rejects them:\n * `p-[3px]` on the list and `h-[calc(100%-1px)]` on the trigger. The list\n * is `p-1` and the trigger sizes to the list rather than to a value one\n * pixel short of it.\n *\n * ONE BEHAVIOUR DIFFERS AND IS WORTH KNOWING. Radix keeps every mounted\n * TabsContent in the DOM and hides the inactive ones; React Aria renders\n * ONLY the selected panel. A panel that holds uncommitted form state, a\n * running video or a scroll position will lose it on tab change. React\n * Aria's `shouldForceMount` on TabPanel restores the old behaviour where\n * a call site needs it.\n */\nexport const tabsVariants = cva([\"flex flex-col gap-2 box-border font-sans\"]);\n\nexport const tabListVariants = cva([\n \"inline-flex w-fit items-center justify-center gap-1\",\n // p-1, not the legacy p-[3px]: a bracket value check:hardcoding rejects.\n \"rounded-lg p-1\",\n \"bg-muted text-muted-foreground\",\n \"box-border\",\n]);\n\nexport const tabVariants = cva([\n \"inline-flex flex-1 items-center justify-center gap-1.5 whitespace-nowrap\",\n \"rounded-md px-2 py-1\",\n \"text-sm leading-(--leading-normal) font-medium text-foreground\",\n \"cursor-pointer select-none box-border\",\n // A transparent border at rest so selecting one does not move the text.\n \"border-(length:--border-width) border-transparent\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\n\n // The selected tab. bg-background on a muted list is 1.13:1 -- invisible\n // as a boundary -- so the border is what satisfies WCAG 1.4.11 at\n // 4.65:1. The fill and the shadow are the polish on top of it.\n \"data-[selected]:bg-background data-[selected]:border-border\",\n \"data-[selected]:shadow-surface\",\n\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,\n // so 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 \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"[&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n]);\n\nexport const tabPanelVariants = cva([\n \"flex-1 box-border font-sans text-foreground\",\n // React Aria gives the panel tabindex=0 when it holds no focusable\n // child, so it can take a ring it does not want at rest.\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\n/**\n * A set of panels, one visible at a time.\n *\n * @example\n * <Tabs defaultSelectedKey=\"overview\">\n * <TabList aria-label=\"Sections\">\n * <Tab id=\"overview\">Overview</Tab>\n * <Tab id=\"activity\">Activity</Tab>\n * </TabList>\n * <TabPanel id=\"overview\">...</TabPanel>\n * <TabPanel id=\"activity\">...</TabPanel>\n * </Tabs>\n */\nexport interface TabsProps extends Omit<AriaTabsProps, \"className\" | \"style\"> {\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport const Tabs = forwardRef<HTMLDivElement, TabsProps>(function Tabs(\n { className, ...props },\n ref,\n) {\n return (\n <AriaTabs ref={ref} {...props} data-slot=\"tabs\" className={cn(tabsVariants(), className)} />\n );\n});\n\n/**\n * The row of tabs.\n *\n * `aria-label` is worth passing: a tablist is a landmark-ish grouping and\n * a page with two of them is ambiguous without one.\n */\nexport interface TabListProps extends Omit<AriaTabListProps<object>, \"className\" | \"style\"> {\n className?: string;\n}\n\nexport const TabList = forwardRef<HTMLDivElement, TabListProps>(function TabList(\n { className, ...props },\n ref,\n) {\n return (\n <AriaTabList\n ref={ref}\n {...props}\n data-slot=\"tab-list\"\n className={cn(tabListVariants(), className)}\n />\n );\n});\n\n/** One tab. Its `id` pairs it with the panel of the same `id`. */\nexport interface TabProps extends Omit<AriaTabProps, \"className\" | \"style\"> {\n className?: string;\n}\n\nexport const Tab = forwardRef<HTMLDivElement, TabProps>(function Tab(\n { className, ...props },\n ref,\n) {\n return (\n <AriaTab ref={ref} {...props} data-slot=\"tab\" className={cn(tabVariants(), className)} />\n );\n});\n\n/** One panel. Its `id` pairs it with the tab of the same `id`. */\nexport interface TabPanelProps extends Omit<AriaTabPanelProps, \"className\" | \"style\"> {\n className?: string;\n}\n\nexport const TabPanel = forwardRef<HTMLDivElement, TabPanelProps>(function TabPanel(\n { className, ...props },\n ref,\n) {\n return (\n <AriaTabPanel\n ref={ref}\n {...props}\n data-slot=\"tab-panel\"\n className={cn(tabPanelVariants(), className)}\n />\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,MAAa,eAAe,IAAI,CAAC,0CAA0C,CAAC;AAE5E,MAAa,kBAAkB,IAAI;CACjC;CAEA;CACA;CACA;AACF,CAAC;AAED,MAAa,cAAc,IAAI;CAC7B;CACA;CACA;CACA;CAEA;CACA;CAKA;CACA;CAKA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;AACF,CAAC;AAED,MAAa,mBAAmB,IAAI;CAClC;CAGA;CACA;CACA;CACA;CACA;AACF,CAAC;AAoBD,MAAaA,SAAO,WAAsC,SAASA,OACjE,EAAE,WAAW,GAAG,SAChB,KACA;CACA,OACE,oBAACC,MAAD;EAAe;EAAK,GAAI;EAAO,aAAU;EAAO,WAAW,GAAG,aAAa,GAAG,SAAS;CAAI,CAAA;AAE/F,CAAC;AAYD,MAAaC,YAAU,WAAyC,SAASA,UACvE,EAAE,WAAW,GAAG,SAChB,KACA;CACA,OACE,oBAACC,SAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,gBAAgB,GAAG,SAAS;CAC3C,CAAA;AAEL,CAAC;AAOD,MAAaC,QAAM,WAAqC,SAASA,MAC/D,EAAE,WAAW,GAAG,SAChB,KACA;CACA,OACE,oBAACC,KAAD;EAAc;EAAK,GAAI;EAAO,aAAU;EAAM,WAAW,GAAG,YAAY,GAAG,SAAS;CAAI,CAAA;AAE5F,CAAC;AAOD,MAAaC,aAAW,WAA0C,SAASA,WACzE,EAAE,WAAW,GAAG,SAChB,KACA;CACA,OACE,oBAACC,UAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,iBAAiB,GAAG,SAAS;CAC5C,CAAA;AAEL,CAAC"}
|
|
@@ -22,6 +22,6 @@ export interface TextareaProps extends Omit<TextAreaProps, "className" | "style"
|
|
|
22
22
|
/** Extra classes. Merged last, so they win conflicts with ours. */
|
|
23
23
|
className?: string;
|
|
24
24
|
}
|
|
25
|
-
export declare
|
|
25
|
+
export declare const Textarea: import("react").ForwardRefExoticComponent<TextareaProps & import("react").RefAttributes<HTMLTextAreaElement>>;
|
|
26
26
|
//#endregion
|
|
27
27
|
//# sourceMappingURL=textarea.d.ts.map
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
3
|
import { fieldSurfaceVariants } from "../lib/field-surface.js";
|
|
4
4
|
import { cva } from "class-variance-authority";
|
|
5
|
+
import { forwardRef } from "react";
|
|
5
6
|
import { TextArea } from "react-aria-components";
|
|
6
7
|
import { jsx } from "react/jsx-runtime";
|
|
7
8
|
//#region src/textarea/textarea.tsx
|
|
@@ -24,13 +25,14 @@ const textareaVariants = cva([
|
|
|
24
25
|
"resize-y",
|
|
25
26
|
"leading-(--leading-normal)"
|
|
26
27
|
]);
|
|
27
|
-
function Textarea({ className, ...props }) {
|
|
28
|
+
const Textarea = forwardRef(function Textarea({ className, ...props }, ref) {
|
|
28
29
|
return /* @__PURE__ */ jsx(TextArea, {
|
|
30
|
+
ref,
|
|
29
31
|
...props,
|
|
30
32
|
"data-slot": "textarea",
|
|
31
33
|
className: cn(textareaVariants(), className)
|
|
32
34
|
});
|
|
33
|
-
}
|
|
35
|
+
});
|
|
34
36
|
//#endregion
|
|
35
37
|
export { Textarea, textareaVariants };
|
|
36
38
|
|