@shadow-js/ui 0.0.0-stage → 1.0.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/CHANGELOG.md +30 -0
- package/LICENSE +21 -0
- package/README.md +598 -2
- package/dist/cli.mjs +2281 -0
- package/dist/dev/cli/schema.d.ts +71 -0
- package/dist/dev/index.d.ts +6 -0
- package/dist/dev/index.js +5 -0
- package/dist/dev/locales/ar.d.ts +139 -0
- package/dist/dev/locales/ar.js +127 -0
- package/dist/dev/locales/ar.js.map +1 -0
- package/dist/dev/locales/en.d.ts +139 -0
- package/dist/dev/locales/en.js +127 -0
- package/dist/dev/locales/en.js.map +1 -0
- package/dist/dev/registry/utils/utils.d.ts +27 -0
- package/dist/dev/registry/utils/utils.js +26 -0
- package/dist/dev/registry/utils/utils.js.map +1 -0
- package/dist/dev/theme/theme-logic.d.ts +185 -0
- package/dist/dev/theme/theme-logic.js +183 -0
- package/dist/dev/theme/theme-logic.js.map +1 -0
- package/dist/dev/theme/theme-provider.d.ts +88 -0
- package/dist/dev/theme/theme-provider.js +149 -0
- package/dist/dev/theme/theme-provider.js.map +1 -0
- package/dist/prod/index.js +1 -0
- package/dist/prod/locales/ar.js +2 -0
- package/dist/prod/locales/ar.js.map +1 -0
- package/dist/prod/locales/en.js +2 -0
- package/dist/prod/locales/en.js.map +1 -0
- package/dist/prod/registry/utils/utils.js +2 -0
- package/dist/prod/registry/utils/utils.js.map +1 -0
- package/dist/prod/theme/theme-logic.js +2 -0
- package/dist/prod/theme/theme-logic.js.map +1 -0
- package/dist/prod/theme/theme-provider.js +2 -0
- package/dist/prod/theme/theme-provider.js.map +1 -0
- package/package.json +102 -4
- package/registry/accordion/accordion.tsx +210 -0
- package/registry/accordion/item.json +4 -0
- package/registry/alert/alert.tsx +200 -0
- package/registry/alert/item.json +4 -0
- package/registry/alert-dialog/alert-dialog.tsx +530 -0
- package/registry/alert-dialog/item.json +4 -0
- package/registry/aspect-ratio/aspect-ratio.tsx +71 -0
- package/registry/aspect-ratio/item.json +4 -0
- package/registry/attachment/attachment.tsx +459 -0
- package/registry/attachment/item.json +4 -0
- package/registry/avatar/avatar.tsx +385 -0
- package/registry/avatar/item.json +4 -0
- package/registry/badge/badge.tsx +108 -0
- package/registry/badge/item.json +4 -0
- package/registry/breadcrumb/breadcrumb.tsx +354 -0
- package/registry/breadcrumb/item.json +4 -0
- package/registry/bubble/bubble.tsx +260 -0
- package/registry/bubble/item.json +4 -0
- package/registry/button/button.tsx +125 -0
- package/registry/button/item.json +4 -0
- package/registry/button-group/button-group.tsx +189 -0
- package/registry/button-group/item.json +4 -0
- package/registry/calendar/calendar.tsx +348 -0
- package/registry/calendar/item.json +4 -0
- package/registry/card/card.tsx +311 -0
- package/registry/card/item.json +4 -0
- package/registry/carousel/carousel.tsx +851 -0
- package/registry/carousel/item.json +4 -0
- package/registry/chart/chart.tsx +1956 -0
- package/registry/chart/item.json +4 -0
- package/registry/chart-scale/chart-scale.ts +670 -0
- package/registry/chart-scale/item.json +5 -0
- package/registry/checkbox/checkbox.tsx +79 -0
- package/registry/checkbox/item.json +4 -0
- package/registry/code-block/code-block.tsx +470 -0
- package/registry/code-block/item.json +4 -0
- package/registry/collapsible/collapsible.tsx +117 -0
- package/registry/collapsible/item.json +4 -0
- package/registry/combobox/combobox.tsx +977 -0
- package/registry/combobox/item.json +4 -0
- package/registry/command/command.tsx +757 -0
- package/registry/command/item.json +4 -0
- package/registry/context-menu/context-menu.tsx +697 -0
- package/registry/context-menu/item.json +4 -0
- package/registry/data-table/data-table.tsx +437 -0
- package/registry/data-table/item.json +4 -0
- package/registry/date-picker/date-picker.tsx +349 -0
- package/registry/date-picker/item.json +4 -0
- package/registry/dialog/dialog.tsx +412 -0
- package/registry/dialog/item.json +4 -0
- package/registry/direction/direction.tsx +88 -0
- package/registry/direction/item.json +4 -0
- package/registry/drawer/drawer.tsx +787 -0
- package/registry/drawer/item.json +4 -0
- package/registry/dropdown-menu/dropdown-menu.tsx +692 -0
- package/registry/dropdown-menu/item.json +4 -0
- package/registry/empty/empty.tsx +240 -0
- package/registry/empty/item.json +4 -0
- package/registry/field/field.tsx +624 -0
- package/registry/field/item.json +4 -0
- package/registry/form/form.tsx +1112 -0
- package/registry/form/item.json +4 -0
- package/registry/hover-card/hover-card.tsx +144 -0
- package/registry/hover-card/item.json +4 -0
- package/registry/icons/icons.tsx +1691 -0
- package/registry/icons/item.json +4 -0
- package/registry/input/input.tsx +43 -0
- package/registry/input/item.json +4 -0
- package/registry/input-group/input-group.tsx +315 -0
- package/registry/input-group/item.json +4 -0
- package/registry/input-otp/input-otp.tsx +329 -0
- package/registry/input-otp/item.json +4 -0
- package/registry/item/item.json +4 -0
- package/registry/item/item.tsx +439 -0
- package/registry/kbd/item.json +4 -0
- package/registry/kbd/kbd.tsx +65 -0
- package/registry/label/item.json +4 -0
- package/registry/label/label.tsx +60 -0
- package/registry/marker/item.json +4 -0
- package/registry/marker/marker.tsx +175 -0
- package/registry/menubar/item.json +4 -0
- package/registry/menubar/menubar.tsx +694 -0
- package/registry/message/item.json +4 -0
- package/registry/message/message.tsx +280 -0
- package/registry/message-scroller/item.json +4 -0
- package/registry/message-scroller/message-scroller.tsx +262 -0
- package/registry/native-select/item.json +4 -0
- package/registry/native-select/native-select.tsx +113 -0
- package/registry/navigation-menu/item.json +4 -0
- package/registry/navigation-menu/navigation-menu.tsx +362 -0
- package/registry/pagination/item.json +4 -0
- package/registry/pagination/pagination.tsx +460 -0
- package/registry/password-input/item.json +5 -0
- package/registry/password-input/password-input.tsx +494 -0
- package/registry/popover/item.json +4 -0
- package/registry/popover/popover.tsx +284 -0
- package/registry/progress/item.json +4 -0
- package/registry/progress/progress.tsx +190 -0
- package/registry/questionnaire/item.json +4 -0
- package/registry/questionnaire/questionnaire.tsx +569 -0
- package/registry/radio-group/item.json +4 -0
- package/registry/radio-group/radio-group.tsx +101 -0
- package/registry/resizable/item.json +4 -0
- package/registry/resizable/resizable.tsx +455 -0
- package/registry/scroll-area/item.json +4 -0
- package/registry/scroll-area/scroll-area.tsx +149 -0
- package/registry/select/item.json +4 -0
- package/registry/select/select.tsx +571 -0
- package/registry/separator/item.json +4 -0
- package/registry/separator/separator.tsx +52 -0
- package/registry/sheet/item.json +4 -0
- package/registry/sheet/sheet.tsx +476 -0
- package/registry/sidebar/item.json +4 -0
- package/registry/sidebar/sidebar.tsx +1870 -0
- package/registry/skeleton/item.json +4 -0
- package/registry/skeleton/skeleton.tsx +47 -0
- package/registry/slider/item.json +4 -0
- package/registry/slider/slider.tsx +149 -0
- package/registry/spinner/item.json +4 -0
- package/registry/spinner/spinner.tsx +51 -0
- package/registry/switch/item.json +4 -0
- package/registry/switch/switch.tsx +93 -0
- package/registry/table/item.json +4 -0
- package/registry/table/table.tsx +373 -0
- package/registry/tabs/item.json +4 -0
- package/registry/tabs/tabs.tsx +209 -0
- package/registry/textarea/item.json +4 -0
- package/registry/textarea/textarea.tsx +51 -0
- package/registry/toast/item.json +4 -0
- package/registry/toast/toast.tsx +949 -0
- package/registry/toggle/item.json +4 -0
- package/registry/toggle/toggle.tsx +110 -0
- package/registry/toggle-group/item.json +4 -0
- package/registry/toggle-group/toggle-group.tsx +189 -0
- package/registry/tooltip/item.json +4 -0
- package/registry/tooltip/tooltip.tsx +169 -0
- package/registry/trace/item.json +4 -0
- package/registry/trace/trace.tsx +356 -0
- package/registry/ui-strings/item.json +5 -0
- package/registry/ui-strings/ui-strings.tsx +454 -0
- package/registry/use-highlighted-code/item.json +5 -0
- package/registry/use-highlighted-code/use-highlighted-code.tsx +145 -0
- package/registry/use-mobile/item.json +5 -0
- package/registry/use-mobile/use-mobile.ts +46 -0
- package/registry/utils/item.json +5 -0
- package/registry/utils/utils.ts +38 -0
- package/registry.json +3274 -0
- package/styles/animations.css +321 -0
- package/styles/theme-core.css +55 -0
- package/styles/theme.css +12 -0
- package/styles/themes/gray.css +69 -0
- package/styles/themes/neutral.css +69 -0
- package/styles/themes/slate.css +69 -0
- package/styles/themes/stone.css +69 -0
- package/styles/themes/zinc.css +69 -0
- package/styles/typeset.css +251 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import { untrack } from "@shadow-js/core";
|
|
2
|
+
import type { Direction } from "@shadow-js/primitives";
|
|
3
|
+
import type { Column, Header, Row, Table } from "@shadow-js/table";
|
|
4
|
+
import { For, type JSX, type JsxChild } from "@shadow-js/web";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import {
|
|
7
|
+
DropdownMenu,
|
|
8
|
+
DropdownMenuCheckboxItem,
|
|
9
|
+
DropdownMenuContent,
|
|
10
|
+
DropdownMenuTrigger,
|
|
11
|
+
} from "@/components/ui/dropdown-menu";
|
|
12
|
+
import {
|
|
13
|
+
TableBody,
|
|
14
|
+
TableCell,
|
|
15
|
+
TableHead,
|
|
16
|
+
TableHeader,
|
|
17
|
+
Table as TableRoot,
|
|
18
|
+
TableRow,
|
|
19
|
+
} from "@/components/ui/table";
|
|
20
|
+
import { useUiStrings } from "@/lib/ui-strings";
|
|
21
|
+
import { cn } from "@/lib/utils";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Props of {@link DataTable}: the table to render and the options of its element.
|
|
25
|
+
* @see {@link DataTable}
|
|
26
|
+
* @since 1.0.0
|
|
27
|
+
*/
|
|
28
|
+
export interface DataTableProps<TData> {
|
|
29
|
+
/** The table from `createTable` in `@shadow-js/table`. @default none (required) */
|
|
30
|
+
readonly table: Table<TData>;
|
|
31
|
+
/** Classes for the `table` element, merged with the table's own classes. @default none */
|
|
32
|
+
readonly class?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Shown in one cell spanning every column while there are no rows.
|
|
35
|
+
* @default "No results."
|
|
36
|
+
*/
|
|
37
|
+
readonly empty?: JsxChild;
|
|
38
|
+
/**
|
|
39
|
+
* The table's accessible name. It names the table and the scrollable region around it, so a wide
|
|
40
|
+
* table is a labeled region that keyboard users can scroll. Pass it or `aria-labelledby`.
|
|
41
|
+
* @default none
|
|
42
|
+
*/
|
|
43
|
+
readonly "aria-label"?: string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* The id of a visible heading that names the table. Use it instead of `aria-label` when the page
|
|
46
|
+
* already has the heading.
|
|
47
|
+
* @default none
|
|
48
|
+
*/
|
|
49
|
+
readonly "aria-labelledby"?: string | undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The `aria-sort` value of a column header: the direction the column is sorted in, `"none"` when it
|
|
54
|
+
* is sortable but not sorted, and `undefined` (no attribute) when it cannot be sorted at all.
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
function sortState<TData>(column: Column<TData>): "ascending" | "descending" | "none" | undefined {
|
|
58
|
+
if (!column.getCanSort()) return undefined;
|
|
59
|
+
const sorted = column.getIsSorted();
|
|
60
|
+
return sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : "none";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Renders a column's `header` or `cell` definition. A function is called with the context; a cell
|
|
65
|
+
* without a definition shows its value (`renderValue`, passed as the fallback).
|
|
66
|
+
*/
|
|
67
|
+
function render(definition: unknown, context: unknown): JsxChild {
|
|
68
|
+
if (typeof definition === "function") return definition(context) as JsxChild;
|
|
69
|
+
if (definition === undefined || definition === null) return "";
|
|
70
|
+
return String(definition);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A table that renders a `@shadow-js/table` table: one header cell per visible column (a group header
|
|
75
|
+
* spans its children), one row per row of the current row model, and `data-state="selected"` on the
|
|
76
|
+
* rows that are selected. A sort or a page change updates only the parts that changed. Use
|
|
77
|
+
* {@link DataTableColumnHeader} for sortable headers and {@link DataTablePagination} for the footer.
|
|
78
|
+
*
|
|
79
|
+
* Keyboard and ARIA: the element is a native table. Each header cell is a `columnheader` with
|
|
80
|
+
* `aria-sort` set to `"ascending"`, `"descending"` or `"none"` when the column can be sorted, and no
|
|
81
|
+
* `aria-sort` when it cannot. Sort buttons and pagination buttons are native buttons, so Enter and
|
|
82
|
+
* Space activate them, and a disabled button is skipped. Name the table with `aria-label` or
|
|
83
|
+
* `aria-labelledby` so its scrollable region has a name.
|
|
84
|
+
*
|
|
85
|
+
* shadcn/ui differences: the table is driven by `@shadow-js/table` (`createTable`) rather than by
|
|
86
|
+
* TanStack Table, so `table.getRowModel()`, `column.toggleSorting()` and the other table methods come
|
|
87
|
+
* from that package. The prop is `class`, not `className`, and every element has a `data-slot`.
|
|
88
|
+
*
|
|
89
|
+
* @param props The table and its options: see {@link DataTableProps}.
|
|
90
|
+
* @returns The table element.
|
|
91
|
+
* @example
|
|
92
|
+
* import { createColumnHelper, createTable } from "@shadow-js/table";
|
|
93
|
+
* import { layout } from "@shadow-js/table/layout";
|
|
94
|
+
* import { DataTable, DataTableColumnHeader, DataTablePagination } from "@/components/ui/data-table";
|
|
95
|
+
*
|
|
96
|
+
* interface Person {
|
|
97
|
+
* id: string;
|
|
98
|
+
* name: string;
|
|
99
|
+
* age: number;
|
|
100
|
+
* }
|
|
101
|
+
*
|
|
102
|
+
* const helper = createColumnHelper<Person>();
|
|
103
|
+
* const people: Person[] = [
|
|
104
|
+
* { id: "1", name: "Ada", age: 36 },
|
|
105
|
+
* { id: "2", name: "Grace", age: 85 },
|
|
106
|
+
* ];
|
|
107
|
+
*
|
|
108
|
+
* export function PeopleTable() {
|
|
109
|
+
* const table = createTable({
|
|
110
|
+
* data: people,
|
|
111
|
+
* getRowId: (row) => row.id,
|
|
112
|
+
* features: [layout],
|
|
113
|
+
* columns: [
|
|
114
|
+
* helper.accessor("name", {
|
|
115
|
+
* header: (context) => <DataTableColumnHeader column={context.column} title="Name" />,
|
|
116
|
+
* }),
|
|
117
|
+
* helper.accessor("age", { header: "Age" }),
|
|
118
|
+
* ],
|
|
119
|
+
* });
|
|
120
|
+
* return (
|
|
121
|
+
* <>
|
|
122
|
+
* <DataTable table={table} aria-label="People" />
|
|
123
|
+
* <DataTablePagination table={table} />
|
|
124
|
+
* </>
|
|
125
|
+
* );
|
|
126
|
+
* }
|
|
127
|
+
* @see {@link DataTableColumnHeader}
|
|
128
|
+
* @see {@link DataTablePagination}
|
|
129
|
+
* @since 1.0.0
|
|
130
|
+
*/
|
|
131
|
+
export function DataTable<TData>(props: DataTableProps<TData>): JSX.Element {
|
|
132
|
+
const table = props.table;
|
|
133
|
+
const t = useUiStrings();
|
|
134
|
+
return (
|
|
135
|
+
<TableRoot
|
|
136
|
+
class={cn(props.class)}
|
|
137
|
+
aria-label={props["aria-label"]}
|
|
138
|
+
aria-labelledby={props["aria-labelledby"]}
|
|
139
|
+
>
|
|
140
|
+
<TableHeader>
|
|
141
|
+
{() =>
|
|
142
|
+
table.getHeaderGroups().map((group) => (
|
|
143
|
+
<TableRow>
|
|
144
|
+
{group.headers.map((header: Header<TData>) =>
|
|
145
|
+
// A placeholder is covered by the header above it (its rowSpan), so it is no cell.
|
|
146
|
+
header.isPlaceholder ? null : (
|
|
147
|
+
<TableHead
|
|
148
|
+
colSpan={header.colSpan}
|
|
149
|
+
rowSpan={header.rowSpan}
|
|
150
|
+
aria-sort={sortState(header.column)}
|
|
151
|
+
>
|
|
152
|
+
{render(header.column.columnDef.header, header.getContext())}
|
|
153
|
+
</TableHead>
|
|
154
|
+
),
|
|
155
|
+
)}
|
|
156
|
+
</TableRow>
|
|
157
|
+
))
|
|
158
|
+
}
|
|
159
|
+
</TableHeader>
|
|
160
|
+
<TableBody>
|
|
161
|
+
{() => {
|
|
162
|
+
const rows = table.getRowModel().rows;
|
|
163
|
+
if (rows.length === 0) {
|
|
164
|
+
return (
|
|
165
|
+
<TableRow>
|
|
166
|
+
<TableCell colSpan={table.getVisibleLeafColumns().length}>
|
|
167
|
+
{props.empty ?? t("dataTable.empty", "No results.")}
|
|
168
|
+
</TableCell>
|
|
169
|
+
</TableRow>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return rows.map((row: Row<TData>) => (
|
|
173
|
+
<TableRow data-state={row.getIsSelected() ? "selected" : undefined}>
|
|
174
|
+
{row.getVisibleCells().map((cell) => (
|
|
175
|
+
<TableCell>
|
|
176
|
+
{render(cell.column.columnDef.cell ?? cell.renderValue, cell.getContext())}
|
|
177
|
+
</TableCell>
|
|
178
|
+
))}
|
|
179
|
+
</TableRow>
|
|
180
|
+
));
|
|
181
|
+
}}
|
|
182
|
+
</TableBody>
|
|
183
|
+
</TableRoot>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Props of {@link DataTableColumnHeader}: the column to sort and the visible title.
|
|
189
|
+
* @see {@link DataTableColumnHeader}
|
|
190
|
+
* @since 1.0.0
|
|
191
|
+
*/
|
|
192
|
+
export interface DataTableColumnHeaderProps<TData> {
|
|
193
|
+
/** The column this header sorts, from the header context (`context.column`). @default none (required) */
|
|
194
|
+
readonly column: Column<TData>;
|
|
195
|
+
/**
|
|
196
|
+
* The visible title of the header.
|
|
197
|
+
* @default the column id
|
|
198
|
+
*/
|
|
199
|
+
readonly title?: string;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* A button in a column header that sorts its column. Each click cycles the column through ascending,
|
|
204
|
+
* descending and unsorted (`column.toggleSorting()`). The header is disabled when the column cannot
|
|
205
|
+
* be sorted. The direction is shown as an arrow and announced by `aria-sort` on the column header
|
|
206
|
+
* cell, which {@link DataTable} sets; the button itself has no `aria-sort`.
|
|
207
|
+
*
|
|
208
|
+
* @param props The header props: see {@link DataTableColumnHeaderProps}.
|
|
209
|
+
* @returns The sort button.
|
|
210
|
+
* @example
|
|
211
|
+
* import { createColumnHelper } from "@shadow-js/table";
|
|
212
|
+
* import { DataTableColumnHeader } from "@/components/ui/data-table";
|
|
213
|
+
*
|
|
214
|
+
* interface Person {
|
|
215
|
+
* name: string;
|
|
216
|
+
* }
|
|
217
|
+
*
|
|
218
|
+
* const helper = createColumnHelper<Person>();
|
|
219
|
+
* export const nameColumn = helper.accessor("name", {
|
|
220
|
+
* header: (context) => <DataTableColumnHeader column={context.column} title="Name" />,
|
|
221
|
+
* });
|
|
222
|
+
* @see {@link DataTable}
|
|
223
|
+
* @since 1.0.0
|
|
224
|
+
*/
|
|
225
|
+
export function DataTableColumnHeader<TData>(
|
|
226
|
+
props: DataTableColumnHeaderProps<TData>,
|
|
227
|
+
): JSX.Element {
|
|
228
|
+
const column = props.column;
|
|
229
|
+
return (
|
|
230
|
+
<Button
|
|
231
|
+
variant="ghost"
|
|
232
|
+
size="sm"
|
|
233
|
+
disabled={!column.getCanSort()}
|
|
234
|
+
onClick={() => column.toggleSorting()}
|
|
235
|
+
>
|
|
236
|
+
{props.title ?? column.id}
|
|
237
|
+
{() => (column.getIsSorted() === "desc" ? " ↓" : column.getIsSorted() === "asc" ? " ↑" : "")}
|
|
238
|
+
</Button>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The words of {@link DataTablePagination}. Each one defaults to English; an app in another language
|
|
244
|
+
* passes its own.
|
|
245
|
+
* @see {@link DataTablePagination}
|
|
246
|
+
* @since 1.0.0
|
|
247
|
+
*/
|
|
248
|
+
export interface DataTablePaginationLabels {
|
|
249
|
+
/**
|
|
250
|
+
* The text of the previous-page button.
|
|
251
|
+
* @default "Previous"
|
|
252
|
+
*/
|
|
253
|
+
readonly previous?: string | undefined;
|
|
254
|
+
/**
|
|
255
|
+
* The text of the next-page button.
|
|
256
|
+
* @default "Next"
|
|
257
|
+
*/
|
|
258
|
+
readonly next?: string | undefined;
|
|
259
|
+
/**
|
|
260
|
+
* The page status, given the current page (from 1) and the page count.
|
|
261
|
+
* @default `Page ${page} of ${pages}`
|
|
262
|
+
*/
|
|
263
|
+
readonly page?: ((page: number, pages: number) => string) | undefined;
|
|
264
|
+
/**
|
|
265
|
+
* The status shown when the table has no pages.
|
|
266
|
+
* @default "No pages"
|
|
267
|
+
*/
|
|
268
|
+
readonly noPages?: string | undefined;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** The words of the footer, each one set (the defaults filled in). @internal */
|
|
272
|
+
type PaginationWords = {
|
|
273
|
+
readonly previous: string;
|
|
274
|
+
readonly next: string;
|
|
275
|
+
readonly page: (page: number, pages: number) => string;
|
|
276
|
+
readonly noPages: string;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Props of {@link DataTablePagination}: the table to page through and the footer's words.
|
|
281
|
+
* @see {@link DataTablePagination}
|
|
282
|
+
* @since 1.0.0
|
|
283
|
+
*/
|
|
284
|
+
export interface DataTablePaginationProps<TData> {
|
|
285
|
+
/** The table from `createTable` in `@shadow-js/table`, whose page state the footer changes. @default none (required) */
|
|
286
|
+
readonly table: Table<TData>;
|
|
287
|
+
/** The words of the footer, for a language other than English. @default none */
|
|
288
|
+
readonly labels?: DataTablePaginationLabels | undefined;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* The footer of a {@link DataTable}: the page status, and Previous and Next buttons. Previous is
|
|
293
|
+
* disabled on the first page and Next on the last one. The status reads "No pages" when there is
|
|
294
|
+
* nothing to page through.
|
|
295
|
+
*
|
|
296
|
+
* Keyboard and ARIA: the buttons are native buttons, so Enter and Space activate them, and a disabled
|
|
297
|
+
* one is skipped. The page status is plain text.
|
|
298
|
+
*
|
|
299
|
+
* @param props The footer props: see {@link DataTablePaginationProps}.
|
|
300
|
+
* @returns The footer element.
|
|
301
|
+
* @example
|
|
302
|
+
* import { createColumnHelper, createTable } from "@shadow-js/table";
|
|
303
|
+
* import { DataTablePagination } from "@/components/ui/data-table";
|
|
304
|
+
*
|
|
305
|
+
* interface Person {
|
|
306
|
+
* id: string;
|
|
307
|
+
* name: string;
|
|
308
|
+
* }
|
|
309
|
+
*
|
|
310
|
+
* const helper = createColumnHelper<Person>();
|
|
311
|
+
*
|
|
312
|
+
* export function Footer() {
|
|
313
|
+
* const table = createTable({
|
|
314
|
+
* data: [{ id: "1", name: "Ada" }],
|
|
315
|
+
* getRowId: (row) => row.id,
|
|
316
|
+
* columns: [helper.accessor("name", { header: "Name" })],
|
|
317
|
+
* });
|
|
318
|
+
* return <DataTablePagination table={table} labels={{ previous: "Zurück", next: "Weiter" }} />;
|
|
319
|
+
* }
|
|
320
|
+
* @see {@link DataTable}
|
|
321
|
+
* @since 1.0.0
|
|
322
|
+
*/
|
|
323
|
+
export function DataTablePagination<TData>(props: DataTablePaginationProps<TData>): JSX.Element {
|
|
324
|
+
const table = props.table;
|
|
325
|
+
const t = useUiStrings();
|
|
326
|
+
const label = (): PaginationWords => ({
|
|
327
|
+
previous: props.labels?.previous ?? t("dataTable.previous", "Previous"),
|
|
328
|
+
next: props.labels?.next ?? t("dataTable.next", "Next"),
|
|
329
|
+
page:
|
|
330
|
+
props.labels?.page ??
|
|
331
|
+
((page: number, pages: number) =>
|
|
332
|
+
t("dataTable.page", "Page {page} of {pages}", { page, pages })),
|
|
333
|
+
noPages: props.labels?.noPages ?? t("dataTable.noPages", "No pages"),
|
|
334
|
+
});
|
|
335
|
+
return (
|
|
336
|
+
<div data-slot="data-table-pagination" class="flex items-center justify-end gap-2 py-2">
|
|
337
|
+
<span class="text-sm text-muted-foreground">
|
|
338
|
+
{() => {
|
|
339
|
+
const count = table.getPageCount();
|
|
340
|
+
const page = table.getState().pagination.pageIndex + 1;
|
|
341
|
+
return count > 0 ? label().page(page, count) : label().noPages;
|
|
342
|
+
}}
|
|
343
|
+
</span>
|
|
344
|
+
<Button
|
|
345
|
+
variant="outline"
|
|
346
|
+
size="sm"
|
|
347
|
+
disabled={!table.getCanPreviousPage()}
|
|
348
|
+
onClick={() => table.previousPage()}
|
|
349
|
+
>
|
|
350
|
+
{() => label().previous}
|
|
351
|
+
</Button>
|
|
352
|
+
<Button
|
|
353
|
+
variant="outline"
|
|
354
|
+
size="sm"
|
|
355
|
+
disabled={!table.getCanNextPage()}
|
|
356
|
+
onClick={() => table.nextPage()}
|
|
357
|
+
>
|
|
358
|
+
{() => label().next}
|
|
359
|
+
</Button>
|
|
360
|
+
</div>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Props of {@link DataTableViewOptions}.
|
|
366
|
+
* @see {@link DataTableViewOptions}
|
|
367
|
+
* @since 1.0.0
|
|
368
|
+
*/
|
|
369
|
+
export interface DataTableViewOptionsProps<TData> {
|
|
370
|
+
/** Table with the `visibility` feature installed. @default none (required) */
|
|
371
|
+
readonly table: Table<TData>;
|
|
372
|
+
/** Accessible name and text of the menu trigger. @default "View" */
|
|
373
|
+
readonly label?: string | undefined;
|
|
374
|
+
/** Label for each column, called untracked. @default the column id */
|
|
375
|
+
readonly columnLabel?: ((column: Column<TData>) => JsxChild) | undefined;
|
|
376
|
+
/** Trigger classes, merged with the button's defaults. @default none */
|
|
377
|
+
readonly class?: string | undefined;
|
|
378
|
+
/** Reading direction of the portaled menu. @default inherited direction */
|
|
379
|
+
readonly dir?: Direction | undefined;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* A checkbox menu for showing and hiding leaf columns. Columns with `enableHiding: false` are
|
|
384
|
+
* excluded. Checked state follows the table, including changes made outside the menu.
|
|
385
|
+
*
|
|
386
|
+
* Keyboard and ARIA: a named menu button opens with Enter, Space or ArrowDown. The menu uses
|
|
387
|
+
* `menuitemcheckbox` roles; arrow keys move focus, Enter or Space toggles, and Escape closes and
|
|
388
|
+
* returns focus. Supply `label` and `columnLabel` for the app's language.
|
|
389
|
+
*
|
|
390
|
+
* @param props The table, labels, trigger classes and reading direction.
|
|
391
|
+
* @returns The column visibility menu.
|
|
392
|
+
* @example
|
|
393
|
+
* import { createColumnHelper, createTable } from "@shadow-js/table";
|
|
394
|
+
* import { visibility } from "@shadow-js/table/visibility";
|
|
395
|
+
* import { DataTableViewOptions } from "@/components/ui/data-table";
|
|
396
|
+
*
|
|
397
|
+
* export function Columns() {
|
|
398
|
+
* const helper = createColumnHelper<{ name: string }>();
|
|
399
|
+
* const table = createTable({ data: [], columns: [helper.accessor("name")], features: [visibility] });
|
|
400
|
+
* return <DataTableViewOptions table={table} />;
|
|
401
|
+
* }
|
|
402
|
+
* @see {@link DataTable}
|
|
403
|
+
* @since 1.0.0
|
|
404
|
+
*/
|
|
405
|
+
export function DataTableViewOptions<TData>(props: DataTableViewOptionsProps<TData>): JSX.Element {
|
|
406
|
+
const t = useUiStrings();
|
|
407
|
+
const columns = () => props.table.getAllLeafColumns().filter((column) => column.getCanHide());
|
|
408
|
+
return (
|
|
409
|
+
<DropdownMenu dir={props.dir}>
|
|
410
|
+
<DropdownMenuTrigger
|
|
411
|
+
as={Button}
|
|
412
|
+
data-slot="data-table-view-options"
|
|
413
|
+
variant="outline"
|
|
414
|
+
size="sm"
|
|
415
|
+
class={props.class}
|
|
416
|
+
disabled={columns().length === 0}
|
|
417
|
+
>
|
|
418
|
+
{props.label ?? t("dataTable.view", "View")}
|
|
419
|
+
</DropdownMenuTrigger>
|
|
420
|
+
<DropdownMenuContent align="end">
|
|
421
|
+
<For each={columns()}>
|
|
422
|
+
{(column) => (
|
|
423
|
+
<DropdownMenuCheckboxItem
|
|
424
|
+
checked={column.getIsVisible()}
|
|
425
|
+
onCheckedChange={(checked) => column.toggleVisibility(checked)}
|
|
426
|
+
>
|
|
427
|
+
{() => {
|
|
428
|
+
const label = props.columnLabel;
|
|
429
|
+
return label === undefined ? column.id : untrack(() => label(column));
|
|
430
|
+
}}
|
|
431
|
+
</DropdownMenuCheckboxItem>
|
|
432
|
+
)}
|
|
433
|
+
</For>
|
|
434
|
+
</DropdownMenuContent>
|
|
435
|
+
</DropdownMenu>
|
|
436
|
+
);
|
|
437
|
+
}
|