aplance-ui 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/LICENSE +21 -0
- package/README.md +29 -0
- package/dist/api.d.ts +175 -0
- package/dist/api.js +1954 -0
- package/dist/api.js.map +1 -0
- package/dist/frameworks/astro/astro.config.snippet.md +23 -0
- package/dist/frameworks/astro/postcss.config.mjs.template +20 -0
- package/dist/frameworks/astro/vite-inline.snippet.md +22 -0
- package/dist/frameworks/electron-vite/vite.config.snippet.md +44 -0
- package/dist/frameworks/next/layout-import.snippet.md +19 -0
- package/dist/frameworks/next/next.config.snippet.md +15 -0
- package/dist/frameworks/next/postcss.config.js.template +16 -0
- package/dist/frameworks/tanstack-start/postcss.config.ts.template +17 -0
- package/dist/frameworks/tanstack-start/vite-inline.snippet.md +22 -0
- package/dist/frameworks/vite-react/postcss.config.ts.template +16 -0
- package/dist/frameworks/vite-react/vite-inline.snippet.md +20 -0
- package/dist/frameworks/vite-react/vite.config.ts.template +48 -0
- package/dist/index.js +2047 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
- package/registry/components/primitives/icon/icon.module.css +63 -0
- package/registry/components/primitives/icon/icon.tsx +53 -0
- package/registry/components/primitives/modal/modal-context.ts +19 -0
- package/registry/components/primitives/modal/modal.module.css +415 -0
- package/registry/components/primitives/modal/modal.tsx +550 -0
- package/registry/components/primitives/popover/popover-context.ts +28 -0
- package/registry/components/primitives/popover/popover.module.css +28 -0
- package/registry/components/primitives/popover/popover.tsx +465 -0
- package/registry/components/ui/accordion/accordion.module.css +208 -0
- package/registry/components/ui/accordion/accordion.tsx +327 -0
- package/registry/components/ui/avatar/avatar.module.css +32 -0
- package/registry/components/ui/avatar/avatar.tsx +37 -0
- package/registry/components/ui/badge/badge.module.css +103 -0
- package/registry/components/ui/badge/badge.tsx +29 -0
- package/registry/components/ui/button/button.module.css +216 -0
- package/registry/components/ui/button/button.tsx +133 -0
- package/registry/components/ui/chart/chart.module.css +168 -0
- package/registry/components/ui/chart/chart.tsx +1293 -0
- package/registry/components/ui/chart/types.ts +210 -0
- package/registry/components/ui/chart/utils.ts +59 -0
- package/registry/components/ui/copy-button/copy-button.module.css +21 -0
- package/registry/components/ui/copy-button/copy-button.tsx +123 -0
- package/registry/components/ui/countdown/countdown.module.css +5 -0
- package/registry/components/ui/countdown/countdown.tsx +152 -0
- package/registry/components/ui/data-table/checkbox-column.tsx +59 -0
- package/registry/components/ui/data-table/data-table.module.css +594 -0
- package/registry/components/ui/data-table/data-table.tsx +1109 -0
- package/registry/components/ui/data-table/filter-bar.tsx +33 -0
- package/registry/components/ui/data-table/pagination-bar.tsx +125 -0
- package/registry/components/ui/data-table/types.ts +594 -0
- package/registry/components/ui/data-table/useDataTable.tsx +573 -0
- package/registry/components/ui/data-table/utils.ts +445 -0
- package/registry/components/ui/empty-state/empty-state.module.css +26 -0
- package/registry/components/ui/empty-state/empty-state.tsx +32 -0
- package/registry/components/ui/input-fields/color-picker/color-palette.module.css +102 -0
- package/registry/components/ui/input-fields/color-picker/color-palette.tsx +92 -0
- package/registry/components/ui/input-fields/color-picker/color-picker.module.css +29 -0
- package/registry/components/ui/input-fields/color-picker/color-picker.tsx +168 -0
- package/registry/components/ui/input-fields/common/field-message/field-message.module.css +28 -0
- package/registry/components/ui/input-fields/common/field-message/field-message.tsx +39 -0
- package/registry/components/ui/input-fields/common/input-field-common.module.css +318 -0
- package/registry/components/ui/input-fields/common/input-field-native.module.css +267 -0
- package/registry/components/ui/input-fields/common/picker-popover-common.module.css +40 -0
- package/registry/components/ui/input-fields/common/select-button/select-button.module.css +9 -0
- package/registry/components/ui/input-fields/common/select-button/select-button.tsx +171 -0
- package/registry/components/ui/input-fields/common/types.ts +38 -0
- package/registry/components/ui/input-fields/custom-select/custom-select.module.css +56 -0
- package/registry/components/ui/input-fields/custom-select/custom-select.tsx +403 -0
- package/registry/components/ui/input-fields/date-picker/calendar.module.css +311 -0
- package/registry/components/ui/input-fields/date-picker/calendar.tsx +186 -0
- package/registry/components/ui/input-fields/date-picker/date-picker.module.css +57 -0
- package/registry/components/ui/input-fields/date-picker/date-picker.tsx +945 -0
- package/registry/components/ui/input-fields/date-picker/types.ts +102 -0
- package/registry/components/ui/input-fields/date-picker/utils.ts +236 -0
- package/registry/components/ui/input-fields/emoji-picker/emoji-palette.module.css +90 -0
- package/registry/components/ui/input-fields/emoji-picker/emoji-palette.tsx +168 -0
- package/registry/components/ui/input-fields/emoji-picker/emoji-picker.module.css +8 -0
- package/registry/components/ui/input-fields/emoji-picker/emoji-picker.tsx +102 -0
- package/registry/components/ui/input-fields/emoji-picker/utils.ts +505 -0
- package/registry/components/ui/input-fields/field-group/field-group.module.css +86 -0
- package/registry/components/ui/input-fields/field-group/field-group.tsx +86 -0
- package/registry/components/ui/input-fields/input/input.tsx +319 -0
- package/registry/components/ui/input-fields/media-input/kind-icons.tsx +62 -0
- package/registry/components/ui/input-fields/media-input/media-input.module.css +294 -0
- package/registry/components/ui/input-fields/media-input/media-input.tsx +327 -0
- package/registry/components/ui/input-fields/media-input/media-preview.tsx +101 -0
- package/registry/components/ui/input-fields/media-input/types.ts +129 -0
- package/registry/components/ui/input-fields/media-input/useMediaUpload.tsx +302 -0
- package/registry/components/ui/input-fields/media-input/utils.ts +325 -0
- package/registry/components/ui/input-fields/otp-input/otp-input.module.css +24 -0
- package/registry/components/ui/input-fields/otp-input/otp-input.tsx +291 -0
- package/registry/components/ui/input-fields/select/select.tsx +118 -0
- package/registry/components/ui/input-fields/switch/switch.module.css +70 -0
- package/registry/components/ui/input-fields/switch/switch.tsx +59 -0
- package/registry/components/ui/input-fields/textarea/textarea.tsx +133 -0
- package/registry/components/ui/input-fields/time-picker/time-picker.module.css +57 -0
- package/registry/components/ui/input-fields/time-picker/time-picker.tsx +437 -0
- package/registry/components/ui/loader/loader.module.css +156 -0
- package/registry/components/ui/loader/loader.tsx +102 -0
- package/registry/components/ui/stepper/stepper.module.css +294 -0
- package/registry/components/ui/stepper/stepper.tsx +197 -0
- package/registry/components/ui/tabs/tabs.module.css +150 -0
- package/registry/components/ui/tabs/tabs.tsx +134 -0
- package/registry/components/ui/toast/toast.module.css +159 -0
- package/registry/components/ui/toast/toast.tsx +104 -0
- package/registry/components/ui/tooltip/tooltip.module.css +82 -0
- package/registry/components/ui/tooltip/tooltip.tsx +72 -0
- package/registry/global.d.ts +4 -0
- package/registry/hooks/useFocusOutside.tsx +33 -0
- package/registry/hooks/useResizeObserver.tsx +32 -0
- package/registry/lib/dom.ts +25 -0
- package/registry/lib/string.ts +23 -0
- package/registry/registry.json +719 -0
- package/registry/styles/app.css +240 -0
- package/registry/styles/components.css +78 -0
- package/registry/styles/fonts/geist-regular.woff2 +0 -0
- package/registry/styles/globals.css +7 -0
- package/registry/styles/media.css +25 -0
- package/registry/styles/utilities.css +90 -0
- package/registry/themes/colors/blue.css +37 -0
- package/registry/themes/colors/green.css +37 -0
- package/registry/themes/colors/rose.css +37 -0
- package/registry/themes/colors/slate.css +37 -0
- package/registry/themes/colors/stone.css +37 -0
- package/registry/themes/colors/violet.css +37 -0
- package/registry/themes/colors/zinc.css +37 -0
- package/registry/themes/radius/pill.css +8 -0
- package/registry/themes/radius/rounded.css +8 -0
- package/registry/themes/radius/sharp.css +8 -0
- package/registry/themes/spacing/compact.css +17 -0
- package/registry/themes/spacing/spacious.css +17 -0
- package/registry/tsconfig.json +25 -0
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Cell,
|
|
3
|
+
ColumnDef,
|
|
4
|
+
ColumnOrderState,
|
|
5
|
+
ColumnPinningState,
|
|
6
|
+
ColumnSizingState,
|
|
7
|
+
ExpandedState,
|
|
8
|
+
GroupingState,
|
|
9
|
+
Header,
|
|
10
|
+
OnChangeFn,
|
|
11
|
+
RowData,
|
|
12
|
+
Row,
|
|
13
|
+
RowSelectionState,
|
|
14
|
+
SortingState,
|
|
15
|
+
Table,
|
|
16
|
+
TableMeta as TanstackTableMeta,
|
|
17
|
+
VisibilityState,
|
|
18
|
+
} from "@tanstack/react-table";
|
|
19
|
+
import type { ReactNode } from "react";
|
|
20
|
+
|
|
21
|
+
// ─── Column meta augmentation ─────────────────────────────────────────────────
|
|
22
|
+
// Attach presentation hints directly to the column definition so consumers
|
|
23
|
+
// stay in TanStack's idiomatic API surface (`columns: [{ ..., meta: {...} }]`)
|
|
24
|
+
// instead of inventing parallel prop maps on the table.
|
|
25
|
+
declare module "@tanstack/react-table" {
|
|
26
|
+
/* eslint-disable @typescript-eslint/no-unused-vars -- generics must mirror tanstack's signature exactly */
|
|
27
|
+
interface ColumnMeta<TData extends RowData, TValue> {
|
|
28
|
+
/** Horizontal alignment of cell + header content. Default: `'left'`. */
|
|
29
|
+
align?: "left" | "center" | "right";
|
|
30
|
+
/**
|
|
31
|
+
* Whether to truncate overflowing text with an ellipsis. Default: `true`.
|
|
32
|
+
* Set to `false` for cells that host interactive controls (inputs,
|
|
33
|
+
* selects, buttons) so focus rings and dropdowns are not clipped.
|
|
34
|
+
*/
|
|
35
|
+
truncate?: boolean;
|
|
36
|
+
/** Extra class merged onto the `<th>` element. */
|
|
37
|
+
headerClassName?: string;
|
|
38
|
+
/** Extra class merged onto the `<td>` element. */
|
|
39
|
+
cellClassName?: string;
|
|
40
|
+
}
|
|
41
|
+
/* eslint-enable @typescript-eslint/no-unused-vars */
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ─── Filter fields ────────────────────────────────────────────────────────────
|
|
45
|
+
// Two headless primitives - nothing else. Every concrete filter (search,
|
|
46
|
+
// select, date picker, custom slider, …) plugs in through the same
|
|
47
|
+
// `value`/`onChange` render-prop contract, so the data-table has zero
|
|
48
|
+
// compile-time dependency on any specific input component.
|
|
49
|
+
|
|
50
|
+
/** Any input-like field with a string value. Render anything that fulfils the API. */
|
|
51
|
+
export interface FilterInputField {
|
|
52
|
+
type: "field";
|
|
53
|
+
key: string;
|
|
54
|
+
/** Grid column width template. Default: `'1fr'` (shares remaining space). */
|
|
55
|
+
width?: string;
|
|
56
|
+
render: (api: {
|
|
57
|
+
value: string;
|
|
58
|
+
onChange: (value: string) => void;
|
|
59
|
+
}) => ReactNode;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Any non-value slot - button, menu, custom control, … */
|
|
63
|
+
export interface FilterAction {
|
|
64
|
+
type: "action";
|
|
65
|
+
/** Grid column width template. Default: `'auto'` (hugs content). */
|
|
66
|
+
width?: string;
|
|
67
|
+
render: () => ReactNode;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type FilterField = FilterInputField | FilterAction;
|
|
71
|
+
|
|
72
|
+
export interface FilterBarProps {
|
|
73
|
+
fields: FilterField[];
|
|
74
|
+
values: Record<string, string>;
|
|
75
|
+
onChange: (key: string, value: string) => void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─── Table meta ───────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
export interface DataTableMeta<TData> extends TanstackTableMeta<TData> {
|
|
81
|
+
editingRowId: string | null;
|
|
82
|
+
updateData: (rowId: string, columnId: string, value: unknown) => void;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ─── State persistence ────────────────────────────────────────────────────────
|
|
86
|
+
// `useDataTable` wires the table's persistable state slices to one of three
|
|
87
|
+
// destinations via its `persistState` option. The persisted blob shape
|
|
88
|
+
// (`PersistedState`) is identical across modes - the mode just decides where
|
|
89
|
+
// it lives.
|
|
90
|
+
|
|
91
|
+
/** Snapshot of every persistable table state slice. Each is optional. */
|
|
92
|
+
export interface PersistedState {
|
|
93
|
+
filterValues?: Record<string, string>;
|
|
94
|
+
pagination?: PaginationState;
|
|
95
|
+
sorting?: SortingState;
|
|
96
|
+
columnOrder?: ColumnOrderState;
|
|
97
|
+
columnVisibility?: VisibilityState;
|
|
98
|
+
columnPinning?: ColumnPinningState;
|
|
99
|
+
columnSizing?: ColumnSizingState;
|
|
100
|
+
grouping?: GroupingState;
|
|
101
|
+
expanded?: ExpandedState;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type PersistMode = "search-params" | "local-storage" | "manual";
|
|
105
|
+
|
|
106
|
+
/** Base shared by every mode. */
|
|
107
|
+
interface PersistStateBase {
|
|
108
|
+
enabled: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Fired whenever any persistable slice changes, *in addition* to the mode's
|
|
111
|
+
* primary destination (URL / localStorage / your handler). Useful for
|
|
112
|
+
* telemetry, debug inspectors, or mirroring state into React for display.
|
|
113
|
+
*/
|
|
114
|
+
onChange?: (state: PersistedState) => void;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
interface PersistSearchParams extends PersistStateBase {
|
|
118
|
+
mode: "search-params";
|
|
119
|
+
/**
|
|
120
|
+
* Optional URL param renames. Keys are the canonical state names
|
|
121
|
+
* (`pageIndex`, `sortBy`, filter keys, etc.); values are the URL params
|
|
122
|
+
* actually written. Unmapped keys default to their canonical name.
|
|
123
|
+
*/
|
|
124
|
+
paramMap?: Partial<Record<string, string>>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface PersistLocalStorage extends PersistStateBase {
|
|
128
|
+
mode: "local-storage";
|
|
129
|
+
/** Storage key for the JSON blob. */
|
|
130
|
+
storageKey: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
interface PersistManual extends PersistStateBase {
|
|
134
|
+
mode: "manual";
|
|
135
|
+
/** Hydrated once on mount. */
|
|
136
|
+
initialState?: PersistedState;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export type PersistState =
|
|
140
|
+
| PersistSearchParams
|
|
141
|
+
| PersistLocalStorage
|
|
142
|
+
| PersistManual;
|
|
143
|
+
|
|
144
|
+
// ─── Pagination ───────────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
export interface PaginationState {
|
|
147
|
+
pageIndex: number;
|
|
148
|
+
pageSize: number;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface PaginationBarProps<TData> {
|
|
152
|
+
table: Table<TData>;
|
|
153
|
+
pagination: PaginationState;
|
|
154
|
+
onPaginationChange: (next: PaginationState) => void;
|
|
155
|
+
pageSizeOptions: number[];
|
|
156
|
+
showPageSizeSelect: boolean;
|
|
157
|
+
totalCount: number | undefined;
|
|
158
|
+
rowLabel: string;
|
|
159
|
+
rowsSelectable: boolean;
|
|
160
|
+
selectedCount: number;
|
|
161
|
+
extra: ReactNode;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ─── Re-exports for column-control state ─────────────────────────────────────
|
|
165
|
+
// Re-exposed from tanstack so consumers do not need to import two modules.
|
|
166
|
+
export type {
|
|
167
|
+
ColumnOrderState,
|
|
168
|
+
ColumnPinningState,
|
|
169
|
+
ColumnSizingState,
|
|
170
|
+
ExpandedState,
|
|
171
|
+
GroupingState,
|
|
172
|
+
VisibilityState,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// ─── Main DataTable props ─────────────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
export interface DataTableProps<TData extends { id: string }> {
|
|
178
|
+
data: TData[];
|
|
179
|
+
/**
|
|
180
|
+
* TanStack Table's ColumnDef is invariant on its value type parameter.
|
|
181
|
+
* `ColumnDef<Email, string>` is NOT assignable to `ColumnDef<Email, unknown>`
|
|
182
|
+
* because `footer`/`header` templates place the value param in a contravariant
|
|
183
|
+
* position. `any` is the correct solution - it is bivariant (simultaneously
|
|
184
|
+
* supertype and subtype of every type), which is the only param that lets
|
|
185
|
+
* columns of any concrete value type pass here without errors.
|
|
186
|
+
* This matches how TanStack Table itself types its own internal APIs.
|
|
187
|
+
*
|
|
188
|
+
* In consumers: do NOT write `useMemo<ColumnDef<X, unknown>[]>(...)`.
|
|
189
|
+
* Just write `useMemo(() => [...], [deps])` - let TypeScript infer the
|
|
190
|
+
* column types. The inferred union widens correctly into `ColumnDef<X, any>`
|
|
191
|
+
* at the prop boundary with zero errors.
|
|
192
|
+
*/
|
|
193
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
194
|
+
columns: ColumnDef<TData, any>[];
|
|
195
|
+
|
|
196
|
+
isLoading?: boolean;
|
|
197
|
+
emptyMessage?: string;
|
|
198
|
+
|
|
199
|
+
filters?: FilterField[];
|
|
200
|
+
filterValues?: Record<string, string>;
|
|
201
|
+
onFilterChange?: (key: string, value: string) => void;
|
|
202
|
+
|
|
203
|
+
sorting: SortingState;
|
|
204
|
+
onSortingChange: OnChangeFn<SortingState>;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* When true, TanStack tracks sorting state but does NOT sort data
|
|
208
|
+
* client-side. Use for server-side sorting where the API returns
|
|
209
|
+
* pre-sorted results. Default: `false`.
|
|
210
|
+
*/
|
|
211
|
+
manualSorting?: boolean;
|
|
212
|
+
|
|
213
|
+
showPagination?: boolean;
|
|
214
|
+
pagination?: PaginationState;
|
|
215
|
+
onPaginationChange?: (next: PaginationState) => void;
|
|
216
|
+
totalPages?: number;
|
|
217
|
+
totalCount?: number;
|
|
218
|
+
pageSizeOptions?: number[];
|
|
219
|
+
showPageSizeSelect?: boolean;
|
|
220
|
+
rowLabel?: string;
|
|
221
|
+
|
|
222
|
+
rowsSelectable?: boolean;
|
|
223
|
+
rowSelection?: RowSelectionState;
|
|
224
|
+
onRowSelectionChange?: OnChangeFn<RowSelectionState>;
|
|
225
|
+
|
|
226
|
+
editingRowId?: string | null;
|
|
227
|
+
onUpdateData?: (rowId: string, columnId: string, value: unknown) => void;
|
|
228
|
+
|
|
229
|
+
// ─── Column controls (all opt-in) ───────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Called whenever any internal data changes (e.g. from inline edits or drag-and-drop).
|
|
233
|
+
* Perfect for triggering API updates.
|
|
234
|
+
*/
|
|
235
|
+
onDataChange?: (data: TData[]) => void;
|
|
236
|
+
|
|
237
|
+
/** Enable drag-and-drop row reordering. Fires `onDataChange` when reordered if using `useDataTable`, or `onRowOrderChange` if fully controlled. */
|
|
238
|
+
enableRowOrdering?: boolean;
|
|
239
|
+
onRowOrderChange?: (draggedRow: TData, targetRow: TData) => void;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Drag-and-drop column reordering via the header.
|
|
243
|
+
* Pass `columnOrder` + `onColumnOrderChange` to control externally,
|
|
244
|
+
* or omit to use the default order from `columns`.
|
|
245
|
+
*/
|
|
246
|
+
enableColumnOrdering?: boolean;
|
|
247
|
+
columnOrder?: ColumnOrderState;
|
|
248
|
+
onColumnOrderChange?: OnChangeFn<ColumnOrderState>;
|
|
249
|
+
|
|
250
|
+
/** Column resize handles on the right edge of each header cell. */
|
|
251
|
+
enableColumnResizing?: boolean;
|
|
252
|
+
columnSizing?: ColumnSizingState;
|
|
253
|
+
onColumnSizingChange?: OnChangeFn<ColumnSizingState>;
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Sticky column pinning. When enabled, pinned columns stay visible
|
|
257
|
+
* during horizontal scroll. Per-column opt-out: `enablePinning: false`.
|
|
258
|
+
*/
|
|
259
|
+
enableColumnPinning?: boolean;
|
|
260
|
+
columnPinning?: ColumnPinningState;
|
|
261
|
+
onColumnPinningChange?: OnChangeFn<ColumnPinningState>;
|
|
262
|
+
defaultColumnPinning?: ColumnPinningState;
|
|
263
|
+
|
|
264
|
+
/** Show/hide columns. Pass with a UI to let users toggle visibility. */
|
|
265
|
+
columnVisibility?: VisibilityState;
|
|
266
|
+
onColumnVisibilityChange?: OnChangeFn<VisibilityState>;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Row grouping by column id. Renders a collapsible group row per
|
|
270
|
+
* unique value. Pair with a UI control to add/remove grouping columns.
|
|
271
|
+
*/
|
|
272
|
+
enableGrouping?: boolean;
|
|
273
|
+
grouping?: GroupingState;
|
|
274
|
+
onGroupingChange?: OnChangeFn<GroupingState>;
|
|
275
|
+
expanded?: ExpandedState;
|
|
276
|
+
onExpandedChange?: OnChangeFn<ExpandedState>;
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Visual: draw 1px vertical dividers between columns in both the header
|
|
280
|
+
* and body. Useful for spreadsheet-style tables. Default: `false`.
|
|
281
|
+
*/
|
|
282
|
+
showVerticalBorders?: boolean;
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Called when a data row is clicked. Receives the row's original data.
|
|
286
|
+
* Adds cursor-pointer to rows automatically when provided.
|
|
287
|
+
*/
|
|
288
|
+
onRow?: (row: TData) => void;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Slot rendered inside the pagination bar next to the count text.
|
|
292
|
+
* Perfect for bulk-action buttons. Does not shift table layout.
|
|
293
|
+
*/
|
|
294
|
+
paginationExtra?: ReactNode;
|
|
295
|
+
|
|
296
|
+
className?: string;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ─── Internal subcomponent props ──────────────────────────────────────────────
|
|
300
|
+
// Kept here (rather than next to each subcomponent) so all data-table types
|
|
301
|
+
// live in one place. Subcomponents are not exported, but their prop shapes
|
|
302
|
+
// matter for review and editor tooling.
|
|
303
|
+
|
|
304
|
+
export interface HeaderActionButtonProps {
|
|
305
|
+
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
306
|
+
ariaLabel: string;
|
|
307
|
+
ariaPressed?: boolean;
|
|
308
|
+
active?: boolean;
|
|
309
|
+
children: ReactNode;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export interface HeaderCellProps<TData> {
|
|
313
|
+
header: Header<TData, unknown>;
|
|
314
|
+
extraWidth: number;
|
|
315
|
+
enableColumnOrdering: boolean;
|
|
316
|
+
enableColumnResizing: boolean;
|
|
317
|
+
enableColumnPinning: boolean;
|
|
318
|
+
enableGrouping: boolean;
|
|
319
|
+
draggedColumnId: string | null;
|
|
320
|
+
dropTargetColumnId: string | null;
|
|
321
|
+
onDragStartCol: (id: string) => void;
|
|
322
|
+
onDragEndCol: () => void;
|
|
323
|
+
onDragOverCol: (id: string | null) => void;
|
|
324
|
+
onDropCol: (sourceId: string, targetId: string) => void;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export interface BodyRowProps<TData extends { id: string }> {
|
|
328
|
+
row: Row<TData>;
|
|
329
|
+
editingRowId: string | null;
|
|
330
|
+
onRow?: (row: TData) => void;
|
|
331
|
+
enableGrouping: boolean;
|
|
332
|
+
columnExtraWidths: Record<string, number>;
|
|
333
|
+
enableRowOrdering?: boolean;
|
|
334
|
+
draggedRowId?: string | null;
|
|
335
|
+
dropTargetRowId?: string | null;
|
|
336
|
+
onDragStartRow?: (id: string) => void;
|
|
337
|
+
onDragOverRow?: (id: string | null) => void;
|
|
338
|
+
onDragEndRow?: () => void;
|
|
339
|
+
onDropRow?: (draggedId: string, targetId: string) => void;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export interface BodyCellProps<TData> {
|
|
343
|
+
cell: Cell<TData, unknown>;
|
|
344
|
+
extraWidth: number;
|
|
345
|
+
groupSourceCell?: Cell<TData, unknown>;
|
|
346
|
+
isGroupAnchorCell: boolean;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ─── useDataTable hook ────────────────────────────────────────────────────────
|
|
350
|
+
|
|
351
|
+
export interface UseDataTableOptions<TData extends { id: string }> {
|
|
352
|
+
/** Full dataset. The hook slices it per page automatically (client-side mode). */
|
|
353
|
+
data: TData[];
|
|
354
|
+
|
|
355
|
+
/** Initial page size. Default: 10. */
|
|
356
|
+
pageSize?: number;
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Initial filter values keyed by filter field key. The keys here also act
|
|
360
|
+
* as the "universe" of filter keys when persisting state - only these keys
|
|
361
|
+
* are read from / written to the URL or storage.
|
|
362
|
+
* @example { search: '', status: '' }
|
|
363
|
+
*/
|
|
364
|
+
defaultFilterValues?: Record<string, string>;
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Client-side filter function.
|
|
368
|
+
* Called with the full dataset + current filter values.
|
|
369
|
+
* Return a filtered subset - the hook handles paging on top of it.
|
|
370
|
+
*
|
|
371
|
+
* If omitted, the full dataset is used as-is (no client filtering).
|
|
372
|
+
*
|
|
373
|
+
* @example
|
|
374
|
+
* filterFn: (rows, filters) =>
|
|
375
|
+
* rows.filter(r =>
|
|
376
|
+
* !filters.search || r.name.toLowerCase().includes(filters.search.toLowerCase())
|
|
377
|
+
* )
|
|
378
|
+
*/
|
|
379
|
+
filterFn?: (rows: TData[], filters: Record<string, string>) => TData[];
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Called whenever a filter value changes (after internal state update).
|
|
383
|
+
* Use for server-side filtering: trigger your API call here.
|
|
384
|
+
* pageIndex is automatically reset to 0 on every filter change.
|
|
385
|
+
*/
|
|
386
|
+
onFilterChange?: (
|
|
387
|
+
key: string,
|
|
388
|
+
value: string,
|
|
389
|
+
allValues: Record<string, string>,
|
|
390
|
+
) => void;
|
|
391
|
+
|
|
392
|
+
/** Called whenever pagination changes (page or page size). */
|
|
393
|
+
onPaginationChange?: (next: PaginationState) => void;
|
|
394
|
+
|
|
395
|
+
/** Called whenever sorting changes. */
|
|
396
|
+
onSortingChange?: (next: SortingState) => void;
|
|
397
|
+
|
|
398
|
+
/** Called whenever row selection changes. */
|
|
399
|
+
onRowSelectionChange?: (next: RowSelectionState) => void;
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Called whenever internal data changes (e.g. from inline edits or drag-and-drop).
|
|
403
|
+
* Used to trigger API sync.
|
|
404
|
+
*/
|
|
405
|
+
onDataChange?: (data: TData[]) => void;
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* If true (default), the hook slices `data` for the current page.
|
|
409
|
+
* Set to false when using server-side pagination - you control `data` externally
|
|
410
|
+
* and just want the hook to manage state.
|
|
411
|
+
*/
|
|
412
|
+
clientSidePagination?: boolean;
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* When `true`, sorting state is tracked but not applied to data - your code
|
|
416
|
+
* is responsible for returning pre-sorted rows (typically via a server API).
|
|
417
|
+
* Default: `false` (rows are sorted client-side).
|
|
418
|
+
*/
|
|
419
|
+
manualSorting?: boolean;
|
|
420
|
+
|
|
421
|
+
/** Total record count for server-side scenarios. */
|
|
422
|
+
totalCount?: number;
|
|
423
|
+
/** Total page count for server-side scenarios. */
|
|
424
|
+
totalPages?: number;
|
|
425
|
+
|
|
426
|
+
// ─── Column controls (all opt-in) ───────────────────────────────────────────
|
|
427
|
+
|
|
428
|
+
/** Initial drag-drop column order. */
|
|
429
|
+
defaultColumnOrder?: ColumnOrderState;
|
|
430
|
+
/** Initial column visibility map. */
|
|
431
|
+
defaultColumnVisibility?: VisibilityState;
|
|
432
|
+
/** Initial column pinning state. This also acts as the "anchors" for normalization. */
|
|
433
|
+
defaultColumnPinning?: ColumnPinningState;
|
|
434
|
+
/** Initial column sizing state. */
|
|
435
|
+
defaultColumnSizing?: ColumnSizingState;
|
|
436
|
+
/** Initial grouping state (column ids to group by). */
|
|
437
|
+
defaultGrouping?: GroupingState;
|
|
438
|
+
/** Initial expansion state. Default: `true` (all groups expanded). */
|
|
439
|
+
defaultExpanded?: ExpandedState;
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* State persistence - wires the hook's persistable slices to the URL,
|
|
443
|
+
* localStorage, or a manual handler. The persisted blob shape is the same
|
|
444
|
+
* across modes; the mode just decides where it lives.
|
|
445
|
+
*/
|
|
446
|
+
persistState?: PersistState;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export interface UseDataTableState<TData extends { id: string }> {
|
|
450
|
+
filterValues: Record<string, string>;
|
|
451
|
+
pagination: PaginationState;
|
|
452
|
+
sorting: SortingState;
|
|
453
|
+
rowSelection: RowSelectionState;
|
|
454
|
+
editingRowId: string | null;
|
|
455
|
+
columnOrder: ColumnOrderState;
|
|
456
|
+
columnVisibility: VisibilityState;
|
|
457
|
+
columnPinning: ColumnPinningState;
|
|
458
|
+
columnSizing: ColumnSizingState;
|
|
459
|
+
grouping: GroupingState;
|
|
460
|
+
expanded: ExpandedState;
|
|
461
|
+
/** Rows in the current page (already sliced when clientSidePagination=true) */
|
|
462
|
+
pageData: TData[];
|
|
463
|
+
/** All rows after filtering (before paging) */
|
|
464
|
+
filteredData: TData[];
|
|
465
|
+
/** IDs of selected rows */
|
|
466
|
+
selectedIds: string[];
|
|
467
|
+
/** Full row objects that are selected */
|
|
468
|
+
selectedRows: TData[];
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export interface UseDataTableActions {
|
|
472
|
+
/** Set a single filter value (resets page to 0 automatically) */
|
|
473
|
+
setFilter: (key: string, value: string) => void;
|
|
474
|
+
/** Set multiple filter values at once (resets page to 0 automatically) */
|
|
475
|
+
setFilters: (values: Record<string, string>) => void;
|
|
476
|
+
/** Reset all filters to their default values */
|
|
477
|
+
resetFilters: () => void;
|
|
478
|
+
/** Go to a specific page index (0-based) */
|
|
479
|
+
goToPage: (index: number) => void;
|
|
480
|
+
/** Set page size (resets page to 0 automatically) */
|
|
481
|
+
setPageSize: (size: number) => void;
|
|
482
|
+
/** Clear row selection */
|
|
483
|
+
clearSelection: () => void;
|
|
484
|
+
/** Start editing a row by id (saves snapshot for cancel) */
|
|
485
|
+
startEdit: (rowId: string) => void;
|
|
486
|
+
/** Cancel editing - restores row to snapshot */
|
|
487
|
+
cancelEdit: () => void;
|
|
488
|
+
/** Confirm save - clears editing state (actual save is your responsibility) */
|
|
489
|
+
confirmSave: () => void;
|
|
490
|
+
/** Update a single field on a row (for inline editing) */
|
|
491
|
+
updateRowData: (rowId: string, columnId: string, value: unknown) => void;
|
|
492
|
+
/** Toggle visibility of a single column. */
|
|
493
|
+
toggleColumnVisibility: (columnId: string) => void;
|
|
494
|
+
/** Replace the visibility map. */
|
|
495
|
+
setColumnVisibility: (next: VisibilityState) => void;
|
|
496
|
+
/** Replace the column order. */
|
|
497
|
+
setColumnOrder: (next: ColumnOrderState) => void;
|
|
498
|
+
/** Replace the column pinning state. */
|
|
499
|
+
setColumnPinning: (next: ColumnPinningState) => void;
|
|
500
|
+
/** Replace the column sizing state. */
|
|
501
|
+
setColumnSizing: (next: ColumnSizingState) => void;
|
|
502
|
+
/** Toggle grouping on/off for a column id. */
|
|
503
|
+
toggleGrouping: (columnId: string) => void;
|
|
504
|
+
/** Replace the grouping state. */
|
|
505
|
+
setGrouping: (next: GroupingState) => void;
|
|
506
|
+
/** Replace the expansion state. */
|
|
507
|
+
setExpanded: (next: ExpandedState) => void;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export interface UseDataTableReturn<TData extends { id: string }> {
|
|
511
|
+
/**
|
|
512
|
+
* Spread directly onto <DataTable> - contains all wired props.
|
|
513
|
+
* Only `columns` and `filters` are omitted since those are defined by the consumer.
|
|
514
|
+
*
|
|
515
|
+
* @example
|
|
516
|
+
* <DataTable {...table.props} columns={columns} filters={filters} />
|
|
517
|
+
*/
|
|
518
|
+
props: Omit<DataTableProps<TData>, "columns" | "filters" | "className">;
|
|
519
|
+
/** Current table state - use to build columns, bulk actions, etc. */
|
|
520
|
+
state: UseDataTableState<TData>;
|
|
521
|
+
/** Imperative helpers */
|
|
522
|
+
actions: UseDataTableActions;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ─── Internal persist-wiring args ─────────────────────────────────────────────
|
|
526
|
+
// Used by the `usePersist` helper inside `useDataTable` and the pure
|
|
527
|
+
// snapshot/apply helpers in `utils.ts`.
|
|
528
|
+
|
|
529
|
+
export interface PersistArgs {
|
|
530
|
+
config: PersistState | undefined;
|
|
531
|
+
filterKeys: string[];
|
|
532
|
+
filterValues: Record<string, string>;
|
|
533
|
+
setFilterValues: (next: Record<string, string>) => void;
|
|
534
|
+
pagination: PaginationState;
|
|
535
|
+
setPagination: (next: PaginationState) => void;
|
|
536
|
+
sorting: SortingState;
|
|
537
|
+
setSorting: (next: SortingState) => void;
|
|
538
|
+
columnOrder: ColumnOrderState;
|
|
539
|
+
setColumnOrder: (next: ColumnOrderState) => void;
|
|
540
|
+
columnVisibility: VisibilityState;
|
|
541
|
+
setColumnVisibility: (next: VisibilityState) => void;
|
|
542
|
+
columnPinning: ColumnPinningState;
|
|
543
|
+
setColumnPinning: (next: ColumnPinningState) => void;
|
|
544
|
+
columnSizing: ColumnSizingState;
|
|
545
|
+
setColumnSizing: (next: ColumnSizingState) => void;
|
|
546
|
+
grouping: GroupingState;
|
|
547
|
+
setGrouping: (next: GroupingState) => void;
|
|
548
|
+
expanded: ExpandedState;
|
|
549
|
+
setExpanded: (next: ExpandedState) => void;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// ─── Snapshot / apply helpers shape ───────────────────────────────────────────
|
|
553
|
+
|
|
554
|
+
/** Inputs needed to produce a `PersistedState` snapshot from current state. */
|
|
555
|
+
export interface SnapshotInput {
|
|
556
|
+
filterValues: Record<string, string>;
|
|
557
|
+
pagination: PaginationState;
|
|
558
|
+
/** Used to skip persisting pagination when both index and size are at defaults. */
|
|
559
|
+
defaultPageSize: number;
|
|
560
|
+
sorting: SortingState;
|
|
561
|
+
columnOrder: ColumnOrderState;
|
|
562
|
+
columnVisibility: VisibilityState;
|
|
563
|
+
columnPinning: ColumnPinningState;
|
|
564
|
+
columnSizing: ColumnSizingState;
|
|
565
|
+
grouping: GroupingState;
|
|
566
|
+
expanded: ExpandedState;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/** Setters used by `applyPersistedState` to push hydrated values back into state. */
|
|
570
|
+
export interface ApplyPersistedTarget {
|
|
571
|
+
filterValues: Record<string, string>;
|
|
572
|
+
filterKeys: string[];
|
|
573
|
+
setFilterValues: (next: Record<string, string>) => void;
|
|
574
|
+
setPagination: (next: PaginationState) => void;
|
|
575
|
+
setSorting: (next: SortingState) => void;
|
|
576
|
+
setColumnOrder: (next: ColumnOrderState) => void;
|
|
577
|
+
setColumnVisibility: (next: VisibilityState) => void;
|
|
578
|
+
setColumnPinning: (next: ColumnPinningState) => void;
|
|
579
|
+
setColumnSizing: (next: ColumnSizingState) => void;
|
|
580
|
+
setGrouping: (next: GroupingState) => void;
|
|
581
|
+
setExpanded: (next: ExpandedState) => void;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Source for resolving the runtime column id from a raw `ColumnDef`. Mirrors
|
|
586
|
+
* how tanstack derives ids: explicit `id` first, then `accessorKey`. Typed
|
|
587
|
+
* structurally on the two fields actually read so callers can pass any
|
|
588
|
+
* concrete `ColumnDef<…>` without tripping tanstack's invariance on the
|
|
589
|
+
* value-type parameter.
|
|
590
|
+
*/
|
|
591
|
+
export interface ColumnIdSource {
|
|
592
|
+
id?: string;
|
|
593
|
+
accessorKey?: string | number | symbol;
|
|
594
|
+
}
|