@vendure-io/ui 1.3.0 → 2.0.0-beta.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/README.md +5 -5
- package/package.json +10 -5
- package/src/components/{ui → atoms}/alert-dialog.tsx +4 -4
- package/src/components/{ui → atoms}/badge.tsx +6 -3
- package/src/components/{ui → atoms}/button-group.tsx +1 -2
- package/src/components/{ui → atoms}/button.tsx +8 -2
- package/src/components/{ui → atoms}/calendar.tsx +1 -1
- package/src/components/{ui → atoms}/card.tsx +2 -3
- package/src/components/{ui → atoms}/carousel.tsx +1 -1
- package/src/components/atoms/checkbox.tsx +29 -0
- package/src/components/{ui → atoms}/combobox.tsx +3 -4
- package/src/components/{ui → atoms}/command.tsx +2 -2
- package/src/components/{ui → atoms}/context-menu.tsx +1 -1
- package/src/components/{ui → atoms}/dialog.tsx +4 -4
- package/src/components/{ui → atoms}/drawer.tsx +3 -3
- package/src/components/{ui → atoms}/dropdown-menu.tsx +3 -2
- package/src/components/{ui → atoms}/empty.tsx +1 -1
- package/src/components/{ui → atoms}/field.tsx +2 -2
- package/src/components/{ui → atoms}/hover-card.tsx +1 -1
- package/src/components/{ui → atoms}/input-group.tsx +3 -4
- package/src/components/{ui → atoms}/input-otp.tsx +0 -1
- package/src/components/{ui → atoms}/input.tsx +0 -1
- package/src/components/{ui → atoms}/item.tsx +2 -2
- package/src/components/{ui → atoms}/menubar.tsx +3 -4
- package/src/components/{ui → atoms}/native-select.tsx +0 -1
- package/src/components/{ui → atoms}/navigation-menu.tsx +7 -4
- package/src/components/{ui → atoms}/pagination.tsx +1 -1
- package/src/components/{ui → atoms}/popover.tsx +2 -2
- package/src/components/{ui → atoms}/select.tsx +22 -4
- package/src/components/{ui → atoms}/sheet.tsx +4 -4
- package/src/components/{ui → atoms}/sidebar.tsx +6 -6
- package/src/components/atoms/skeleton.tsx +16 -0
- package/src/components/{ui → atoms}/slider.tsx +0 -1
- package/src/components/{ui → atoms}/sonner.tsx +1 -1
- package/src/components/{ui → atoms}/switch.tsx +0 -1
- package/src/components/{ui → atoms}/tabs.tsx +6 -4
- package/src/components/{ui → atoms}/textarea.tsx +0 -1
- package/src/components/{ui → atoms}/toggle-group.tsx +3 -3
- package/src/components/{ui → atoms}/toggle.tsx +0 -1
- package/src/components/{ui → atoms}/tooltip.tsx +2 -1
- package/src/components/molecules/anonymized-token.tsx +170 -0
- package/src/components/molecules/chip.tsx +85 -0
- package/src/components/{custom → molecules}/combobox-free-text.tsx +7 -3
- package/src/components/molecules/confirm-dialog.tsx +108 -0
- package/src/components/molecules/copyable-text.tsx +102 -0
- package/src/components/molecules/data-table/data-table-bulk-actions.tsx +69 -0
- package/src/components/molecules/data-table/data-table-column-header.tsx +61 -0
- package/src/components/molecules/data-table/data-table-filters.tsx +198 -0
- package/src/components/molecules/data-table/data-table-helpers.tsx +73 -0
- package/src/components/molecules/data-table/data-table-types.tsx +241 -0
- package/src/components/molecules/data-table/data-table-view-options.tsx +73 -0
- package/src/components/molecules/data-table/data-table.tsx +500 -0
- package/src/components/molecules/data-table/list-header.tsx +53 -0
- package/src/components/molecules/data-table/table-pagination.tsx +263 -0
- package/src/components/molecules/date-time.tsx +73 -0
- package/src/components/molecules/description-list.tsx +120 -0
- package/src/components/molecules/format-provider.tsx +46 -0
- package/src/components/molecules/id-chip.tsx +79 -0
- package/src/components/molecules/illustrations/access-denied.tsx +55 -0
- package/src/components/molecules/illustrations/empty-collection.tsx +66 -0
- package/src/components/molecules/illustrations/empty-database.tsx +44 -0
- package/src/components/molecules/illustrations/empty-media.tsx +68 -0
- package/src/components/molecules/illustrations/error.tsx +64 -0
- package/src/components/molecules/illustrations/first-run.tsx +55 -0
- package/src/components/molecules/illustrations/illustration-types.tsx +9 -0
- package/src/components/molecules/illustrations/no-activity.tsx +49 -0
- package/src/components/molecules/illustrations/no-deployments.tsx +49 -0
- package/src/components/molecules/illustrations/no-documents.tsx +67 -0
- package/src/components/molecules/illustrations/no-keys.tsx +50 -0
- package/src/components/molecules/illustrations/no-logs.tsx +53 -0
- package/src/components/molecules/illustrations/no-members.tsx +56 -0
- package/src/components/molecules/illustrations/no-notifications.tsx +52 -0
- package/src/components/molecules/illustrations/no-orders.tsx +48 -0
- package/src/components/molecules/illustrations/no-plugins.tsx +72 -0
- package/src/components/molecules/illustrations/no-results.tsx +69 -0
- package/src/components/molecules/illustrations/not-found.tsx +48 -0
- package/src/components/molecules/illustrations/offline.tsx +43 -0
- package/src/components/molecules/illustrations/pending-approval.tsx +56 -0
- package/src/components/molecules/illustrations/upload-dropzone.tsx +59 -0
- package/src/components/molecules/money.tsx +91 -0
- package/src/components/{custom → molecules}/multi-select.tsx +2 -2
- package/src/components/molecules/page-header.tsx +80 -0
- package/src/components/molecules/password-input.tsx +47 -0
- package/src/components/molecules/relative-time.tsx +81 -0
- package/src/components/molecules/stat-card.tsx +134 -0
- package/src/components/molecules/state-views/empty-state.tsx +75 -0
- package/src/components/molecules/state-views/error-state.tsx +91 -0
- package/src/components/molecules/state-views/loading-state.tsx +80 -0
- package/src/components/molecules/status-badge.tsx +100 -0
- package/src/hooks/use-copy.ts +51 -0
- package/src/lib/base-ui.ts +1 -1
- package/src/lib/format-relative.ts +41 -0
- package/src/lib/state-dictionary.ts +137 -0
- package/src/components/ui/checkbox.tsx +0 -30
- package/src/components/ui/skeleton.tsx +0 -13
- /package/src/components/{ui → atoms}/accordion.tsx +0 -0
- /package/src/components/{ui → atoms}/alert.tsx +0 -0
- /package/src/components/{ui → atoms}/aspect-ratio.tsx +0 -0
- /package/src/components/{ui → atoms}/avatar.tsx +0 -0
- /package/src/components/{ui → atoms}/breadcrumb.tsx +0 -0
- /package/src/components/{ui → atoms}/chart.tsx +0 -0
- /package/src/components/{ui → atoms}/collapsible.tsx +0 -0
- /package/src/components/{ui → atoms}/direction.tsx +0 -0
- /package/src/components/{ui → atoms}/kbd.tsx +0 -0
- /package/src/components/{ui → atoms}/label.tsx +0 -0
- /package/src/components/{ui → atoms}/progress.tsx +0 -0
- /package/src/components/{ui → atoms}/radio-group.tsx +0 -0
- /package/src/components/{ui → atoms}/resizable.tsx +0 -0
- /package/src/components/{ui → atoms}/scroll-area.tsx +0 -0
- /package/src/components/{ui → atoms}/separator.tsx +0 -0
- /package/src/components/{ui → atoms}/spinner.tsx +0 -0
- /package/src/components/{ui → atoms}/table.tsx +0 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CellContext,
|
|
3
|
+
ColumnDef,
|
|
4
|
+
ColumnFiltersState,
|
|
5
|
+
HeaderContext,
|
|
6
|
+
OnChangeFn,
|
|
7
|
+
Row,
|
|
8
|
+
RowSelectionState,
|
|
9
|
+
SortingState,
|
|
10
|
+
Table,
|
|
11
|
+
TableOptions,
|
|
12
|
+
VisibilityState,
|
|
13
|
+
} from '@tanstack/react-table';
|
|
14
|
+
import type * as React from 'react';
|
|
15
|
+
|
|
16
|
+
// Pure type module for the DataTable molecule. Kept runtime-free so it can be
|
|
17
|
+
// imported by both `data-table.tsx` and its sub-components without an import
|
|
18
|
+
// cycle. `data-table.tsx` re-exports everything here (plus the consumer-facing
|
|
19
|
+
// TanStack types) so columns can be authored from a single import.
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Controlled OR uncontrolled, per capability. `value` present → controlled: the
|
|
23
|
+
* prop is the source of truth on every render (no internal seeding). `defaultValue`
|
|
24
|
+
* → uncontrolled: the core seeds its own state and `onChange` (if given) only
|
|
25
|
+
* observes. Never mix `value` with `defaultValue`.
|
|
26
|
+
*/
|
|
27
|
+
export type ControlledState<T> =
|
|
28
|
+
| { value: T; onChange: OnChangeFn<T>; defaultValue?: never }
|
|
29
|
+
| { value?: never; onChange?: OnChangeFn<T>; defaultValue?: T };
|
|
30
|
+
|
|
31
|
+
/* --- capability configs; PRESENCE of the config is the only feature switch --- */
|
|
32
|
+
|
|
33
|
+
export type DataTableSorting = ControlledState<SortingState> & {
|
|
34
|
+
/** 'server' (default) → manualSorting, emit only. 'client' → TanStack sorts `rows`. */
|
|
35
|
+
mode?: 'server' | 'client';
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type DataTableColumnVisibility = ControlledState<VisibilityState> & {
|
|
39
|
+
/**
|
|
40
|
+
* Defaults to true. Set false when a consumer renders its own column-settings
|
|
41
|
+
* control through `toolbar(table)` while the core still consumes visibility.
|
|
42
|
+
*/
|
|
43
|
+
showViewOptions?: boolean;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type DataTableRowSelection<TData> = ControlledState<RowSelectionState> & {
|
|
47
|
+
enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
|
|
48
|
+
/** Display-column id for the generated selection column. Default: 'select'. */
|
|
49
|
+
selectColumnId?: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 1-based, mirrors the Phase-1 `TablePagination` (composed verbatim). Always
|
|
54
|
+
* controlled via `page`/`onPageChange` — no `defaultValue` branch, because page
|
|
55
|
+
* is typically URL-owned.
|
|
56
|
+
*/
|
|
57
|
+
export interface DataTablePagination {
|
|
58
|
+
mode?: 'server' | 'client'; // default 'server'
|
|
59
|
+
page: number; // 1-based
|
|
60
|
+
pageSize: number;
|
|
61
|
+
totalItems?: number; // required in server mode
|
|
62
|
+
onPageChange: (page: number) => void;
|
|
63
|
+
onPageSizeChange?: (size: number) => void; // presence renders the size selector
|
|
64
|
+
pageSizeOptions?: number[]; // default [10, 25, 50, 100]
|
|
65
|
+
getPageHref?: (page: number) => string;
|
|
66
|
+
formatRange?: (range: { start: number; end: number; total: number }) => string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Open operator vocabulary — the core never interprets it; it only stores it in
|
|
71
|
+
* `ColumnFiltersState` and renders chips. Matches the donor's `{ [operator]: value }`.
|
|
72
|
+
*/
|
|
73
|
+
export type DataTableFilterOperator =
|
|
74
|
+
| 'eq'
|
|
75
|
+
| 'notEq'
|
|
76
|
+
| 'contains'
|
|
77
|
+
| 'notContains'
|
|
78
|
+
| 'lt'
|
|
79
|
+
| 'lte'
|
|
80
|
+
| 'gt'
|
|
81
|
+
| 'gte'
|
|
82
|
+
| 'between'
|
|
83
|
+
| 'in'
|
|
84
|
+
| 'notIn'
|
|
85
|
+
| 'regex'
|
|
86
|
+
| 'isNull'
|
|
87
|
+
| (string & {});
|
|
88
|
+
|
|
89
|
+
export type DataTableFilterValue = Partial<Record<DataTableFilterOperator, unknown>>;
|
|
90
|
+
|
|
91
|
+
/** Declares one filterable column to the add-filter menu + chip row. */
|
|
92
|
+
export interface DataTableFilterColumn<TData = unknown> {
|
|
93
|
+
id: string; // must match a ColumnDef.id / accessorKey
|
|
94
|
+
label: React.ReactNode;
|
|
95
|
+
operators?: DataTableFilterOperator[];
|
|
96
|
+
/**
|
|
97
|
+
* Editor for this column's value. Default: a text `Input` emitting
|
|
98
|
+
* `{ [operators?.[0] ?? 'contains']: value }`. The dashboard passes its own
|
|
99
|
+
* typed editors here.
|
|
100
|
+
*/
|
|
101
|
+
renderInput?: (ctx: {
|
|
102
|
+
value: DataTableFilterValue | undefined;
|
|
103
|
+
onChange: (next: DataTableFilterValue | undefined) => void;
|
|
104
|
+
column: DataTableFilterColumn<TData>;
|
|
105
|
+
}) => React.ReactNode;
|
|
106
|
+
/**
|
|
107
|
+
* Chip summary. Default: `${label}: ${JSON.stringify(value)}`. The dashboard
|
|
108
|
+
* passes its human-readable-operator formatter here.
|
|
109
|
+
*/
|
|
110
|
+
formatChip?: (value: DataTableFilterValue) => React.ReactNode;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export type DataTableFilters<TData> = ControlledState<ColumnFiltersState> & {
|
|
114
|
+
mode?: 'server' | 'client'; // default 'server'
|
|
115
|
+
/**
|
|
116
|
+
* Optional. Present → the core renders the add-filter menu. Absent → the core
|
|
117
|
+
* renders ONLY the applied-chip row (the dashboard supplies its own add-filter
|
|
118
|
+
* in `toolbar`).
|
|
119
|
+
*/
|
|
120
|
+
columns?: DataTableFilterColumn<TData>[];
|
|
121
|
+
/** Collapse chips past this many into an "N filters" popover. Default Infinity. */
|
|
122
|
+
inlineChipLimit?: number;
|
|
123
|
+
/**
|
|
124
|
+
* Defaults to true when `columns` is present. Set false when a consumer uses
|
|
125
|
+
* `columns` for chip labels/formatting but renders its own add-filter UI.
|
|
126
|
+
*/
|
|
127
|
+
showAddFilter?: boolean;
|
|
128
|
+
/** Defaults to true. Set false when a consumer renders its own applied-filter UI. */
|
|
129
|
+
showAppliedFilters?: boolean;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export interface DataTableBulkActionContext<TData> {
|
|
133
|
+
/**
|
|
134
|
+
* `row.original` for EVERY selected row across pages (resolved from the core's
|
|
135
|
+
* selection cache). Named `selection` to match the donor's `BulkActionContext`.
|
|
136
|
+
*/
|
|
137
|
+
selection: TData[];
|
|
138
|
+
selectedRowIds: string[];
|
|
139
|
+
table: Table<TData>;
|
|
140
|
+
clearSelection: () => void; // table.resetRowSelection()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface DataTableLabels {
|
|
144
|
+
empty?: string; // 'No results'
|
|
145
|
+
selectAllRows?: string; // 'Select all rows'
|
|
146
|
+
selectRow?: (index: number) => string; // (i) => `Select row ${i + 1}`
|
|
147
|
+
rowActions?: string; // 'Open row actions'
|
|
148
|
+
columnsTrigger?: string; // 'Columns'
|
|
149
|
+
columnsHeading?: string; // 'Toggle columns'
|
|
150
|
+
addFilter?: string; // 'Add filter'
|
|
151
|
+
removeFilter?: (columnLabel: string) => string; // (l) => `Remove ${l} filter`
|
|
152
|
+
sortLabel?: (columnLabel: string) => string; // (c) => `Sort by ${c}`
|
|
153
|
+
filtersCollapsed?: (count: number) => string; // (n) => `${n} filters`
|
|
154
|
+
pagination?: {
|
|
155
|
+
// forwarded to TablePagination
|
|
156
|
+
navLabel?: string;
|
|
157
|
+
previousLabel?: string;
|
|
158
|
+
nextLabel?: string;
|
|
159
|
+
pageSizeLabel?: string;
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface DataTableProps<TData> {
|
|
164
|
+
rows: TData[]; // current page (server) or full set (client). The core never fetches.
|
|
165
|
+
// biome-ignore lint/suspicious/noExplicitAny: matches TanStack's ColumnDef value-type convention
|
|
166
|
+
columns: ColumnDef<TData, any>[]; // `meta` passed through untouched. THE cell/header seam.
|
|
167
|
+
/** TanStack getRowId. Required with server pagination + rowSelection. */
|
|
168
|
+
getRowId?: (row: TData, index: number) => string;
|
|
169
|
+
|
|
170
|
+
isLoading?: boolean; // skeleton rows only while `rows` is empty; never blanks populated rows
|
|
171
|
+
skeletonRowCount?: number; // default 10
|
|
172
|
+
|
|
173
|
+
// capability configs — omit → capability + its UI vanish
|
|
174
|
+
pagination?: DataTablePagination;
|
|
175
|
+
sorting?: DataTableSorting;
|
|
176
|
+
filters?: DataTableFilters<TData>;
|
|
177
|
+
columnVisibility?: DataTableColumnVisibility;
|
|
178
|
+
rowSelection?: DataTableRowSelection<TData>;
|
|
179
|
+
|
|
180
|
+
// slots
|
|
181
|
+
/** First child of the internal `ListHeader` (title zone) — compose `PageHeader` here. */
|
|
182
|
+
header?: React.ReactNode;
|
|
183
|
+
/** Controls row (search, faceted filters, custom buttons). Render-prop gets the live table. */
|
|
184
|
+
toolbar?: React.ReactNode | ((table: Table<TData>) => React.ReactNode);
|
|
185
|
+
/** Overlay content when selection is non-empty. Only meaningful with `rowSelection` wired. */
|
|
186
|
+
bulkActions?: (ctx: DataTableBulkActionContext<TData>) => React.ReactNode;
|
|
187
|
+
/**
|
|
188
|
+
* Per-row actions → appends an actions column. First arg is `row.original`
|
|
189
|
+
* (matches the issue's `rowActions={(row) => …}`); `ctx` gives the TanStack row + table.
|
|
190
|
+
*/
|
|
191
|
+
rowActions?: (row: TData, ctx: { row: Row<TData>; table: Table<TData> }) => React.ReactNode;
|
|
192
|
+
/**
|
|
193
|
+
* Right-click accelerator: wraps each row in a context menu whose content is
|
|
194
|
+
* the returned nodes (author with `ContextMenuItem`/`ContextMenuSeparator`
|
|
195
|
+
* from the context-menu atom). Unlike `rowActions`, the trigger is the row
|
|
196
|
+
* itself, so the consumer supplies only the menu items — the core owns the
|
|
197
|
+
* `ContextMenu` chrome. Additive to `rowActions`: keep the visible `...` button
|
|
198
|
+
* as the discoverable, keyboard- and touch-accessible path and use this as a
|
|
199
|
+
* power-user shortcut for the same actions. `ctx` mirrors `rowActions`.
|
|
200
|
+
*/
|
|
201
|
+
contextActions?: (row: TData, ctx: { row: Row<TData>; table: Table<TData> }) => React.ReactNode;
|
|
202
|
+
/** Replaces the built-in "No results" cell. */
|
|
203
|
+
emptyState?: React.ReactNode;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Row-render seam. Overrides how each row's JSX is produced, so consumers can
|
|
207
|
+
* layer content the default cell grid can't express — full-width utility rows
|
|
208
|
+
* (a single `TableCell` with `colSpan={ctx.columnCount}`), or per-row wrappers
|
|
209
|
+
* (draggable / expandable rows). `ctx.defaultRow` is the built-in `<TableRow>`;
|
|
210
|
+
* return it to keep the default rendering for a given row. Called for every row
|
|
211
|
+
* in the current model. Complements `setTableOptions` (which shapes the row
|
|
212
|
+
* MODEL) by owning the row JSX — together they restore the donor's
|
|
213
|
+
* `isUtilityRow`/`renderUtilityRow`/`DraggableRow` seam without the core reading
|
|
214
|
+
* `meta`.
|
|
215
|
+
*/
|
|
216
|
+
renderRow?: (
|
|
217
|
+
row: Row<TData>,
|
|
218
|
+
ctx: { table: Table<TData>; columnCount: number; defaultRow: React.ReactNode },
|
|
219
|
+
) => React.ReactNode;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Escape hatch (donor's `setTableOptions` name kept for port recognisability):
|
|
223
|
+
* mutate `TableOptions` before `useReactTable` — expanded rows, drag meta,
|
|
224
|
+
* utility rows. Applied last.
|
|
225
|
+
*/
|
|
226
|
+
setTableOptions?: (options: TableOptions<TData>) => TableOptions<TData>;
|
|
227
|
+
|
|
228
|
+
labels?: DataTableLabels;
|
|
229
|
+
className?: string; // outer div[data-slot="data-table"]
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Cell/header helper types — the donor's `DataTableDisplayComponent` shape, so
|
|
234
|
+
* registry-resolved display components drop straight into `ColumnDef.cell`.
|
|
235
|
+
*/
|
|
236
|
+
export type DataTableCellComponent<TData, TValue = unknown> = (
|
|
237
|
+
ctx: CellContext<TData, TValue>,
|
|
238
|
+
) => React.ReactNode;
|
|
239
|
+
export type DataTableHeaderComponent<TData, TValue = unknown> = (
|
|
240
|
+
ctx: HeaderContext<TData, TValue>,
|
|
241
|
+
) => React.ReactNode;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { Column, Table } from '@tanstack/react-table';
|
|
4
|
+
import { Button } from '@vendure-io/ui/components/atoms/button';
|
|
5
|
+
import {
|
|
6
|
+
DropdownMenu,
|
|
7
|
+
DropdownMenuCheckboxItem,
|
|
8
|
+
DropdownMenuContent,
|
|
9
|
+
DropdownMenuGroup,
|
|
10
|
+
DropdownMenuLabel,
|
|
11
|
+
DropdownMenuSeparator,
|
|
12
|
+
DropdownMenuTrigger,
|
|
13
|
+
} from '@vendure-io/ui/components/atoms/dropdown-menu';
|
|
14
|
+
import { getHideableLeafColumns } from '@vendure-io/ui/components/molecules/data-table/data-table-helpers';
|
|
15
|
+
import { SlidersHorizontalIcon } from 'lucide-react';
|
|
16
|
+
|
|
17
|
+
// The column-visibility gear. Lists only hideable columns (`getCanHide()`), so
|
|
18
|
+
// the always-on `select`/`actions` display columns never appear here. Labels
|
|
19
|
+
// come from a string `ColumnDef.header`, falling back to the column id — `meta`
|
|
20
|
+
// is deliberately never read (it belongs to the consumer, make-or-break #7).
|
|
21
|
+
|
|
22
|
+
interface DataTableViewOptionsProps<TData> {
|
|
23
|
+
table: Table<TData>;
|
|
24
|
+
triggerLabel?: string;
|
|
25
|
+
heading?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function columnLabel<TData>(column: Column<TData, unknown>): string {
|
|
29
|
+
const header = column.columnDef.header;
|
|
30
|
+
return typeof header === 'string' ? header : column.id;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function DataTableViewOptions<TData>({
|
|
34
|
+
table,
|
|
35
|
+
triggerLabel = 'Columns',
|
|
36
|
+
heading = 'Toggle columns',
|
|
37
|
+
}: DataTableViewOptionsProps<TData>) {
|
|
38
|
+
const hideableColumns = getHideableLeafColumns(table);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<DropdownMenu>
|
|
42
|
+
<DropdownMenuTrigger
|
|
43
|
+
data-slot="data-table-view-options"
|
|
44
|
+
render={
|
|
45
|
+
<Button variant="outline" size="sm">
|
|
46
|
+
<SlidersHorizontalIcon />
|
|
47
|
+
{triggerLabel}
|
|
48
|
+
</Button>
|
|
49
|
+
}
|
|
50
|
+
/>
|
|
51
|
+
<DropdownMenuContent align="end" className="w-44">
|
|
52
|
+
{/* Base UI ties `GroupLabel` to a `Group` via context: the heading names
|
|
53
|
+
the toggle group and errors if rendered outside one. */}
|
|
54
|
+
<DropdownMenuGroup>
|
|
55
|
+
<DropdownMenuLabel>{heading}</DropdownMenuLabel>
|
|
56
|
+
<DropdownMenuSeparator />
|
|
57
|
+
{hideableColumns.map((column) => (
|
|
58
|
+
<DropdownMenuCheckboxItem
|
|
59
|
+
key={column.id}
|
|
60
|
+
className="capitalize"
|
|
61
|
+
checked={column.getIsVisible()}
|
|
62
|
+
onCheckedChange={(checked) => column.toggleVisibility(checked)}
|
|
63
|
+
>
|
|
64
|
+
{columnLabel(column)}
|
|
65
|
+
</DropdownMenuCheckboxItem>
|
|
66
|
+
))}
|
|
67
|
+
</DropdownMenuGroup>
|
|
68
|
+
</DropdownMenuContent>
|
|
69
|
+
</DropdownMenu>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { DataTableViewOptions };
|