@rtkelly-labs/design-system 0.0.0-bootstrap.0 → 0.16.0-dev.0.37dedac
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/CHANGELOG.md +347 -0
- package/LICENSE +21 -0
- package/README.md +317 -2
- package/dist/components/AlertDialog.js +95 -0
- package/dist/components/AlertDialog.js.map +1 -0
- package/dist/components/AppShell.js +261 -0
- package/dist/components/AppShell.js.map +1 -0
- package/dist/components/AsciiDivider.js +8 -0
- package/dist/components/AsciiDivider.js.map +1 -0
- package/dist/components/AsciiGauge.js +130 -0
- package/dist/components/AsciiGauge.js.map +1 -0
- package/dist/components/Avatar.js +87 -0
- package/dist/components/Avatar.js.map +1 -0
- package/dist/components/Badge.js +25 -0
- package/dist/components/Badge.js.map +1 -0
- package/dist/components/BarChart.js +288 -0
- package/dist/components/BarChart.js.map +1 -0
- package/dist/components/BracketText.js +23 -0
- package/dist/components/BracketText.js.map +1 -0
- package/dist/components/BulletChart.js +50 -0
- package/dist/components/BulletChart.js.map +1 -0
- package/dist/components/Button.js +130 -0
- package/dist/components/Button.js.map +1 -0
- package/dist/components/Card.js +181 -0
- package/dist/components/Card.js.map +1 -0
- package/dist/components/ChartTooltip.js +54 -0
- package/dist/components/ChartTooltip.js.map +1 -0
- package/dist/components/Checkbox.js +55 -0
- package/dist/components/Checkbox.js.map +1 -0
- package/dist/components/ColophonCard.js +122 -0
- package/dist/components/ColophonCard.js.map +1 -0
- package/dist/components/DataTable.js +299 -0
- package/dist/components/DataTable.js.map +1 -0
- package/dist/components/Divider.js +42 -0
- package/dist/components/Divider.js.map +1 -0
- package/dist/components/Drawer.js +81 -0
- package/dist/components/Drawer.js.map +1 -0
- package/dist/components/EmptyState.js +39 -0
- package/dist/components/EmptyState.js.map +1 -0
- package/dist/components/ErrorSummary.js +122 -0
- package/dist/components/ErrorSummary.js.map +1 -0
- package/dist/components/Fieldset.js +37 -0
- package/dist/components/Fieldset.js.map +1 -0
- package/dist/components/Glyph.js +60 -0
- package/dist/components/Glyph.js.map +1 -0
- package/dist/components/Input.js +111 -0
- package/dist/components/Input.js.map +1 -0
- package/dist/components/LinkProvider.js +76 -0
- package/dist/components/LinkProvider.js.map +1 -0
- package/dist/components/Menu.js +144 -0
- package/dist/components/Menu.js.map +1 -0
- package/dist/components/MetadataGrid.js +184 -0
- package/dist/components/MetadataGrid.js.map +1 -0
- package/dist/components/MobileNav.js +120 -0
- package/dist/components/MobileNav.js.map +1 -0
- package/dist/components/Modal.js +79 -0
- package/dist/components/Modal.js.map +1 -0
- package/dist/components/NerdIcon.js +177 -0
- package/dist/components/NerdIcon.js.map +1 -0
- package/dist/components/NoteBlock.js +84 -0
- package/dist/components/NoteBlock.js.map +1 -0
- package/dist/components/PageHeader.js +34 -0
- package/dist/components/PageHeader.js.map +1 -0
- package/dist/components/PageTitle.js +21 -0
- package/dist/components/PageTitle.js.map +1 -0
- package/dist/components/Pagination.js +96 -0
- package/dist/components/Pagination.js.map +1 -0
- package/dist/components/Popover.js +87 -0
- package/dist/components/Popover.js.map +1 -0
- package/dist/components/Progress.js +83 -0
- package/dist/components/Progress.js.map +1 -0
- package/dist/components/RadioGroup.js +74 -0
- package/dist/components/RadioGroup.js.map +1 -0
- package/dist/components/ReportDocument.js +75 -0
- package/dist/components/ReportDocument.js.map +1 -0
- package/dist/components/SectionContainer.js +18 -0
- package/dist/components/SectionContainer.js.map +1 -0
- package/dist/components/Select.js +199 -0
- package/dist/components/Select.js.map +1 -0
- package/dist/components/SiteFooter.js +29 -0
- package/dist/components/SiteFooter.js.map +1 -0
- package/dist/components/SiteHeader.js +59 -0
- package/dist/components/SiteHeader.js.map +1 -0
- package/dist/components/SiteNav.js +141 -0
- package/dist/components/SiteNav.js.map +1 -0
- package/dist/components/Skeleton.js +51 -0
- package/dist/components/Skeleton.js.map +1 -0
- package/dist/components/SocialIcon.js +77 -0
- package/dist/components/SocialIcon.js.map +1 -0
- package/dist/components/Sparkline.js +104 -0
- package/dist/components/Sparkline.js.map +1 -0
- package/dist/components/Spinner.js +65 -0
- package/dist/components/Spinner.js.map +1 -0
- package/dist/components/StatCard.js +53 -0
- package/dist/components/StatCard.js.map +1 -0
- package/dist/components/StatusPage.js +99 -0
- package/dist/components/StatusPage.js.map +1 -0
- package/dist/components/Swatch.js +55 -0
- package/dist/components/Swatch.js.map +1 -0
- package/dist/components/Switch.js +53 -0
- package/dist/components/Switch.js.map +1 -0
- package/dist/components/TLDR.js +53 -0
- package/dist/components/TLDR.js.map +1 -0
- package/dist/components/Table.js +155 -0
- package/dist/components/Table.js.map +1 -0
- package/dist/components/Tabs.js +325 -0
- package/dist/components/Tabs.js.map +1 -0
- package/dist/components/Tag.js +19 -0
- package/dist/components/Tag.js.map +1 -0
- package/dist/components/TelemetryGauge.js +112 -0
- package/dist/components/TelemetryGauge.js.map +1 -0
- package/dist/components/ThemeProvider.js +87 -0
- package/dist/components/ThemeProvider.js.map +1 -0
- package/dist/components/TickerTape.js +196 -0
- package/dist/components/TickerTape.js.map +1 -0
- package/dist/components/Toast.js +200 -0
- package/dist/components/Toast.js.map +1 -0
- package/dist/components/Tooltip.js +69 -0
- package/dist/components/Tooltip.js.map +1 -0
- package/dist/components/TrackCard.js +206 -0
- package/dist/components/TrackCard.js.map +1 -0
- package/dist/components/blog/BlogPost.js +116 -0
- package/dist/components/blog/BlogPost.js.map +1 -0
- package/dist/components/blog/LoremIpsumPost.js +62 -0
- package/dist/components/blog/LoremIpsumPost.js.map +1 -0
- package/dist/components/blog/author.js +25 -0
- package/dist/components/blog/author.js.map +1 -0
- package/dist/components/booleanControl.js +50 -0
- package/dist/components/booleanControl.js.map +1 -0
- package/dist/components/dialogPopupRef.js +28 -0
- package/dist/components/dialogPopupRef.js.map +1 -0
- package/dist/components/dialogSurface.js +75 -0
- package/dist/components/dialogSurface.js.map +1 -0
- package/dist/components/docs/AnchorHeading.js +80 -0
- package/dist/components/docs/AnchorHeading.js.map +1 -0
- package/dist/components/docs/Breadcrumbs.js +26 -0
- package/dist/components/docs/Breadcrumbs.js.map +1 -0
- package/dist/components/docs/CodeBlock.js +63 -0
- package/dist/components/docs/CodeBlock.js.map +1 -0
- package/dist/components/docs/CodeTabs.js +71 -0
- package/dist/components/docs/CodeTabs.js.map +1 -0
- package/dist/components/docs/DocPager.js +28 -0
- package/dist/components/docs/DocPager.js.map +1 -0
- package/dist/components/docs/DocsHeader.js +124 -0
- package/dist/components/docs/DocsHeader.js.map +1 -0
- package/dist/components/docs/DocsLayout.js +61 -0
- package/dist/components/docs/DocsLayout.js.map +1 -0
- package/dist/components/docs/DocsLinkProvider.js +25 -0
- package/dist/components/docs/DocsLinkProvider.js.map +1 -0
- package/dist/components/docs/DocsSidebar.js +147 -0
- package/dist/components/docs/DocsSidebar.js.map +1 -0
- package/dist/components/docs/Prose.js +56 -0
- package/dist/components/docs/Prose.js.map +1 -0
- package/dist/components/docs/TableOfContents.js +71 -0
- package/dist/components/docs/TableOfContents.js.map +1 -0
- package/dist/components/docs/codeTabsStore.js +75 -0
- package/dist/components/docs/codeTabsStore.js.map +1 -0
- package/dist/components/docs/figures/ActivityGrid.js +116 -0
- package/dist/components/docs/figures/ActivityGrid.js.map +1 -0
- package/dist/components/docs/figures/BeforeAfter.js +64 -0
- package/dist/components/docs/figures/BeforeAfter.js.map +1 -0
- package/dist/components/docs/figures/ChangeSummary.js +74 -0
- package/dist/components/docs/figures/ChangeSummary.js.map +1 -0
- package/dist/components/docs/figures/Decision.js +34 -0
- package/dist/components/docs/figures/Decision.js.map +1 -0
- package/dist/components/docs/figures/DrawingSheet.js +37 -0
- package/dist/components/docs/figures/DrawingSheet.js.map +1 -0
- package/dist/components/docs/figures/FigureFrame.js +226 -0
- package/dist/components/docs/figures/FigureFrame.js.map +1 -0
- package/dist/components/docs/figures/FileChangeTree.js +134 -0
- package/dist/components/docs/figures/FileChangeTree.js.map +1 -0
- package/dist/components/docs/figures/FlowDiagram.js +82 -0
- package/dist/components/docs/figures/FlowDiagram.js.map +1 -0
- package/dist/components/docs/figures/GanttChart.js +79 -0
- package/dist/components/docs/figures/GanttChart.js.map +1 -0
- package/dist/components/docs/figures/IndexedSection.js +45 -0
- package/dist/components/docs/figures/IndexedSection.js.map +1 -0
- package/dist/components/docs/figures/MetricStrip.js +23 -0
- package/dist/components/docs/figures/MetricStrip.js.map +1 -0
- package/dist/components/docs/figures/Steps.js +58 -0
- package/dist/components/docs/figures/Steps.js.map +1 -0
- package/dist/components/docs/figures/SwatchCompare.js +42 -0
- package/dist/components/docs/figures/SwatchCompare.js.map +1 -0
- package/dist/components/docs/figures/TermGrid.js +22 -0
- package/dist/components/docs/figures/TermGrid.js.map +1 -0
- package/dist/components/docs/figures/Terminal.js +37 -0
- package/dist/components/docs/figures/Terminal.js.map +1 -0
- package/dist/components/docs/figures/Timeline.js +61 -0
- package/dist/components/docs/figures/Timeline.js.map +1 -0
- package/dist/components/docs/figures/TokenTable.js +42 -0
- package/dist/components/docs/figures/TokenTable.js.map +1 -0
- package/dist/components/docs/figures/TreeDiagram.js +76 -0
- package/dist/components/docs/figures/TreeDiagram.js.map +1 -0
- package/dist/components/docs/figures/UptimeStrip.js +68 -0
- package/dist/components/docs/figures/UptimeStrip.js.map +1 -0
- package/dist/components/docs/figures/index.js +24 -0
- package/dist/components/docs/figures/index.js.map +1 -0
- package/dist/components/docs/index.js +15 -0
- package/dist/components/docs/index.js.map +1 -0
- package/dist/components/docs/mdxComponents.js +108 -0
- package/dist/components/docs/mdxComponents.js.map +1 -0
- package/dist/components/experiments/DesignSandbox.js +206 -0
- package/dist/components/experiments/DesignSandbox.js.map +1 -0
- package/dist/components/experiments/ExperimentsView.js +138 -0
- package/dist/components/experiments/ExperimentsView.js.map +1 -0
- package/dist/components/fieldFrame.js +200 -0
- package/dist/components/fieldFrame.js.map +1 -0
- package/dist/components/floatingSurface.js +35 -0
- package/dist/components/floatingSurface.js.map +1 -0
- package/dist/components/marketing/CTASection.js +47 -0
- package/dist/components/marketing/CTASection.js.map +1 -0
- package/dist/components/marketing/FeatureGrid.js +49 -0
- package/dist/components/marketing/FeatureGrid.js.map +1 -0
- package/dist/components/marketing/Hero.js +44 -0
- package/dist/components/marketing/Hero.js.map +1 -0
- package/dist/components/marketing/PricingGrid.js +65 -0
- package/dist/components/marketing/PricingGrid.js.map +1 -0
- package/dist/components/marketing/SectionGrid.js +51 -0
- package/dist/components/marketing/SectionGrid.js.map +1 -0
- package/dist/components/paginationRange.js +28 -0
- package/dist/components/paginationRange.js.map +1 -0
- package/dist/components/portalTheme.js +14 -0
- package/dist/components/portalTheme.js.map +1 -0
- package/dist/components/saas/SaasLandingPage.js +201 -0
- package/dist/components/saas/SaasLandingPage.js.map +1 -0
- package/dist/components/siteNavContext.js +24 -0
- package/dist/components/siteNavContext.js.map +1 -0
- package/dist/components/slides/Slide.js +84 -0
- package/dist/components/slides/Slide.js.map +1 -0
- package/dist/components/slides/SlideDeck.js +228 -0
- package/dist/components/slides/SlideDeck.js.map +1 -0
- package/dist/components/themeInitScript.js +16 -0
- package/dist/components/themeInitScript.js.map +1 -0
- package/dist/components/themeScope.js +9 -0
- package/dist/components/themeScope.js.map +1 -0
- package/dist/components/toastLifetime.js +21 -0
- package/dist/components/toastLifetime.js.map +1 -0
- package/dist/fonts/DSSymbols-OFL.txt +98 -0
- package/dist/fonts/DSSymbols-Regular.woff2 +0 -0
- package/dist/fonts/SymbolsNerdFontMono-Regular.woff2 +0 -0
- package/dist/hooks/useActiveHeading.js +59 -0
- package/dist/hooks/useActiveHeading.js.map +1 -0
- package/dist/hooks/useCopyToClipboard.js +58 -0
- package/dist/hooks/useCopyToClipboard.js.map +1 -0
- package/dist/index.d.ts +6394 -0
- package/dist/index.js +117 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/accentClasses.js +87 -0
- package/dist/lib/accentClasses.js.map +1 -0
- package/dist/lib/recipe.js +33 -0
- package/dist/lib/recipe.js.map +1 -0
- package/dist/lib/slug.js +39 -0
- package/dist/lib/slug.js.map +1 -0
- package/dist/lib/theme.js +76 -0
- package/dist/lib/theme.js.map +1 -0
- package/dist/lib/tokenRules.js +51 -0
- package/dist/lib/tokenRules.js.map +1 -0
- package/dist/prose.css +766 -0
- package/dist/styles.css +203 -0
- package/dist/theme/ansi.js +77 -0
- package/dist/theme/ansi.js.map +1 -0
- package/dist/theme/contrast.js +342 -0
- package/dist/theme/contrast.js.map +1 -0
- package/dist/theme/levels.js +234 -0
- package/dist/theme/levels.js.map +1 -0
- package/dist/theme/media.js +115 -0
- package/dist/theme/media.js.map +1 -0
- package/dist/theme/recommended.js +105 -0
- package/dist/theme/recommended.js.map +1 -0
- package/dist/theme.css +538 -0
- package/dist/tokens.js +1 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +209 -6
- package/skills/design-system-usage/SKILL.md +75 -0
- package/terminal/rtk-midnight.alacritty.toml +33 -0
- package/terminal/rtk-midnight.ghostty.conf +24 -0
- package/terminal/rtk-midnight.itermcolors +167 -0
- package/terminal/rtk-midnight.windows-terminal.json +23 -0
- package/terminal/rtk-sketch.alacritty.toml +33 -0
- package/terminal/rtk-sketch.ghostty.conf +24 -0
- package/terminal/rtk-sketch.itermcolors +167 -0
- package/terminal/rtk-sketch.windows-terminal.json +23 -0
- package/tokens/palette.midnight.tokens.json +477 -0
- package/tokens/palette.sketch.tokens.json +477 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/DataTable.tsx
|
|
4
|
+
import {
|
|
5
|
+
flexRender,
|
|
6
|
+
getCoreRowModel,
|
|
7
|
+
getFilteredRowModel,
|
|
8
|
+
getPaginationRowModel,
|
|
9
|
+
getSortedRowModel,
|
|
10
|
+
useReactTable
|
|
11
|
+
} from "@tanstack/react-table";
|
|
12
|
+
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
13
|
+
import {
|
|
14
|
+
useId,
|
|
15
|
+
useLayoutEffect,
|
|
16
|
+
useMemo,
|
|
17
|
+
useRef,
|
|
18
|
+
useState
|
|
19
|
+
} from "react";
|
|
20
|
+
import { cn } from "../lib/recipe.js";
|
|
21
|
+
import { NerdIcon } from "./NerdIcon.js";
|
|
22
|
+
import { Pagination } from "./Pagination.js";
|
|
23
|
+
import {
|
|
24
|
+
Table,
|
|
25
|
+
TableBody,
|
|
26
|
+
TableCaption,
|
|
27
|
+
TableCell,
|
|
28
|
+
TableHead,
|
|
29
|
+
TableHeader,
|
|
30
|
+
TableRow
|
|
31
|
+
} from "./Table.js";
|
|
32
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
33
|
+
var SORT_HINT = { asc: "Sort ascending", desc: "Sort descending" };
|
|
34
|
+
var SPACER_STYLE_BASE = { padding: 0, border: 0 };
|
|
35
|
+
function spacerStyle(height) {
|
|
36
|
+
return { height, ...SPACER_STYLE_BASE };
|
|
37
|
+
}
|
|
38
|
+
function scrollBoxStyle(height) {
|
|
39
|
+
return { height };
|
|
40
|
+
}
|
|
41
|
+
function DataTable({
|
|
42
|
+
table: providedTable,
|
|
43
|
+
columns,
|
|
44
|
+
data,
|
|
45
|
+
keyExtractor,
|
|
46
|
+
caption,
|
|
47
|
+
appearance = "grid",
|
|
48
|
+
density = "comfortable",
|
|
49
|
+
emptyText = "No items found.",
|
|
50
|
+
className = "",
|
|
51
|
+
containerClassName = "",
|
|
52
|
+
virtualize,
|
|
53
|
+
...rest
|
|
54
|
+
}) {
|
|
55
|
+
const [sorting, setSorting] = useState([]);
|
|
56
|
+
const uid = useId();
|
|
57
|
+
const virtualization = useMemo(
|
|
58
|
+
() => virtualize ? typeof virtualize === "object" ? virtualize : {} : void 0,
|
|
59
|
+
[virtualize]
|
|
60
|
+
);
|
|
61
|
+
const {
|
|
62
|
+
height = 480,
|
|
63
|
+
rowHeight = density === "compact" ? 36 : 44,
|
|
64
|
+
overscan = 8,
|
|
65
|
+
scrollElementRef
|
|
66
|
+
} = virtualization ?? {};
|
|
67
|
+
const [globalFilter, setGlobalFilter] = useState("");
|
|
68
|
+
const tanstackColumns = useMemo(() => {
|
|
69
|
+
if (!columns) return [];
|
|
70
|
+
return columns.map((col) => {
|
|
71
|
+
if ("accessorKey" in col || "accessorFn" in col || "id" in col) {
|
|
72
|
+
return col;
|
|
73
|
+
}
|
|
74
|
+
const legacy = col;
|
|
75
|
+
return {
|
|
76
|
+
id: typeof legacy.accessor === "string" ? legacy.accessor : legacy.header,
|
|
77
|
+
header: legacy.header,
|
|
78
|
+
enableSorting: legacy.enableSorting ?? true,
|
|
79
|
+
accessorFn: (row) => {
|
|
80
|
+
if (legacy.sortValue) {
|
|
81
|
+
return legacy.sortValue(row);
|
|
82
|
+
}
|
|
83
|
+
if (typeof legacy.accessor === "function") {
|
|
84
|
+
const res = legacy.accessor(row);
|
|
85
|
+
if (res && typeof res === "object" && "props" in res) {
|
|
86
|
+
const children = res.props?.children;
|
|
87
|
+
if (typeof children === "string" || typeof children === "number") {
|
|
88
|
+
return children;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return typeof res === "string" || typeof res === "number" ? res : "";
|
|
92
|
+
}
|
|
93
|
+
return row[legacy.accessor];
|
|
94
|
+
},
|
|
95
|
+
cell: (info) => {
|
|
96
|
+
if (typeof legacy.accessor === "function") {
|
|
97
|
+
return legacy.accessor(info.row.original);
|
|
98
|
+
}
|
|
99
|
+
return info.getValue();
|
|
100
|
+
},
|
|
101
|
+
meta: {
|
|
102
|
+
className: legacy.className,
|
|
103
|
+
rowHeader: legacy.rowHeader
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
}, [columns]);
|
|
108
|
+
const paginated = !virtualization && "pageSize" in rest && Boolean(rest.pageSize);
|
|
109
|
+
const requestedPageSize = paginated ? rest.pageSize : 10;
|
|
110
|
+
const [pageIndexState, setPageIndexState] = useState(0);
|
|
111
|
+
const [pageSizeSeen, setPageSizeSeen] = useState(requestedPageSize);
|
|
112
|
+
if (pageSizeSeen !== requestedPageSize) {
|
|
113
|
+
setPageSizeSeen(requestedPageSize);
|
|
114
|
+
setPageIndexState(0);
|
|
115
|
+
}
|
|
116
|
+
const pagination = { pageIndex: pageIndexState, pageSize: requestedPageSize };
|
|
117
|
+
const defaultTable = useReactTable({
|
|
118
|
+
data: data || [],
|
|
119
|
+
columns: tanstackColumns,
|
|
120
|
+
state: {
|
|
121
|
+
sorting,
|
|
122
|
+
globalFilter,
|
|
123
|
+
...paginated ? { pagination } : {}
|
|
124
|
+
},
|
|
125
|
+
onPaginationChange: (updater) => {
|
|
126
|
+
const next = typeof updater === "function" ? updater(pagination) : updater;
|
|
127
|
+
setPageIndexState(next.pageIndex);
|
|
128
|
+
},
|
|
129
|
+
enableSorting: "enableSorting" in rest ? rest.enableSorting !== false : true,
|
|
130
|
+
onSortingChange: setSorting,
|
|
131
|
+
onGlobalFilterChange: setGlobalFilter,
|
|
132
|
+
getCoreRowModel: getCoreRowModel(),
|
|
133
|
+
getSortedRowModel: getSortedRowModel(),
|
|
134
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
135
|
+
getPaginationRowModel: paginated ? getPaginationRowModel() : void 0
|
|
136
|
+
});
|
|
137
|
+
const activeTable = providedTable || defaultTable;
|
|
138
|
+
const rows = activeTable.getRowModel().rows;
|
|
139
|
+
const headerGroups = activeTable.getHeaderGroups();
|
|
140
|
+
const totalRows = activeTable.getRowCount();
|
|
141
|
+
const windowed = Boolean(virtualization) || rows.length < totalRows;
|
|
142
|
+
const { pageIndex, pageSize } = activeTable.getState().pagination;
|
|
143
|
+
const pageOffset = virtualization ? 0 : Math.min(pageIndex * pageSize, Math.max(0, totalRows - rows.length));
|
|
144
|
+
const scrollRef = useRef(null);
|
|
145
|
+
const [scrollMargin, setScrollMargin] = useState(0);
|
|
146
|
+
useLayoutEffect(() => {
|
|
147
|
+
if (!virtualization) return;
|
|
148
|
+
const scrollEl = scrollElementRef?.current ?? scrollRef.current;
|
|
149
|
+
const body = scrollEl?.querySelector('[data-slot="table-body"]');
|
|
150
|
+
if (!scrollEl || !body) return;
|
|
151
|
+
setScrollMargin(
|
|
152
|
+
body.getBoundingClientRect().top - scrollEl.getBoundingClientRect().top + scrollEl.scrollTop
|
|
153
|
+
);
|
|
154
|
+
}, [virtualization, scrollElementRef, rows.length]);
|
|
155
|
+
const virtualizer = useVirtualizer({
|
|
156
|
+
count: virtualization ? rows.length : 0,
|
|
157
|
+
estimateSize: () => rowHeight,
|
|
158
|
+
overscan,
|
|
159
|
+
getScrollElement: () => scrollElementRef?.current ?? scrollRef.current,
|
|
160
|
+
scrollMargin
|
|
161
|
+
});
|
|
162
|
+
const renderRow = (row, position) => {
|
|
163
|
+
const rowKey = keyExtractor ? keyExtractor(row.original, row.index) : row.id;
|
|
164
|
+
return /* @__PURE__ */ jsx(
|
|
165
|
+
TableRow,
|
|
166
|
+
{
|
|
167
|
+
"aria-rowindex": windowed ? headerGroups.length + pageOffset + position + 1 : void 0,
|
|
168
|
+
"data-state": row.getIsSelected() && "selected",
|
|
169
|
+
children: row.getVisibleCells().map((cell) => {
|
|
170
|
+
const meta = cell.column.columnDef.meta;
|
|
171
|
+
const content = flexRender(cell.column.columnDef.cell, cell.getContext());
|
|
172
|
+
return meta?.rowHeader ? /* @__PURE__ */ jsx(TableHead, { scope: "row", className: meta.className, children: content }, cell.id) : /* @__PURE__ */ jsx(TableCell, { className: meta?.className, children: content }, cell.id);
|
|
173
|
+
})
|
|
174
|
+
},
|
|
175
|
+
rowKey
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
const colSpan = activeTable.getAllLeafColumns().length || 1;
|
|
179
|
+
const virtualItems = virtualization ? virtualizer.getVirtualItems() : [];
|
|
180
|
+
const firstItem = virtualItems[0];
|
|
181
|
+
const lastItem = virtualItems[virtualItems.length - 1];
|
|
182
|
+
const topGap = virtualization && firstItem ? firstItem.start - scrollMargin : 0;
|
|
183
|
+
const bottomGap = virtualization && lastItem ? virtualizer.getTotalSize() - lastItem.end : 0;
|
|
184
|
+
const regionLabel = typeof caption === "string" ? caption : void 0;
|
|
185
|
+
const table = /* @__PURE__ */ jsxs(
|
|
186
|
+
Table,
|
|
187
|
+
{
|
|
188
|
+
className,
|
|
189
|
+
label: regionLabel,
|
|
190
|
+
appearance,
|
|
191
|
+
density,
|
|
192
|
+
"aria-rowcount": windowed ? headerGroups.length + totalRows : void 0,
|
|
193
|
+
containerClassName: virtualization && !scrollElementRef ? cn(containerClassName, "border-0 overflow-x-visible") : containerClassName,
|
|
194
|
+
children: [
|
|
195
|
+
caption != null && /* @__PURE__ */ jsx(TableCaption, { children: caption }),
|
|
196
|
+
/* @__PURE__ */ jsx(TableHeader, { children: headerGroups.map((headerGroup, groupIdx) => /* @__PURE__ */ jsx(TableRow, { "aria-rowindex": windowed ? groupIdx + 1 : void 0, children: headerGroup.headers.map((header, headerIdx) => {
|
|
197
|
+
const canSort = !header.isPlaceholder && header.column.getCanSort();
|
|
198
|
+
const isSorted = !header.isPlaceholder && header.column.getIsSorted();
|
|
199
|
+
const meta = header.column.columnDef.meta;
|
|
200
|
+
const ariaSort = isSorted && header.column.getSortIndex() === 0 ? isSorted === "asc" ? "ascending" : "descending" : void 0;
|
|
201
|
+
const next = canSort ? header.column.getNextSortingOrder() : false;
|
|
202
|
+
const hintId = `${uid}-sort-${groupIdx}-${headerIdx}`;
|
|
203
|
+
const label = header.isPlaceholder ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
204
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "[" }),
|
|
205
|
+
/* @__PURE__ */ jsx("span", { children: flexRender(header.column.columnDef.header, header.getContext()) }),
|
|
206
|
+
canSort && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "inline-flex items-center", children: /* @__PURE__ */ jsx(
|
|
207
|
+
NerdIcon,
|
|
208
|
+
{
|
|
209
|
+
name: isSorted === "asc" ? "sort-asc" : isSorted === "desc" ? "sort-desc" : "sort",
|
|
210
|
+
size: "sm",
|
|
211
|
+
accent: isSorted ? "primary" : "muted"
|
|
212
|
+
}
|
|
213
|
+
) }),
|
|
214
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "]" })
|
|
215
|
+
] });
|
|
216
|
+
return /* @__PURE__ */ jsx(
|
|
217
|
+
TableHead,
|
|
218
|
+
{
|
|
219
|
+
colSpan: header.colSpan > 1 ? header.colSpan : void 0,
|
|
220
|
+
scope: header.colSpan > 1 && !header.isPlaceholder ? "colgroup" : void 0,
|
|
221
|
+
"aria-sort": ariaSort,
|
|
222
|
+
className: cn(
|
|
223
|
+
canSort && "relative cursor-pointer select-none hover:bg-surface-base transition-colors",
|
|
224
|
+
meta?.className
|
|
225
|
+
),
|
|
226
|
+
children: canSort ? /* @__PURE__ */ jsxs(
|
|
227
|
+
"button",
|
|
228
|
+
{
|
|
229
|
+
type: "button",
|
|
230
|
+
"aria-describedby": hintId,
|
|
231
|
+
onClick: header.column.getToggleSortingHandler(),
|
|
232
|
+
className: "inline-flex cursor-pointer items-center gap-1.5 uppercase after:absolute after:inset-0",
|
|
233
|
+
children: [
|
|
234
|
+
label,
|
|
235
|
+
/* @__PURE__ */ jsx("span", { id: hintId, hidden: true, children: next ? SORT_HINT[next] : "Remove sort" })
|
|
236
|
+
]
|
|
237
|
+
}
|
|
238
|
+
) : label && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center gap-1.5", children: label })
|
|
239
|
+
},
|
|
240
|
+
header.id
|
|
241
|
+
);
|
|
242
|
+
}) }, headerGroup.id)) }),
|
|
243
|
+
/* @__PURE__ */ jsx(TableBody, { children: rows.length === 0 ? /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsxs(
|
|
244
|
+
TableCell,
|
|
245
|
+
{
|
|
246
|
+
colSpan,
|
|
247
|
+
className: cn(
|
|
248
|
+
density === "compact" ? "px-3 py-4" : "px-4 py-8",
|
|
249
|
+
"text-center text-content-muted font-mono"
|
|
250
|
+
),
|
|
251
|
+
children: [
|
|
252
|
+
"> ",
|
|
253
|
+
emptyText
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
) }) : virtualization ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
257
|
+
topGap > 0 && /* @__PURE__ */ jsx(SpacerRow, { height: topGap, colSpan }),
|
|
258
|
+
virtualItems.map((item) => renderRow(rows[item.index], item.index)),
|
|
259
|
+
bottomGap > 0 && /* @__PURE__ */ jsx(SpacerRow, { height: bottomGap, colSpan })
|
|
260
|
+
] }) : rows.map((row, position) => renderRow(row, position)) })
|
|
261
|
+
]
|
|
262
|
+
}
|
|
263
|
+
);
|
|
264
|
+
const pageCount = activeTable.getPageCount();
|
|
265
|
+
if (paginated && !providedTable && pageCount > 1) {
|
|
266
|
+
return /* @__PURE__ */ jsxs("div", { "data-slot": "datatable-paginated", className: "flex flex-col gap-4", children: [
|
|
267
|
+
table,
|
|
268
|
+
/* @__PURE__ */ jsx(
|
|
269
|
+
Pagination,
|
|
270
|
+
{
|
|
271
|
+
totalPages: pageCount,
|
|
272
|
+
currentPage: pageIndex + 1,
|
|
273
|
+
onPageChange: (page) => activeTable.setPageIndex(page - 1)
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
] });
|
|
277
|
+
}
|
|
278
|
+
if (!virtualization || scrollElementRef) return table;
|
|
279
|
+
return /* @__PURE__ */ jsx(
|
|
280
|
+
"div",
|
|
281
|
+
{
|
|
282
|
+
ref: scrollRef,
|
|
283
|
+
"data-slot": "table-virtual-scroll",
|
|
284
|
+
tabIndex: 0,
|
|
285
|
+
role: "region",
|
|
286
|
+
"aria-label": regionLabel ?? "Table contents",
|
|
287
|
+
className: "relative w-full overflow-y-auto",
|
|
288
|
+
style: scrollBoxStyle(height),
|
|
289
|
+
children: table
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
function SpacerRow({ height, colSpan }) {
|
|
294
|
+
return /* @__PURE__ */ jsx("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx("td", { "aria-hidden": "true", colSpan, style: spacerStyle(height) }) });
|
|
295
|
+
}
|
|
296
|
+
export {
|
|
297
|
+
DataTable
|
|
298
|
+
};
|
|
299
|
+
//# sourceMappingURL=DataTable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/DataTable.tsx"],"sourcesContent":["'use client';\n\nimport {\n flexRender,\n getCoreRowModel,\n getFilteredRowModel,\n getPaginationRowModel,\n getSortedRowModel,\n useReactTable,\n type ColumnDef,\n type SortingState,\n type Table as TanStackTable,\n} from '@tanstack/react-table';\nimport { useVirtualizer } from '@tanstack/react-virtual';\nimport {\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n type RefObject,\n} from 'react';\nimport { cn } from '../lib/recipe';\nimport { NerdIcon } from './NerdIcon';\nimport { Pagination } from './Pagination';\nimport {\n Table,\n TableBody,\n TableCaption,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n type TableAppearance,\n type TableDensity,\n} from './Table';\n\n// Legacy column interface for backwards-compatibility\nexport interface Column<T> {\n header: string;\n accessor: keyof T | ((row: T) => ReactNode);\n className?: string;\n enableSorting?: boolean;\n sortValue?: (row: T) => any;\n /**\n * The column that names each row — a hostname, a branch. Its body cells\n * render as `<th scope=\"row\">`, so a screen reader announces the row by it\n * while moving across. On a TanStack `ColumnDef`, set `meta.rowHeader`.\n */\n rowHeader?: boolean;\n}\n\ninterface DataTableColumnMeta {\n className?: string;\n rowHeader?: boolean;\n}\n\n// What a press on a sort button does, keyed by the column's next order so the\n// description cannot disagree with the click.\nconst SORT_HINT = { asc: 'Sort ascending', desc: 'Sort descending' } as const;\n\n/** Virtualization geometry is runtime arithmetic; hoisted objects (the\n * `ATTACHED_STYLE` convention from CodeBlock) keep it off the inline-style\n * sites the contract counts. */\nconst SPACER_STYLE_BASE = { padding: 0, border: 0 } as const;\nfunction spacerStyle(height: number) {\n return { height, ...SPACER_STYLE_BASE };\n}\nfunction scrollBoxStyle(height: number) {\n return { height };\n}\n\n/**\n * Row virtualization is planned against a fixed row height rather than measured\n * rows: deterministic rendering is a contract here (docs/deterministic-rendering.md),\n * and a measured engine would make the snapshot of a 10k-row table depend on font\n * loading. Multi-line cell content is therefore clipped to `rowHeight`.\n */\nexport interface DataTableVirtualization {\n /**\n * Viewport height, in px, of the scroll box the table builds for itself.\n * Ignored when `scrollElementRef` is provided. Defaults to 480.\n */\n height?: number;\n /** Fixed row height, in px, the virtualizer plans against. Defaults to 44. */\n rowHeight?: number;\n /** Rows rendered beyond each edge of the viewport. Defaults to 8. */\n overscan?: number;\n /**\n * Scroll inside an existing scrollable element instead of building one. When\n * provided, no wrapper is rendered and this element owns both axes — and it\n * must be keyboard-reachable itself (`tabIndex={0}`), or the window cannot be\n * scrolled without a mouse.\n */\n scrollElementRef?: RefObject<HTMLElement | null>;\n}\n\ntype DataTableSharedProps<T> = {\n /**\n * `grid` keeps the framed default, `quiet` removes the full cell grid, and\n * `index` adds a keyline to row headers for catalog-style scanning.\n */\n appearance?: TableAppearance;\n /**\n * `comfortable` preserves the existing cell spacing. `compact` reduces it\n * for reference tables with many rows.\n */\n density?: TableDensity;\n /**\n * `index` is the row's position in `data`, not on screen, so a key built\n * from it follows its row through a sort rather than staying with the slot.\n */\n keyExtractor?: (row: T, index: number) => string | number;\n /**\n * What the table is a table of. Rendered as the `<caption>`, which is the\n * table's accessible name; a string caption also names the scroll regions.\n */\n caption?: ReactNode;\n emptyText?: string;\n className?: string;\n containerClassName?: string;\n /**\n * Window the body to the visible rows so a dataset of thousands renders as\n * many rows as fit, not as many as exist. `true` uses the defaults;\n * mutually exclusive with `pageSize`, which wins nothing — pagination is\n * simply not attached while virtualizing. The table still reports the whole\n * dataset to assistive tech, through `aria-rowcount` and `aria-rowindex`.\n */\n virtualize?: boolean | DataTableVirtualization;\n};\n\nexport type DataTableProps<T> = DataTableSharedProps<T> &\n (\n | {\n /** Pre-configured TanStack table instance */\n table: TanStackTable<T>;\n columns?: never;\n data?: never;\n }\n | {\n table?: never;\n /** Column definitions (either simple Column<T>[] or TanStack ColumnDef<T>[]) */\n columns: Column<T>[] | ColumnDef<T, any>[];\n data: T[];\n /** `false` turns sorting off for every column. Defaults to `true`. */\n enableSorting?: boolean;\n pageSize?: number;\n }\n );\n\n/**\n * A data table on `@tanstack/react-table`, with the semantics TanStack does not\n * supply written here: `scope` on every header, `aria-sort` on the sorted\n * column, a real sort button per sortable header, an optional `caption`, and a\n * true row count when the body is windowed.\n */\nexport function DataTable<T>({\n table: providedTable,\n columns,\n data,\n keyExtractor,\n caption,\n appearance = 'grid',\n density = 'comfortable',\n emptyText = 'No items found.',\n className = '',\n containerClassName = '',\n virtualize,\n ...rest\n}: DataTableProps<T>) {\n const [sorting, setSorting] = useState<SortingState>([]);\n const uid = useId();\n // Memoized (the exhaustive-deps rule's own suggestion): a caller passing a\n // fresh `virtualize={{ ... }}` literal must not churn the measure effect.\n const virtualization = useMemo(\n () => (virtualize ? (typeof virtualize === 'object' ? virtualize : {}) : undefined),\n [virtualize],\n );\n const {\n height = 480,\n rowHeight = density === 'compact' ? 36 : 44,\n overscan = 8,\n scrollElementRef,\n } = virtualization ?? {};\n const [globalFilter, setGlobalFilter] = useState('');\n\n // Normalize columns if legacy format is provided\n const tanstackColumns = useMemo<ColumnDef<T, any>[]>(() => {\n if (!columns) return [];\n\n return columns.map((col: any) => {\n // Check if it's already a TanStack ColumnDef\n if ('accessorKey' in col || 'accessorFn' in col || 'id' in col) {\n return col as ColumnDef<T, any>;\n }\n\n // Convert legacy Column<T> to ColumnDef<T>\n const legacy = col as Column<T>;\n return {\n id: typeof legacy.accessor === 'string' ? legacy.accessor : legacy.header,\n header: legacy.header,\n enableSorting: legacy.enableSorting ?? true,\n accessorFn: (row: T) => {\n if (legacy.sortValue) {\n return legacy.sortValue(row);\n }\n if (typeof legacy.accessor === 'function') {\n const res = legacy.accessor(row);\n if (res && typeof res === 'object' && 'props' in (res as any)) {\n const children = (res as any).props?.children;\n if (typeof children === 'string' || typeof children === 'number') {\n return children;\n }\n }\n return typeof res === 'string' || typeof res === 'number' ? res : '';\n }\n return row[legacy.accessor];\n },\n cell: (info: any) => {\n if (typeof legacy.accessor === 'function') {\n return legacy.accessor(info.row.original);\n }\n return info.getValue() as ReactNode;\n },\n meta: {\n className: legacy.className,\n rowHeader: legacy.rowHeader,\n },\n } as ColumnDef<T, any>;\n });\n }, [columns]);\n\n // `pageSize` used to switch the pagination model on and nothing else, so\n // TanStack's default of 10 applied whatever was asked for (#276). It is read\n // from the prop on every render, not handed to `initialState` once, so a\n // later change is honoured; only the page index is state. A new size starts\n // again at page one rather than stranding the reader past the last page.\n const paginated = !virtualization && 'pageSize' in rest && Boolean(rest.pageSize);\n const requestedPageSize = paginated ? (rest as { pageSize: number }).pageSize : 10;\n const [pageIndexState, setPageIndexState] = useState(0);\n const [pageSizeSeen, setPageSizeSeen] = useState(requestedPageSize);\n if (pageSizeSeen !== requestedPageSize) {\n setPageSizeSeen(requestedPageSize);\n setPageIndexState(0);\n }\n const pagination = { pageIndex: pageIndexState, pageSize: requestedPageSize };\n const defaultTable = useReactTable<T>({\n data: data || [],\n columns: tanstackColumns,\n state: {\n sorting,\n globalFilter,\n ...(paginated ? { pagination } : {}),\n },\n onPaginationChange: (updater) => {\n const next = typeof updater === 'function' ? updater(pagination) : updater;\n setPageIndexState(next.pageIndex);\n },\n enableSorting: 'enableSorting' in rest ? rest.enableSorting !== false : true,\n onSortingChange: setSorting,\n onGlobalFilterChange: setGlobalFilter,\n getCoreRowModel: getCoreRowModel(),\n getSortedRowModel: getSortedRowModel(),\n getFilteredRowModel: getFilteredRowModel(),\n getPaginationRowModel: paginated ? getPaginationRowModel() : undefined,\n });\n\n const activeTable = providedTable || defaultTable;\n const rows = activeTable.getRowModel().rows;\n const headerGroups = activeTable.getHeaderGroups();\n\n // A windowed body — virtualized, or one page of a paginated model — holds\n // fewer rows than the data, and a screen reader counts what it is given. So\n // the table states the real count and each row its real position, all from\n // one total. aria-rowcount counts every row, header rows included.\n const totalRows = activeTable.getRowCount();\n const windowed = Boolean(virtualization) || rows.length < totalRows;\n const { pageIndex, pageSize } = activeTable.getState().pagination;\n const pageOffset = virtualization\n ? 0\n : Math.min(pageIndex * pageSize, Math.max(0, totalRows - rows.length));\n\n const scrollRef = useRef<HTMLDivElement>(null);\n // The virtualizer measures from the top of the scroll element, which on a\n // table is the header's top-left — but rows begin below it. Measuring the\n // body's offset once (the header's height is static by recipe) removes the\n // scrollMargin error that would otherwise offset every window by ~46px.\n const [scrollMargin, setScrollMargin] = useState(0);\n useLayoutEffect(() => {\n if (!virtualization) return;\n const scrollEl = scrollElementRef?.current ?? scrollRef.current;\n const body = scrollEl?.querySelector('[data-slot=\"table-body\"]');\n if (!scrollEl || !body) return;\n setScrollMargin(\n body.getBoundingClientRect().top -\n scrollEl.getBoundingClientRect().top +\n scrollEl.scrollTop,\n );\n }, [virtualization, scrollElementRef, rows.length]);\n\n const virtualizer = useVirtualizer({\n count: virtualization ? rows.length : 0,\n estimateSize: () => rowHeight,\n overscan,\n getScrollElement: () => scrollElementRef?.current ?? scrollRef.current,\n scrollMargin,\n });\n\n const renderRow = (row: (typeof rows)[number], position: number) => {\n // `row.index`, not `position`: a key built from the on-screen position\n // would stay with the slot while the rows move through it on a sort.\n const rowKey = keyExtractor ? keyExtractor(row.original, row.index) : row.id;\n\n return (\n <TableRow\n key={rowKey}\n aria-rowindex={windowed ? headerGroups.length + pageOffset + position + 1 : undefined}\n data-state={row.getIsSelected() && 'selected'}\n >\n {row.getVisibleCells().map((cell) => {\n const meta = cell.column.columnDef.meta as DataTableColumnMeta | undefined;\n const content = flexRender(cell.column.columnDef.cell, cell.getContext());\n return meta?.rowHeader ? (\n <TableHead key={cell.id} scope=\"row\" className={meta.className}>\n {content}\n </TableHead>\n ) : (\n <TableCell key={cell.id} className={meta?.className}>\n {content}\n </TableCell>\n );\n })}\n </TableRow>\n );\n };\n\n const colSpan = activeTable.getAllLeafColumns().length || 1;\n\n const virtualItems = virtualization ? virtualizer.getVirtualItems() : [];\n // `getVirtualItems` positions are relative to the scroll element and already\n // include `scrollMargin`; the gaps are what remains of the total column\n // above the first rendered row and below the last.\n const firstItem = virtualItems[0];\n const lastItem = virtualItems[virtualItems.length - 1];\n const topGap = virtualization && firstItem ? firstItem.start - scrollMargin : 0;\n const bottomGap =\n virtualization && lastItem\n ? virtualizer.getTotalSize() - lastItem.end\n : 0;\n\n const regionLabel = typeof caption === 'string' ? caption : undefined;\n\n const table = (\n <Table\n className={className}\n label={regionLabel}\n appearance={appearance}\n density={density}\n aria-rowcount={windowed ? headerGroups.length + totalRows : undefined}\n containerClassName={\n virtualization && !scrollElementRef\n ? cn(containerClassName, 'border-0 overflow-x-visible')\n : containerClassName\n }\n >\n {caption != null && <TableCaption>{caption}</TableCaption>}\n <TableHeader>\n {headerGroups.map((headerGroup, groupIdx) => (\n <TableRow key={headerGroup.id} aria-rowindex={windowed ? groupIdx + 1 : undefined}>\n {headerGroup.headers.map((header, headerIdx) => {\n const canSort = !header.isPlaceholder && header.column.getCanSort();\n const isSorted = !header.isPlaceholder && header.column.getIsSorted();\n const meta = header.column.columnDef.meta as DataTableColumnMeta | undefined;\n // One header carries aria-sort at a time (ARIA 1.2); under a\n // multi-column sort it is the primary key's.\n const ariaSort =\n isSorted && header.column.getSortIndex() === 0\n ? isSorted === 'asc'\n ? 'ascending'\n : 'descending'\n : undefined;\n const next = canSort ? header.column.getNextSortingOrder() : false;\n const hintId = `${uid}-sort-${groupIdx}-${headerIdx}`;\n // The icon is decorative: the state it draws is aria-sort's.\n const label = header.isPlaceholder ? null : (\n <>\n <span aria-hidden=\"true\">[</span>\n <span>{flexRender(header.column.columnDef.header, header.getContext())}</span>\n {canSort && (\n <span aria-hidden=\"true\" className=\"inline-flex items-center\">\n <NerdIcon\n name={isSorted === 'asc' ? 'sort-asc' : isSorted === 'desc' ? 'sort-desc' : 'sort'}\n size=\"sm\"\n accent={isSorted ? 'primary' : 'muted'}\n />\n </span>\n )}\n <span aria-hidden=\"true\">]</span>\n </>\n );\n\n // A sortable header holds a native button, so sorting is\n // reachable by Tab and fired by Enter or Space. Its name is the\n // column's; what a press does is its description, which keeps\n // \"sort ascending\" out of the header name read before every\n // cell. `::after` stretches the hit area over the cell, as the\n // old whole-cell click had it.\n return (\n <TableHead\n key={header.id}\n colSpan={header.colSpan > 1 ? header.colSpan : undefined}\n // A group heading spanning several columns heads all of them;\n // `col`, the default, would tie it to the first one only.\n scope={header.colSpan > 1 && !header.isPlaceholder ? 'colgroup' : undefined}\n aria-sort={ariaSort}\n className={cn(\n canSort && 'relative cursor-pointer select-none hover:bg-surface-base transition-colors',\n meta?.className,\n )}\n >\n {canSort ? (\n <button\n type=\"button\"\n aria-describedby={hintId}\n onClick={header.column.getToggleSortingHandler()}\n className=\"inline-flex cursor-pointer items-center gap-1.5 uppercase after:absolute after:inset-0\"\n >\n {label}\n <span id={hintId} hidden>\n {next ? SORT_HINT[next] : 'Remove sort'}\n </span>\n </button>\n ) : (\n label && <span className=\"inline-flex items-center gap-1.5\">{label}</span>\n )}\n </TableHead>\n );\n })}\n </TableRow>\n ))}\n </TableHeader>\n <TableBody>\n {rows.length === 0 ? (\n <TableRow>\n <TableCell\n colSpan={colSpan}\n className={cn(\n density === 'compact' ? 'px-3 py-4' : 'px-4 py-8',\n 'text-center text-content-muted font-mono',\n )}\n >\n > {emptyText}\n </TableCell>\n </TableRow>\n ) : virtualization ? (\n <>\n {topGap > 0 && <SpacerRow height={topGap} colSpan={colSpan} />}\n {virtualItems.map((item) => renderRow(rows[item.index], item.index))}\n {bottomGap > 0 && <SpacerRow height={bottomGap} colSpan={colSpan} />}\n </>\n ) : (\n rows.map((row, position) => renderRow(row, position))\n )}\n </TableBody>\n </Table>\n );\n\n // A paginated body needs a way to the other pages, or every row past the\n // first page is unreachable. It is the system's own `Pagination`, in its\n // callback mode, rather than a second pager written here. Only for the\n // table this component builds: a caller passing `table` owns its state.\n const pageCount = activeTable.getPageCount();\n if (paginated && !providedTable && pageCount > 1) {\n return (\n <div data-slot=\"datatable-paginated\" className=\"flex flex-col gap-4\">\n {table}\n <Pagination\n totalPages={pageCount}\n currentPage={pageIndex + 1}\n onPageChange={(page) => activeTable.setPageIndex(page - 1)}\n />\n </div>\n );\n }\n\n if (!virtualization || scrollElementRef) return table;\n\n return (\n <div\n ref={scrollRef}\n data-slot=\"table-virtual-scroll\"\n tabIndex={0}\n role=\"region\"\n aria-label={regionLabel ?? 'Table contents'}\n className=\"relative w-full overflow-y-auto\"\n style={scrollBoxStyle(height)}\n >\n {table}\n </div>\n );\n}\n\n/**\n * Splits the difference between a windowed tbody and the table's own grid:\n * the row contributes height, contributes nothing to column sizing, and is\n * hidden from assistive tech because the rows it stands in for are absent.\n */\nfunction SpacerRow({ height, colSpan }: { height: number; colSpan: number }) {\n return (\n <tr aria-hidden=\"true\">\n <td aria-hidden=\"true\" colSpan={colSpan} style={spacerStyle(height)} />\n </tr>\n );\n}\n"],"mappings":";;;AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,sBAAsB;AAC/B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,UAAU;AACnB,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAgSK,SA8DI,UA9DJ,KA8DI,YA9DJ;AAxQZ,IAAM,YAAY,EAAE,KAAK,kBAAkB,MAAM,kBAAkB;AAKnE,IAAM,oBAAoB,EAAE,SAAS,GAAG,QAAQ,EAAE;AAClD,SAAS,YAAY,QAAgB;AACnC,SAAO,EAAE,QAAQ,GAAG,kBAAkB;AACxC;AACA,SAAS,eAAe,QAAgB;AACtC,SAAO,EAAE,OAAO;AAClB;AAsFO,SAAS,UAAa;AAAA,EAC3B,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,qBAAqB;AAAA,EACrB;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,QAAM,CAAC,SAAS,UAAU,IAAI,SAAuB,CAAC,CAAC;AACvD,QAAM,MAAM,MAAM;AAGlB,QAAM,iBAAiB;AAAA,IACrB,MAAO,aAAc,OAAO,eAAe,WAAW,aAAa,CAAC,IAAK;AAAA,IACzE,CAAC,UAAU;AAAA,EACb;AACA,QAAM;AAAA,IACJ,SAAS;AAAA,IACT,YAAY,YAAY,YAAY,KAAK;AAAA,IACzC,WAAW;AAAA,IACX;AAAA,EACF,IAAI,kBAAkB,CAAC;AACvB,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,EAAE;AAGnD,QAAM,kBAAkB,QAA6B,MAAM;AACzD,QAAI,CAAC,QAAS,QAAO,CAAC;AAEtB,WAAO,QAAQ,IAAI,CAAC,QAAa;AAE/B,UAAI,iBAAiB,OAAO,gBAAgB,OAAO,QAAQ,KAAK;AAC9D,eAAO;AAAA,MACT;AAGA,YAAM,SAAS;AACf,aAAO;AAAA,QACL,IAAI,OAAO,OAAO,aAAa,WAAW,OAAO,WAAW,OAAO;AAAA,QACnE,QAAQ,OAAO;AAAA,QACf,eAAe,OAAO,iBAAiB;AAAA,QACvC,YAAY,CAAC,QAAW;AACtB,cAAI,OAAO,WAAW;AACpB,mBAAO,OAAO,UAAU,GAAG;AAAA,UAC7B;AACA,cAAI,OAAO,OAAO,aAAa,YAAY;AACzC,kBAAM,MAAM,OAAO,SAAS,GAAG;AAC/B,gBAAI,OAAO,OAAO,QAAQ,YAAY,WAAY,KAAa;AAC7D,oBAAM,WAAY,IAAY,OAAO;AACrC,kBAAI,OAAO,aAAa,YAAY,OAAO,aAAa,UAAU;AAChE,uBAAO;AAAA,cACT;AAAA,YACF;AACA,mBAAO,OAAO,QAAQ,YAAY,OAAO,QAAQ,WAAW,MAAM;AAAA,UACpE;AACA,iBAAO,IAAI,OAAO,QAAQ;AAAA,QAC5B;AAAA,QACA,MAAM,CAAC,SAAc;AACnB,cAAI,OAAO,OAAO,aAAa,YAAY;AACzC,mBAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,UAC1C;AACA,iBAAO,KAAK,SAAS;AAAA,QACvB;AAAA,QACA,MAAM;AAAA,UACJ,WAAW,OAAO;AAAA,UAClB,WAAW,OAAO;AAAA,QACpB;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,OAAO,CAAC;AAOZ,QAAM,YAAY,CAAC,kBAAkB,cAAc,QAAQ,QAAQ,KAAK,QAAQ;AAChF,QAAM,oBAAoB,YAAa,KAA8B,WAAW;AAChF,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAS,CAAC;AACtD,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,iBAAiB;AAClE,MAAI,iBAAiB,mBAAmB;AACtC,oBAAgB,iBAAiB;AACjC,sBAAkB,CAAC;AAAA,EACrB;AACA,QAAM,aAAa,EAAE,WAAW,gBAAgB,UAAU,kBAAkB;AAC5E,QAAM,eAAe,cAAiB;AAAA,IACpC,MAAM,QAAQ,CAAC;AAAA,IACf,SAAS;AAAA,IACT,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,GAAI,YAAY,EAAE,WAAW,IAAI,CAAC;AAAA,IACpC;AAAA,IACA,oBAAoB,CAAC,YAAY;AAC/B,YAAM,OAAO,OAAO,YAAY,aAAa,QAAQ,UAAU,IAAI;AACnE,wBAAkB,KAAK,SAAS;AAAA,IAClC;AAAA,IACA,eAAe,mBAAmB,OAAO,KAAK,kBAAkB,QAAQ;AAAA,IACxE,iBAAiB;AAAA,IACjB,sBAAsB;AAAA,IACtB,iBAAiB,gBAAgB;AAAA,IACjC,mBAAmB,kBAAkB;AAAA,IACrC,qBAAqB,oBAAoB;AAAA,IACzC,uBAAuB,YAAY,sBAAsB,IAAI;AAAA,EAC/D,CAAC;AAED,QAAM,cAAc,iBAAiB;AACrC,QAAM,OAAO,YAAY,YAAY,EAAE;AACvC,QAAM,eAAe,YAAY,gBAAgB;AAMjD,QAAM,YAAY,YAAY,YAAY;AAC1C,QAAM,WAAW,QAAQ,cAAc,KAAK,KAAK,SAAS;AAC1D,QAAM,EAAE,WAAW,SAAS,IAAI,YAAY,SAAS,EAAE;AACvD,QAAM,aAAa,iBACf,IACA,KAAK,IAAI,YAAY,UAAU,KAAK,IAAI,GAAG,YAAY,KAAK,MAAM,CAAC;AAEvE,QAAM,YAAY,OAAuB,IAAI;AAK7C,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,CAAC;AAClD,kBAAgB,MAAM;AACpB,QAAI,CAAC,eAAgB;AACrB,UAAM,WAAW,kBAAkB,WAAW,UAAU;AACxD,UAAM,OAAO,UAAU,cAAc,0BAA0B;AAC/D,QAAI,CAAC,YAAY,CAAC,KAAM;AACxB;AAAA,MACE,KAAK,sBAAsB,EAAE,MAC3B,SAAS,sBAAsB,EAAE,MACjC,SAAS;AAAA,IACb;AAAA,EACF,GAAG,CAAC,gBAAgB,kBAAkB,KAAK,MAAM,CAAC;AAElD,QAAM,cAAc,eAAe;AAAA,IACjC,OAAO,iBAAiB,KAAK,SAAS;AAAA,IACtC,cAAc,MAAM;AAAA,IACpB;AAAA,IACA,kBAAkB,MAAM,kBAAkB,WAAW,UAAU;AAAA,IAC/D;AAAA,EACF,CAAC;AAED,QAAM,YAAY,CAAC,KAA4B,aAAqB;AAGlE,UAAM,SAAS,eAAe,aAAa,IAAI,UAAU,IAAI,KAAK,IAAI,IAAI;AAE1E,WACE;AAAA,MAAC;AAAA;AAAA,QAEC,iBAAe,WAAW,aAAa,SAAS,aAAa,WAAW,IAAI;AAAA,QAC5E,cAAY,IAAI,cAAc,KAAK;AAAA,QAElC,cAAI,gBAAgB,EAAE,IAAI,CAAC,SAAS;AACnC,gBAAM,OAAO,KAAK,OAAO,UAAU;AACnC,gBAAM,UAAU,WAAW,KAAK,OAAO,UAAU,MAAM,KAAK,WAAW,CAAC;AACxE,iBAAO,MAAM,YACX,oBAAC,aAAwB,OAAM,OAAM,WAAW,KAAK,WAClD,qBADa,KAAK,EAErB,IAEA,oBAAC,aAAwB,WAAW,MAAM,WACvC,qBADa,KAAK,EAErB;AAAA,QAEJ,CAAC;AAAA;AAAA,MAhBI;AAAA,IAiBP;AAAA,EAEJ;AAEA,QAAM,UAAU,YAAY,kBAAkB,EAAE,UAAU;AAE1D,QAAM,eAAe,iBAAiB,YAAY,gBAAgB,IAAI,CAAC;AAIvE,QAAM,YAAY,aAAa,CAAC;AAChC,QAAM,WAAW,aAAa,aAAa,SAAS,CAAC;AACrD,QAAM,SAAS,kBAAkB,YAAY,UAAU,QAAQ,eAAe;AAC9E,QAAM,YACJ,kBAAkB,WACd,YAAY,aAAa,IAAI,SAAS,MACtC;AAEN,QAAM,cAAc,OAAO,YAAY,WAAW,UAAU;AAE5D,QAAM,QACJ;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA,iBAAe,WAAW,aAAa,SAAS,YAAY;AAAA,MAC5D,oBACE,kBAAkB,CAAC,mBACf,GAAG,oBAAoB,6BAA6B,IACpD;AAAA,MAGL;AAAA,mBAAW,QAAQ,oBAAC,gBAAc,mBAAQ;AAAA,QAC3C,oBAAC,eACE,uBAAa,IAAI,CAAC,aAAa,aAC9B,oBAAC,YAA8B,iBAAe,WAAW,WAAW,IAAI,QACrE,sBAAY,QAAQ,IAAI,CAAC,QAAQ,cAAc;AAC9C,gBAAM,UAAU,CAAC,OAAO,iBAAiB,OAAO,OAAO,WAAW;AAClE,gBAAM,WAAW,CAAC,OAAO,iBAAiB,OAAO,OAAO,YAAY;AACpE,gBAAM,OAAO,OAAO,OAAO,UAAU;AAGrC,gBAAM,WACJ,YAAY,OAAO,OAAO,aAAa,MAAM,IACzC,aAAa,QACX,cACA,eACF;AACN,gBAAM,OAAO,UAAU,OAAO,OAAO,oBAAoB,IAAI;AAC7D,gBAAM,SAAS,GAAG,GAAG,SAAS,QAAQ,IAAI,SAAS;AAEnD,gBAAM,QAAQ,OAAO,gBAAgB,OACnC,iCACE;AAAA,gCAAC,UAAK,eAAY,QAAO,eAAC;AAAA,YAC1B,oBAAC,UAAM,qBAAW,OAAO,OAAO,UAAU,QAAQ,OAAO,WAAW,CAAC,GAAE;AAAA,YACtE,WACC,oBAAC,UAAK,eAAY,QAAO,WAAU,4BACjC;AAAA,cAAC;AAAA;AAAA,gBACC,MAAM,aAAa,QAAQ,aAAa,aAAa,SAAS,cAAc;AAAA,gBAC5E,MAAK;AAAA,gBACL,QAAQ,WAAW,YAAY;AAAA;AAAA,YACjC,GACF;AAAA,YAEF,oBAAC,UAAK,eAAY,QAAO,eAAC;AAAA,aAC5B;AASF,iBACE;AAAA,YAAC;AAAA;AAAA,cAEC,SAAS,OAAO,UAAU,IAAI,OAAO,UAAU;AAAA,cAG/C,OAAO,OAAO,UAAU,KAAK,CAAC,OAAO,gBAAgB,aAAa;AAAA,cAClE,aAAW;AAAA,cACX,WAAW;AAAA,gBACT,WAAW;AAAA,gBACX,MAAM;AAAA,cACR;AAAA,cAEC,oBACC;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,oBAAkB;AAAA,kBAClB,SAAS,OAAO,OAAO,wBAAwB;AAAA,kBAC/C,WAAU;AAAA,kBAET;AAAA;AAAA,oBACD,oBAAC,UAAK,IAAI,QAAQ,QAAM,MACrB,iBAAO,UAAU,IAAI,IAAI,eAC5B;AAAA;AAAA;AAAA,cACF,IAEA,SAAS,oBAAC,UAAK,WAAU,oCAAoC,iBAAM;AAAA;AAAA,YAxBhE,OAAO;AAAA,UA0Bd;AAAA,QAEJ,CAAC,KArEY,YAAY,EAsE3B,CACD,GACH;AAAA,QACA,oBAAC,aACE,eAAK,WAAW,IACf,oBAAC,YACC;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,WAAW;AAAA,cACT,YAAY,YAAY,cAAc;AAAA,cACtC;AAAA,YACF;AAAA,YACD;AAAA;AAAA,cACO;AAAA;AAAA;AAAA,QACR,GACF,IACE,iBACF,iCACG;AAAA,mBAAS,KAAK,oBAAC,aAAU,QAAQ,QAAQ,SAAkB;AAAA,UAC3D,aAAa,IAAI,CAAC,SAAS,UAAU,KAAK,KAAK,KAAK,GAAG,KAAK,KAAK,CAAC;AAAA,UAClE,YAAY,KAAK,oBAAC,aAAU,QAAQ,WAAW,SAAkB;AAAA,WACpE,IAEA,KAAK,IAAI,CAAC,KAAK,aAAa,UAAU,KAAK,QAAQ,CAAC,GAExD;AAAA;AAAA;AAAA,EACF;AAOF,QAAM,YAAY,YAAY,aAAa;AAC3C,MAAI,aAAa,CAAC,iBAAiB,YAAY,GAAG;AAChD,WACE,qBAAC,SAAI,aAAU,uBAAsB,WAAU,uBAC5C;AAAA;AAAA,MACD;AAAA,QAAC;AAAA;AAAA,UACC,YAAY;AAAA,UACZ,aAAa,YAAY;AAAA,UACzB,cAAc,CAAC,SAAS,YAAY,aAAa,OAAO,CAAC;AAAA;AAAA,MAC3D;AAAA,OACF;AAAA,EAEJ;AAEA,MAAI,CAAC,kBAAkB,iBAAkB,QAAO;AAEhD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,aAAU;AAAA,MACV,UAAU;AAAA,MACV,MAAK;AAAA,MACL,cAAY,eAAe;AAAA,MAC3B,WAAU;AAAA,MACV,OAAO,eAAe,MAAM;AAAA,MAE3B;AAAA;AAAA,EACH;AAEJ;AAOA,SAAS,UAAU,EAAE,QAAQ,QAAQ,GAAwC;AAC3E,SACE,oBAAC,QAAG,eAAY,QACd,8BAAC,QAAG,eAAY,QAAO,SAAkB,OAAO,YAAY,MAAM,GAAG,GACvE;AAEJ;","names":[]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/Divider.tsx
|
|
4
|
+
import { LEVELS, DEFAULT_LEVEL } from "../theme/levels.js";
|
|
5
|
+
import { useOptionalTheme } from "./ThemeProvider.js";
|
|
6
|
+
import { cn } from "../lib/recipe.js";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
var DIVIDER_PATTERNS = {
|
|
9
|
+
dark: "//====================================================//",
|
|
10
|
+
light: "\xB7 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \u2500 \xB7"
|
|
11
|
+
};
|
|
12
|
+
var VARIANT_POLARITY = {
|
|
13
|
+
terminal: "dark",
|
|
14
|
+
pencil: "light"
|
|
15
|
+
};
|
|
16
|
+
var Divider = ({
|
|
17
|
+
variant = "auto",
|
|
18
|
+
pattern,
|
|
19
|
+
className = "",
|
|
20
|
+
...props
|
|
21
|
+
}) => {
|
|
22
|
+
const theme = useOptionalTheme();
|
|
23
|
+
const polarity = variant === "auto" ? theme?.polarity ?? LEVELS[DEFAULT_LEVEL].polarity : VARIANT_POLARITY[variant];
|
|
24
|
+
return /* @__PURE__ */ jsx(
|
|
25
|
+
"div",
|
|
26
|
+
{
|
|
27
|
+
"data-slot": "divider",
|
|
28
|
+
className: cn(
|
|
29
|
+
"font-mono text-accent-primary tracking-[0.2em] select-none my-6 overflow-hidden whitespace-nowrap",
|
|
30
|
+
className
|
|
31
|
+
),
|
|
32
|
+
"aria-hidden": "true",
|
|
33
|
+
...props,
|
|
34
|
+
children: pattern ?? DIVIDER_PATTERNS[polarity]
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
export {
|
|
39
|
+
DIVIDER_PATTERNS,
|
|
40
|
+
Divider
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=Divider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/Divider.tsx"],"sourcesContent":["'use client';\n\nimport type React from 'react';\nimport { LEVELS, DEFAULT_LEVEL } from '../theme/levels';\nimport type { Polarity } from '../theme/levels';\nimport { useOptionalTheme } from './ThemeProvider';\nimport { cn } from '../lib/recipe';\n\n/**\n * A rule drawn in characters rather than pixels.\n *\n * The glyph is part of the design, and it is not the same glyph on every level.\n * The dark end of the ladder leans on terminal devices — scanlines, `>`\n * prompts, `//====//` rules. The light end is paper, where the equivalent\n * device is a hand-ruled pencil dash. Same component, same role, different\n * mark.\n *\n * ## Why this exists as its own component\n *\n * That behaviour used to live in the *consumer's* stylesheet, as\n * `.sketch .ascii-divider::after { content: \"· ─ ─ ·\" }` in the blog, overriding\n * a `::after` in the design system. Three things were wrong with that:\n *\n * - The design intent — \"this rule is drawn differently on paper\" — was\n * stated in a downstream repo, so the system did not actually own its own\n * divider.\n * - It keyed off `.sketch`, a level name that no longer exists. Anything\n * built that way silently stops matching when the ladder changes, because\n * a selector that matches nothing fails quietly.\n * - It forced the design system to keep a bare `.ascii-divider` class as a\n * styling hook purely so a pseudo-element had something to attach to,\n * which was the last hole in the styling-lives-in-TSX rule.\n *\n * Encoding it here closes all three: the mapping is a `Record<Polarity, …>`, so\n * it is exhaustive by type, and the glyph is real text rather than generated\n * content — which means it is selectable, searchable, and readable by a screen\n * reader if it ever needed to be (it is `aria-hidden`, being decoration).\n */\n\n/** Which mark to draw. `auto` follows the current level's polarity. */\nexport type DividerVariant = 'auto' | 'terminal' | 'pencil';\n\n/**\n * The mark each polarity draws.\n *\n * A `Record<Polarity, …>` rather than a conditional, for the same reason the\n * rest of the ladder is: a third polarity would be a compile error here rather\n * than a silent fallthrough.\n */\nexport const DIVIDER_PATTERNS: Readonly<Record<Polarity, string>> = {\n dark: '//====================================================//',\n light: '· ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ·',\n};\n\nconst VARIANT_POLARITY: Readonly<Record<Exclude<DividerVariant, 'auto'>, Polarity>> = {\n terminal: 'dark',\n pencil: 'light',\n};\n\nexport interface DividerProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Which mark to draw. Defaults to `auto`, which follows the level's declared\n * polarity — terminal on `midnight`/`dim`, pencil on `bright`/`white`.\n */\n variant?: DividerVariant;\n /** An explicit mark, overriding both the variant and the level. */\n pattern?: string;\n}\n\nexport const Divider: React.FC<DividerProps> = ({\n variant = 'auto',\n pattern,\n className = '',\n ...props\n}) => {\n // Optional on purpose: a divider must still draw something sensible in a\n // story with no decorator, or under a consumer that sets `data-theme` on the\n // document and never mounts a provider.\n const theme = useOptionalTheme();\n const polarity =\n variant === 'auto'\n ? (theme?.polarity ?? LEVELS[DEFAULT_LEVEL].polarity)\n : VARIANT_POLARITY[variant];\n\n return (\n <div data-slot=\"divider\"\n className={cn(\n 'font-mono text-accent-primary tracking-[0.2em] select-none my-6 overflow-hidden whitespace-nowrap',\n className\n )}\n // Clipped, not truncated: an ellipsis on a rule reads as a broken string.\n // The mark is decoration, so losing its tail to a narrow container is\n // correct and `aria-hidden` keeps it out of the accessibility tree.\n aria-hidden=\"true\"\n {...props}\n >\n {pattern ?? DIVIDER_PATTERNS[polarity]}\n </div>\n );\n};\n"],"mappings":";;;AAGA,SAAS,QAAQ,qBAAqB;AAEtC,SAAS,wBAAwB;AACjC,SAAS,UAAU;AA+Ef;AApCG,IAAM,mBAAuD;AAAA,EAClE,MAAM;AAAA,EACN,OAAO;AACT;AAEA,IAAM,mBAAgF;AAAA,EACpF,UAAU;AAAA,EACV,QAAQ;AACV;AAYO,IAAM,UAAkC,CAAC;AAAA,EAC9C,UAAU;AAAA,EACV;AAAA,EACA,YAAY;AAAA,EACZ,GAAG;AACL,MAAM;AAIJ,QAAM,QAAQ,iBAAiB;AAC/B,QAAM,WACJ,YAAY,SACP,OAAO,YAAY,OAAO,aAAa,EAAE,WAC1C,iBAAiB,OAAO;AAE9B,SACE;AAAA,IAAC;AAAA;AAAA,MAAI,aAAU;AAAA,MACb,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MAIA,eAAY;AAAA,MACX,GAAG;AAAA,MAEH,qBAAW,iBAAiB,QAAQ;AAAA;AAAA,EACvC;AAEJ;","names":[]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/Drawer.tsx
|
|
4
|
+
import { forwardRef } from "react";
|
|
5
|
+
import { Dialog } from "@base-ui/react/dialog";
|
|
6
|
+
import { cn } from "../lib/recipe.js";
|
|
7
|
+
import { usePortalThemeAttribute } from "./portalTheme.js";
|
|
8
|
+
import { dialogSurface } from "./dialogSurface.js";
|
|
9
|
+
import { usePopupRef } from "./dialogPopupRef.js";
|
|
10
|
+
import { Button } from "./Button.js";
|
|
11
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
+
var Drawer = forwardRef(function Drawer2({
|
|
13
|
+
isOpen,
|
|
14
|
+
onClose,
|
|
15
|
+
title,
|
|
16
|
+
children,
|
|
17
|
+
placement = "right",
|
|
18
|
+
footer,
|
|
19
|
+
closeOnBackdropClick = true,
|
|
20
|
+
className,
|
|
21
|
+
...props
|
|
22
|
+
}, ref) {
|
|
23
|
+
const slots = dialogSurface({ placement });
|
|
24
|
+
const [popup, attachPopup] = usePopupRef(ref);
|
|
25
|
+
const portalTheme = usePortalThemeAttribute();
|
|
26
|
+
return /* @__PURE__ */ jsx(
|
|
27
|
+
Dialog.Root,
|
|
28
|
+
{
|
|
29
|
+
open: isOpen,
|
|
30
|
+
onOpenChange: (open) => {
|
|
31
|
+
if (!open) onClose();
|
|
32
|
+
},
|
|
33
|
+
disablePointerDismissal: !closeOnBackdropClick,
|
|
34
|
+
children: /* @__PURE__ */ jsxs(Dialog.Portal, { ...portalTheme, children: [
|
|
35
|
+
/* @__PURE__ */ jsx(Dialog.Backdrop, { "data-slot": "drawer-backdrop", className: slots.backdrop() }),
|
|
36
|
+
/* @__PURE__ */ jsx(Dialog.Viewport, { "data-slot": "drawer-viewport", className: slots.viewport(), children: /* @__PURE__ */ jsxs(
|
|
37
|
+
Dialog.Popup,
|
|
38
|
+
{
|
|
39
|
+
ref: attachPopup,
|
|
40
|
+
initialFocus: () => popup.current,
|
|
41
|
+
"data-slot": "drawer",
|
|
42
|
+
"data-placement": placement,
|
|
43
|
+
className: cn(slots.popup(), className),
|
|
44
|
+
...props,
|
|
45
|
+
children: [
|
|
46
|
+
/* @__PURE__ */ jsxs("div", { "data-slot": "drawer-header", className: slots.header(), children: [
|
|
47
|
+
/* @__PURE__ */ jsx(
|
|
48
|
+
Dialog.Title,
|
|
49
|
+
{
|
|
50
|
+
"data-slot": "drawer-title",
|
|
51
|
+
className: slots.title(),
|
|
52
|
+
render: /* @__PURE__ */ jsxs("h3", { children: [
|
|
53
|
+
"[ ",
|
|
54
|
+
title,
|
|
55
|
+
" ]"
|
|
56
|
+
] })
|
|
57
|
+
}
|
|
58
|
+
),
|
|
59
|
+
/* @__PURE__ */ jsx(
|
|
60
|
+
Dialog.Close,
|
|
61
|
+
{
|
|
62
|
+
"data-slot": "drawer-close",
|
|
63
|
+
className: slots.close(),
|
|
64
|
+
"aria-label": "Close drawer",
|
|
65
|
+
children: "\xD7"
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
] }),
|
|
69
|
+
/* @__PURE__ */ jsx("div", { "data-slot": "drawer-body", className: slots.body(), children }),
|
|
70
|
+
/* @__PURE__ */ jsx("div", { "data-slot": "drawer-footer", className: slots.footer(), children: footer || /* @__PURE__ */ jsx(Button, { onClick: onClose, variant: "tertiary", bracketed: true, size: "sm", children: "CLOSE" }) })
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
) })
|
|
74
|
+
] })
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
export {
|
|
79
|
+
Drawer
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=Drawer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/Drawer.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef } from 'react';\nimport type { HTMLAttributes, ReactNode } from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { cn } from '../lib/recipe';\nimport { usePortalThemeAttribute } from './portalTheme';\nimport { dialogSurface } from './dialogSurface';\nimport { usePopupRef } from './dialogPopupRef';\nimport { Button } from './Button';\n\n/**\n * Which edge the drawer is anchored to.\n *\n * Two values, and the omission is deliberate. #241 asks for `left` and\n * `right` only: nothing in the roadmap — mobile navigation, the responsive\n * admin sidebar, filter and settings panels — anchors to `top` or `bottom`,\n * and an edge no consumer asks for is still surface that has to be themed,\n * screenshotted, documented and kept working. Adding one later is additive;\n * removing one is a breaking change.\n *\n * A plain string union rather than anything Base UI or the recipe engine\n * names, so neither library reaches the published `.d.ts`.\n */\nexport type DrawerPlacement = 'left' | 'right';\n\nexport interface DrawerProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'children' | 'className'> {\n /** Whether the drawer is open. Controlled, as `Modal` is. */\n isOpen: boolean;\n /**\n * Called on every dismissal — Escape, the backdrop, the close control, and\n * the default footer button. The caller owns the state; this only reports.\n */\n onClose: () => void;\n /**\n * The drawer's accessible name, rendered in the header. Required for the\n * same reason `Modal`'s is: an off-canvas surface a screen reader announces\n * as \"dialog\" and nothing else gives no clue what slid in.\n */\n title: string;\n /** The panel's content — a nav list, a filter form, a settings group. */\n children: ReactNode;\n /**\n * Which edge to anchor to. Defaults to `right`, the placement a panel\n * raised *from* something takes — a filter or settings panel next to the\n * control that opened it. Site navigation names `left` explicitly.\n */\n placement?: DrawerPlacement;\n /** Replaces the default close button in the footer. */\n footer?: ReactNode;\n /**\n * Close when the backdrop is clicked. On for the usual case; turn it off\n * for a drawer holding unsaved input, where a stray click should not\n * discard it.\n */\n closeOnBackdropClick?: boolean;\n /**\n * Merged into the panel's classes rather than appended, so a caller's width\n * genuinely wins over the default `max-w-sm`.\n */\n className?: string;\n}\n\n/**\n * An edge-anchored dialog — the off-canvas primitive.\n *\n * A drawer is a dialog with a different position and a different transition,\n * and this component is written to make that literally true. It is\n * `@base-ui/react/dialog`, the same primitive `Modal` is built on, wearing\n * `dialogSurface`'s `placement` variant. Nothing about stacking, dismissal,\n * focus or scroll locking is implemented here, because all four already exist\n * one layer down and a second implementation of any of them would be a second\n * answer to a question this system has already answered.\n *\n * ## What it inherits, and why that is the whole point\n *\n * - **The dialog stack.** A drawer over a `Modal`, or an `AlertDialog` over a\n * drawer, resolves through Base UI's stack: Escape closes the topmost\n * surface only. That invariant arrived with #162 and is asserted again in\n * `Drawer.test.tsx` — not because it could plausibly differ, but because\n * the whole overlay set now depends on it and a test is what keeps it true\n * when the next surface is added.\n * - **Focus.** It moves into the panel on open, is trapped while open, and\n * returns to the invoker on close. As in `Modal`, `initialFocus` points at\n * the popup rather than its first tabbable child, so a screen reader\n * announces the drawer's own title instead of \"Close drawer, button\".\n * - **The background, hidden from assistive technology.** Base UI marks the\n * portal's siblings `aria-hidden` while the drawer is open.\n * - **The scroll lock.** `overflow-y: hidden` on `body` with\n * `scrollbar-gutter: stable` on `html`, so the page behind does not jump by\n * the width of its scrollbar when the drawer opens.\n *\n * ## What is its own\n *\n * The position and the transition, both from the recipe. The panel is full\n * height against one edge, bordered on its inner side only, and enters by\n * translating in from off-screen via `data-starting-style` /\n * `data-ending-style` over `--duration-quick` and `--ease-brutalist`.\n * `motion-reduce:transition-none` drops the movement entirely: a panel flying\n * across the viewport is precisely the motion `prefers-reduced-motion` asks\n * not to see, and the drawer still opens and closes without it.\n *\n * ## Why it is not `Modal` with a prop\n *\n * It nearly is, and that was considered. The difference is the *shape of the\n * API a caller wants*: a drawer is sized by its edge and its width, a modal by\n * its width alone; a drawer's `placement` is meaningless on a modal, and a\n * modal's centring is meaningless on a drawer. Keeping them separate leaves\n * both prop lists honest while the surface underneath stays single — which is\n * the same trade `AlertDialog` made against `Modal`, for the same reason.\n *\n * @example\n * ```tsx\n * const [open, setOpen] = useState(false);\n *\n * <Button onClick={() => setOpen(true)}>MENU</Button>\n * <Drawer isOpen={open} onClose={() => setOpen(false)} placement=\"left\" title=\"Navigation\">\n * <nav>…</nav>\n * </Drawer>\n * ```\n */\nexport const Drawer = forwardRef<HTMLDivElement, DrawerProps>(function Drawer(\n {\n isOpen,\n onClose,\n title,\n children,\n placement = 'right',\n footer,\n closeOnBackdropClick = true,\n className,\n ...props\n },\n ref,\n) {\n const slots = dialogSurface({ placement });\n const [popup, attachPopup] = usePopupRef(ref);\n\n const portalTheme = usePortalThemeAttribute();\n return (\n <Dialog.Root\n open={isOpen}\n onOpenChange={(open) => {\n if (!open) onClose();\n }}\n // Base UI's own name for \"a click outside does not dismiss this\",\n // inverted because the published API asks the question the other way\n // round — the same inversion `Modal` makes, and for the same reason: the\n // published API is the one that stays.\n disablePointerDismissal={!closeOnBackdropClick}\n >\n <Dialog.Portal {...portalTheme}>\n <Dialog.Backdrop data-slot=\"drawer-backdrop\" className={slots.backdrop()} />\n <Dialog.Viewport data-slot=\"drawer-viewport\" className={slots.viewport()}>\n <Dialog.Popup\n ref={attachPopup}\n // The function form, not the ref form: Base UI reads a `RefObject`\n // early enough that the popup is sometimes still `null`, and then\n // silently falls back to the first tabbable element. Reading\n // `.current` at focus time is the same intent without the race.\n initialFocus={() => popup.current}\n data-slot=\"drawer\"\n // The placement is on the element as well as in the classes.\n // Classes are a rendering detail that a caller's `className` may\n // legitimately override; this is what the drawer *is*, and it is\n // what a test or a snapshot should read.\n data-placement={placement}\n // `class`, not an appended string: a caller's `max-w-md` would\n // otherwise emit alongside `max-w-sm` and leave CSS source order\n // to pick one.\n className={cn(slots.popup(), className)}\n {...props}\n >\n <div data-slot=\"drawer-header\" className={slots.header()}>\n {/*\n * The heading text lives on the render element rather than on\n * `Title`: Base UI keeps the element's own children, and the\n * a11y lint rule can only see content it can read literally.\n */}\n <Dialog.Title\n data-slot=\"drawer-title\"\n className={slots.title()}\n render={<h3>[ {title} ]</h3>}\n />\n <Dialog.Close\n data-slot=\"drawer-close\"\n className={slots.close()}\n aria-label=\"Close drawer\"\n >\n ×\n </Dialog.Close>\n </div>\n\n <div data-slot=\"drawer-body\" className={slots.body()}>\n {children}\n </div>\n\n <div data-slot=\"drawer-footer\" className={slots.footer()}>\n {footer || (\n <Button onClick={onClose} variant=\"tertiary\" bracketed size=\"sm\">\n CLOSE\n </Button>\n )}\n </div>\n </Dialog.Popup>\n </Dialog.Viewport>\n </Dialog.Portal>\n </Dialog.Root>\n );\n});\n"],"mappings":";;;AAEA,SAAS,kBAAkB;AAE3B,SAAS,cAAc;AACvB,SAAS,UAAU;AACnB,SAAS,+BAA+B;AACxC,SAAS,qBAAqB;AAC9B,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AAgJf,cA8BgB,YA9BhB;AA/BD,IAAM,SAAS,WAAwC,SAASA,QACrE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,uBAAuB;AAAA,EACvB;AAAA,EACA,GAAG;AACL,GACA,KACA;AACA,QAAM,QAAQ,cAAc,EAAE,UAAU,CAAC;AACzC,QAAM,CAAC,OAAO,WAAW,IAAI,YAAY,GAAG;AAE5C,QAAM,cAAc,wBAAwB;AAC5C,SACE;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACC,MAAM;AAAA,MACN,cAAc,CAAC,SAAS;AACtB,YAAI,CAAC,KAAM,SAAQ;AAAA,MACrB;AAAA,MAKA,yBAAyB,CAAC;AAAA,MAE1B,+BAAC,OAAO,QAAP,EAAe,GAAG,aACjB;AAAA,4BAAC,OAAO,UAAP,EAAgB,aAAU,mBAAkB,WAAW,MAAM,SAAS,GAAG;AAAA,QAC1E,oBAAC,OAAO,UAAP,EAAgB,aAAU,mBAAkB,WAAW,MAAM,SAAS,GACrE;AAAA,UAAC,OAAO;AAAA,UAAP;AAAA,YACC,KAAK;AAAA,YAKL,cAAc,MAAM,MAAM;AAAA,YAC1B,aAAU;AAAA,YAKV,kBAAgB;AAAA,YAIhB,WAAW,GAAG,MAAM,MAAM,GAAG,SAAS;AAAA,YACrC,GAAG;AAAA,YAEJ;AAAA,mCAAC,SAAI,aAAU,iBAAgB,WAAW,MAAM,OAAO,GAMrD;AAAA;AAAA,kBAAC,OAAO;AAAA,kBAAP;AAAA,oBACC,aAAU;AAAA,oBACV,WAAW,MAAM,MAAM;AAAA,oBACvB,QAAQ,qBAAC,QAAG;AAAA;AAAA,sBAAG;AAAA,sBAAM;AAAA,uBAAE;AAAA;AAAA,gBACzB;AAAA,gBACA;AAAA,kBAAC,OAAO;AAAA,kBAAP;AAAA,oBACC,aAAU;AAAA,oBACV,WAAW,MAAM,MAAM;AAAA,oBACvB,cAAW;AAAA,oBACZ;AAAA;AAAA,gBAED;AAAA,iBACF;AAAA,cAEA,oBAAC,SAAI,aAAU,eAAc,WAAW,MAAM,KAAK,GAChD,UACH;AAAA,cAEA,oBAAC,SAAI,aAAU,iBAAgB,WAAW,MAAM,OAAO,GACpD,oBACC,oBAAC,UAAO,SAAS,SAAS,SAAQ,YAAW,WAAS,MAAC,MAAK,MAAK,mBAEjE,GAEJ;AAAA;AAAA;AAAA,QACF,GACF;AAAA,SACF;AAAA;AAAA,EACF;AAEJ,CAAC;","names":["Drawer"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/components/EmptyState.tsx
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { recipe } from "../lib/recipe.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
var emptyState = recipe({
|
|
6
|
+
slots: {
|
|
7
|
+
root: "flex flex-col items-center gap-3 border-2 border-dashed border-edge-default bg-surface-raised px-6 py-10 text-center font-mono",
|
|
8
|
+
/** The glyph sits in the muted role: it is the least load-bearing thing here. */
|
|
9
|
+
icon: "flex items-center justify-center text-content-muted",
|
|
10
|
+
title: "font-display text-lg font-bold uppercase tracking-wider text-content-primary",
|
|
11
|
+
/** `max-w-prose` because a description that runs the width of a table is unreadable. */
|
|
12
|
+
description: "max-w-prose font-sans text-sm leading-relaxed text-content-secondary",
|
|
13
|
+
/** A margin rather than the root's gap: the action is a step away from the prose. */
|
|
14
|
+
action: "mt-2 flex flex-wrap items-center justify-center gap-3"
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
var EmptyState = forwardRef(function EmptyState2({ icon, title, headingLevel, description, action, children, className, ...props }, ref) {
|
|
18
|
+
const styles = emptyState();
|
|
19
|
+
const Title = headingLevel ? `h${headingLevel}` : "p";
|
|
20
|
+
return /* @__PURE__ */ jsxs("div", { ref, "data-slot": "empty-state", className: styles.root({ class: className }), ...props, children: [
|
|
21
|
+
icon && /* @__PURE__ */ jsx("div", { "data-slot": "empty-state-icon", className: styles.icon(), children: icon }),
|
|
22
|
+
/* @__PURE__ */ jsxs(Title, { "data-slot": "empty-state-title", className: styles.title(), children: [
|
|
23
|
+
"[ ",
|
|
24
|
+
title,
|
|
25
|
+
" ]"
|
|
26
|
+
] }),
|
|
27
|
+
description && // A `div`, not a `p`, for the reason `AlertDialog`'s body gives: the
|
|
28
|
+
// slot takes arbitrary nodes, and a block element inside a paragraph is
|
|
29
|
+
// invalid HTML that the parser repairs by closing the paragraph early —
|
|
30
|
+
// which silently moves the rest of the description out of the box.
|
|
31
|
+
/* @__PURE__ */ jsx("div", { "data-slot": "empty-state-description", className: styles.description(), children: description }),
|
|
32
|
+
children,
|
|
33
|
+
action && /* @__PURE__ */ jsx("div", { "data-slot": "empty-state-action", className: styles.action(), children: action })
|
|
34
|
+
] });
|
|
35
|
+
});
|
|
36
|
+
export {
|
|
37
|
+
EmptyState
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=EmptyState.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/EmptyState.tsx"],"sourcesContent":["import { forwardRef } from 'react';\nimport type { HTMLAttributes, ReactNode } from 'react';\nimport { recipe } from '../lib/recipe';\n\n/**\n * A dashed well. The border is the one place this component departs from the\n * system's solid `border-edge-strong` edges, and the departure is the message:\n * a solid box is a container with something in it, a dashed one is a container\n * waiting for something. `Card` keeps the solid edge, so the two are not\n * mistakable at a glance.\n */\nconst emptyState = recipe({\n slots: {\n root:\n 'flex flex-col items-center gap-3 border-2 border-dashed border-edge-default '\n + 'bg-surface-raised px-6 py-10 text-center font-mono',\n /** The glyph sits in the muted role: it is the least load-bearing thing here. */\n icon: 'flex items-center justify-center text-content-muted',\n title: 'font-display text-lg font-bold uppercase tracking-wider text-content-primary',\n /** `max-w-prose` because a description that runs the width of a table is unreadable. */\n description: 'max-w-prose font-sans text-sm leading-relaxed text-content-secondary',\n /** A margin rather than the root's gap: the action is a step away from the prose. */\n action: 'mt-2 flex flex-wrap items-center justify-center gap-3',\n },\n});\n\nexport interface EmptyStateProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'className'> {\n /**\n * An optional glyph above the title — a `NerdIcon`, a `Glyph`, or ASCII.\n * Decorative by default, so give it `aria-hidden` unless it carries meaning\n * the title does not.\n */\n icon?: ReactNode;\n /**\n * The state, in as few words as possible — \"No results\", \"Nothing here yet\".\n * The one required slot: a box with no title is a hole in the layout.\n */\n title: string;\n /**\n * Render the title as a heading at this level instead of a paragraph. Leave\n * it unset inside a card, table or panel — the right level there depends on\n * the page around it. Set it where the empty state *is* the section or the\n * page: `StatusPage` passes `1`, because its title is the page's only `<h1>`.\n */\n headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;\n /**\n * Why it is empty, and what would change that. This is where the difference\n * between \"your filter matched nothing\" and \"you have not created anything\n * yet\" is actually said, and it is the difference that decides what the\n * reader does next.\n */\n description?: ReactNode;\n /**\n * The way out — a `Button` that clears the filter, creates the first record,\n * or links to the import. Takes nodes rather than a label and a handler so\n * that two actions, or a link beside a button, need no new prop.\n */\n action?: ReactNode;\n /**\n * Anything else the state needs: a hint list, a keyboard shortcut, a second\n * paragraph. Rendered between the description and the action.\n */\n children?: ReactNode;\n /** Merged onto the box. Sizing and placement belong to the caller's layout. */\n className?: string;\n}\n\n/**\n * Nothing to show, and what to do about it.\n *\n * The fourth state every application has — loading, loaded, empty, failed —\n * and the one most often left as a bare sentence in a table body. It is\n * compositional on purpose: an icon, a title, a description, an action slot,\n * all optional but the title. A fixed \"No results found\" component is one\n * product's empty state with its copy hoisted into a prop, and it is wrong for\n * the second consumer on the day they install it.\n *\n * ## The two empties are not the same state\n *\n * A search that matched nothing and an application with nothing in it look\n * alike and read completely differently. The first is *your query*, and the\n * action is to widen or clear it; the second is *the beginning*, and the action\n * is to create the first thing. Same component, different copy and a different\n * action — which is the argument for the slots.\n *\n * ## The title is a paragraph unless you say otherwise\n *\n * By default. This drops into a card body, a table, a panel, a dialog; the\n * right heading level is different in each and the component cannot know it.\n * A wrong level is worse than none — it breaks the document outline a screen\n * reader user navigates by. Where the empty state *is* the section or the page,\n * say so with `headingLevel`, and the title becomes that heading.\n */\nexport const EmptyState = forwardRef<HTMLDivElement, EmptyStateProps>(function EmptyState(\n { icon, title, headingLevel, description, action, children, className, ...props },\n ref,\n) {\n const styles = emptyState();\n const Title = headingLevel ? (`h${headingLevel}` as const) : 'p';\n\n return (\n <div ref={ref} data-slot=\"empty-state\" className={styles.root({ class: className })} {...props}>\n {icon && (\n <div data-slot=\"empty-state-icon\" className={styles.icon()}>\n {icon}\n </div>\n )}\n <Title data-slot=\"empty-state-title\" className={styles.title()}>\n [ {title} ]\n </Title>\n {description && (\n // A `div`, not a `p`, for the reason `AlertDialog`'s body gives: the\n // slot takes arbitrary nodes, and a block element inside a paragraph is\n // invalid HTML that the parser repairs by closing the paragraph early —\n // which silently moves the rest of the description out of the box.\n <div data-slot=\"empty-state-description\" className={styles.description()}>\n {description}\n </div>\n )}\n {children}\n {action && (\n <div data-slot=\"empty-state-action\" className={styles.action()}>\n {action}\n </div>\n )}\n </div>\n );\n});\n"],"mappings":";AAAA,SAAS,kBAAkB;AAE3B,SAAS,cAAc;AAqGf,cAIF,YAJE;AA5FR,IAAM,aAAa,OAAO;AAAA,EACxB,OAAO;AAAA,IACL,MACE;AAAA;AAAA,IAGF,MAAM;AAAA,IACN,OAAO;AAAA;AAAA,IAEP,aAAa;AAAA;AAAA,IAEb,QAAQ;AAAA,EACV;AACF,CAAC;AAqEM,IAAM,aAAa,WAA4C,SAASA,YAC7E,EAAE,MAAM,OAAO,cAAc,aAAa,QAAQ,UAAU,WAAW,GAAG,MAAM,GAChF,KACA;AACA,QAAM,SAAS,WAAW;AAC1B,QAAM,QAAQ,eAAgB,IAAI,YAAY,KAAe;AAE7D,SACE,qBAAC,SAAI,KAAU,aAAU,eAAc,WAAW,OAAO,KAAK,EAAE,OAAO,UAAU,CAAC,GAAI,GAAG,OACtF;AAAA,YACC,oBAAC,SAAI,aAAU,oBAAmB,WAAW,OAAO,KAAK,GACtD,gBACH;AAAA,IAEF,qBAAC,SAAM,aAAU,qBAAoB,WAAW,OAAO,MAAM,GAAG;AAAA;AAAA,MAC3D;AAAA,MAAM;AAAA,OACX;AAAA,IACC;AAAA;AAAA;AAAA;AAAA,IAKC,oBAAC,SAAI,aAAU,2BAA0B,WAAW,OAAO,YAAY,GACpE,uBACH;AAAA,IAED;AAAA,IACA,UACC,oBAAC,SAAI,aAAU,sBAAqB,WAAW,OAAO,OAAO,GAC1D,kBACH;AAAA,KAEJ;AAEJ,CAAC;","names":["EmptyState"]}
|