flexi-datagrid 1.2.5 → 1.2.7
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 +299 -4
- package/dist/{DataTable.d.ts → Datatable.d.ts} +19 -1
- package/dist/flexi-datagrid.css +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.es.js +712 -322
- package/dist/index.umd.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,9 +11,17 @@ A modern, highly customizable, and feature-rich React data grid component design
|
|
|
11
11
|
- **Multi-State Sorting:** Clickable column headers that support Ascending (asc), Descending (desc), and Unsorted states.
|
|
12
12
|
- **Built-in Filtering:** Filter rows instantly with a clean popup search and multi-select dropdown for unique column values. The popup renders through a portal so it always floats above your grid content instead of being clipped by the table's scroll container.
|
|
13
13
|
- **Row Grouping (Drag-and-Drop):** Drag column headers into the grouping zone to group your dataset hierarchically with expand/collapse support.
|
|
14
|
+
- **Column Reordering (Drag-and-Drop):** Drag any header and drop it on another to reorder columns, AG Grid-style. Column groups move as a unit; columns can be reordered within their own group or at the top level.
|
|
15
|
+
- **Column Resizing:** Drag the handle on the right edge of a header to resize a column live. Supports per-column `minWidth`/`maxWidth`, opt-out via `resizable: false`, and an `onColumnResize` callback.
|
|
14
16
|
- **Nested/Grouped Headers:** Columns can be nested via `children` to render multi-level header rows.
|
|
15
17
|
- **Sticky Actions:** Optional custom row actions column locked to the right side.
|
|
16
18
|
- **Custom Cell Rendering:** Full control over how each cell is rendered via a `render` function.
|
|
19
|
+
- **Conditional Row Styling (AG Grid-style `rowClassRules`):** Map class names to predicates evaluated per row to highlight/style rows based on their data.
|
|
20
|
+
- **Row Click Events:** Pass `onRowClick` to react to clicks anywhere on a data row.
|
|
21
|
+
- **Row Spanning / Cell Merging:** Pass `rowSpanColumns` (e.g. `['city', 'department']`) to vertically merge consecutive rows that share the same value, hierarchically — like a spreadsheet merge.
|
|
22
|
+
- **Virtualized Rows for Large Datasets:** Only the rows in (and just around) the visible viewport are mounted, so grids with thousands of rows stay smooth to scroll and filter.
|
|
23
|
+
- **Display Settings Menu:** Opt-in toolbar (`controlPanel`) with a single Settings menu button that opens a dropdown for all display configuration — toggling pin/filter icon hover visibility, showing/hiding individual columns, and resetting to defaults — with `defaultDisplayPinnedIcon`/`defaultHiddenColumns` to configure the starting display without the panel.
|
|
24
|
+
- **Pagination:** Opt-in (`pagination`) page-based navigation with a configurable page size, a rows-per-page selector, and first/previous/next/last controls, as an alternative to (or alongside) virtualized infinite scrolling.
|
|
17
25
|
- **Full TypeScript Support:** Strongly typed configurations for robust developer experience.
|
|
18
26
|
|
|
19
27
|
---
|
|
@@ -58,10 +66,11 @@ const data: Employee[] = [
|
|
|
58
66
|
];
|
|
59
67
|
|
|
60
68
|
const columns: ColumnConfig<Employee>[] = [
|
|
61
|
-
{ key: 'name', header: 'Name', sortable: true, filterable: true, pinned: 'left',
|
|
69
|
+
{ key: 'name', header: 'Name', sortable: true, filterable: true, pinned: 'left', minWidth: 160, maxWidth: 500,
|
|
70
|
+
style: { width: "400px", backgroundColor: "#f9f9f9" },
|
|
62
71
|
headerStyle: { fontWeight: "bold", color: "#333" } },
|
|
63
72
|
{ key: 'department', header: 'Department', sortable: true, filterable: true },
|
|
64
|
-
{ key: 'role', header: 'Role', sortable: true, filterable: true },
|
|
73
|
+
{ key: 'role', header: 'Role', sortable: true, filterable: true, resizable: false },
|
|
65
74
|
{
|
|
66
75
|
key: 'salary',
|
|
67
76
|
header: 'Salary',
|
|
@@ -84,14 +93,26 @@ export default function EmployeeTable() {
|
|
|
84
93
|
{ id: -1, name: '', department: '', role: 'Total', salary: data.reduce((sum, r) => sum + r.salary, 0) },
|
|
85
94
|
]}
|
|
86
95
|
containerStyle={{ maxHeight: 480 }}
|
|
96
|
+
rowHeight={48}
|
|
97
|
+
overscan={8}
|
|
98
|
+
onColumnResize={(key, width) => console.log('resized', key, width)}
|
|
99
|
+
onColumnReorder={(orderedKeys) => console.log('reordered', orderedKeys)}
|
|
100
|
+
rowClassRules={{
|
|
101
|
+
'row-high-earner': (record) => record.salary > 100000,
|
|
102
|
+
}}
|
|
103
|
+
onRowClick={(record) => console.log('clicked', record.id)}
|
|
104
|
+
rowSpanColumns={['department']}
|
|
105
|
+
controlPanel
|
|
87
106
|
actions={(record) => (
|
|
88
|
-
<button onClick={() => console.log('Edit', record.id)}>Edit</button>
|
|
107
|
+
<button onClick={(e) => { e.stopPropagation(); console.log('Edit', record.id); }}>Edit</button>
|
|
89
108
|
)}
|
|
90
109
|
/>
|
|
91
110
|
);
|
|
92
111
|
}
|
|
93
112
|
```
|
|
94
113
|
|
|
114
|
+
`name` demonstrates a constrained resize range (`minWidth`/`maxWidth`), `role` opts out of resizing entirely (`resizable: false`), and dragging any header reorders columns — `onColumnResize`/`onColumnReorder` let you persist the user's layout. `rowClassRules` highlights any row with `salary > 100000`, and `onRowClick` logs the clicked row (the `Edit` button calls `stopPropagation` so clicking it doesn't also fire the row click). `containerStyle`'s bounded `maxHeight` (paired with `rowHeight`/`overscan`) is also what enables virtualized scrolling for large `data` arrays — see [Performance with Large Datasets](#performance-with-large-datasets).
|
|
115
|
+
|
|
95
116
|
---
|
|
96
117
|
|
|
97
118
|
## How It Works
|
|
@@ -114,7 +135,9 @@ Each entry in `columns` describes one column (or a group of columns, via `childr
|
|
|
114
135
|
| `sortable` | `boolean` | Makes the header clickable, cycling through ascending → descending → unsorted. |
|
|
115
136
|
| `pinned` | `boolean \| 'left' \| 'right'` | Pins the column to an edge by default (`true` is shorthand for `'left'`). Still toggleable per-column from the UI. |
|
|
116
137
|
| `pinnedRender` | `(value, record) => ReactNode` | Overrides `render` specifically for rows supplied via `pinnedBottomRowData` (e.g. render a "Total" label or bold sum). Falls back to `render` when omitted. |
|
|
117
|
-
| `children` | `ColumnConfig<T>[]` | Nest columns to render a grouped, multi-row header. Only leaf columns are sortable/filterable/pinnable. |
|
|
138
|
+
| `children` | `ColumnConfig<T>[]` | Nest columns to render a grouped, multi-row header. Only leaf columns are sortable/filterable/pinnable/resizable. |
|
|
139
|
+
| `resizable` | `boolean` | Set to `false` to hide the resize handle and lock the column's width. Defaults to `true` for leaf columns. |
|
|
140
|
+
| `minWidth` / `maxWidth` | `number` | Clamps how far a column can be resized, in pixels. |
|
|
118
141
|
|
|
119
142
|
### Grid props (`DataTableProps<T>`)
|
|
120
143
|
|
|
@@ -134,6 +157,21 @@ Each entry in `columns` describes one column (or a group of columns, via `childr
|
|
|
134
157
|
| `bordered` | `boolean` | Draws full grid borders (all four sides of every cell) instead of the default minimal row/column dividers. |
|
|
135
158
|
| `pinnedBottomRowData` | `T[]` | Rows rendered in a section fixed to the bottom of the grid (e.g. totals/summary rows). Independent of `data` — not affected by sorting, filtering, or grouping. |
|
|
136
159
|
| `pinnedRowClassName` | `string \| (record: T, index: number) => string` | Extra class name(s) applied to each pinned-bottom row, for styling a totals/summary template. |
|
|
160
|
+
| `rowHeight` | `number` | Row height in pixels used for virtualized row rendering. Defaults to `48`, matching the built-in CSS row height — change both together if you customize row height. |
|
|
161
|
+
| `overscan` | `number` | Number of extra off-screen rows rendered above/below the viewport, to smooth fast scrolling. Defaults to `8`. |
|
|
162
|
+
| `onColumnResize` | `(columnKey: string, width: number) => void` | Called once a column resize drag ends, with the column's new width in pixels. |
|
|
163
|
+
| `onColumnReorder` | `(orderedLeafKeys: string[]) => void` | Called whenever columns are reordered via drag-and-drop, with the full leaf column order. |
|
|
164
|
+
| `rowClassRules` | `Record<string, (record: T, index: number) => boolean>` | AG Grid-style conditional row classes: maps a class name to a predicate. Every rule whose predicate returns `true` for a row is applied to that row's `<tr>`. Does not apply to `pinnedBottomRowData` rows (use `pinnedRowClassName` for those). |
|
|
165
|
+
| `onRowClick` | `(record: T, index: number, event: React.MouseEvent) => void` | Called when a data row is clicked, with the row's record, its index in the currently rendered (filtered/sorted/grouped) rows, and the click event. Adds a pointer cursor to rows when set. |
|
|
166
|
+
| `rowSpanColumns` | `string[]` | Column keys, in priority order, to vertically merge across consecutive rows sharing the same value — e.g. `['city', 'department']`. See [Row Spanning](#row-spanning--cell-merging). |
|
|
167
|
+
| `controlPanel` | `boolean` | Renders an opt-in toolbar above the grid with a Settings menu button for end-user display configuration (pin/filter icon hover visibility, column show/hide). Defaults to `false` — existing grids are unaffected unless you turn it on. See [Control Panel](#control-panel). |
|
|
168
|
+
| `defaultDisplayPinnedIcon` | `boolean` | Initial value of the flag that controls whether the pin and filter icons in leaf column headers reveal on hover at all. When `false`, neither icon is shown on hover, regardless of `controlPanel`. Defaults to `true` (matches prior behavior). |
|
|
169
|
+
| `defaultHiddenColumns` | `string[]` | Leaf column keys hidden by default, independent of `controlPanel`. Defaults to `[]`. |
|
|
170
|
+
| `onColumnVisibilityChange` | `(hiddenKeys: string[]) => void` | Called whenever the set of hidden columns changes via the control panel, with the full list of currently hidden leaf column keys. |
|
|
171
|
+
| `pagination` | `boolean` | Enables page-based navigation, rendering a pagination panel below the grid instead of relying solely on scrolling. Defaults to `false`. See [Pagination](#pagination). |
|
|
172
|
+
| `defaultPageSize` | `number` | Initial number of rows per page when `pagination` is enabled. Defaults to `10`. |
|
|
173
|
+
| `pageSizeOptions` | `number[]` | Options shown in the rows-per-page selector. Defaults to `[10, 25, 50, 100]`. |
|
|
174
|
+
| `onPageChange` | `(page: number, pageSize: number) => void` | Called whenever the current page or page size changes. |
|
|
137
175
|
|
|
138
176
|
### Filtering
|
|
139
177
|
|
|
@@ -143,10 +181,140 @@ Any column with `filterable: true` gets a filter icon in its header. Clicking it
|
|
|
143
181
|
|
|
144
182
|
Clicking a `sortable` column header cycles through ascending → descending → unsorted. Only one column can be actively sorted at a time.
|
|
145
183
|
|
|
184
|
+
### Conditional Row Styling (`rowClassRules`)
|
|
185
|
+
|
|
186
|
+
`rowClassRules` mirrors AG Grid's API: an object mapping a CSS class name to a predicate function `(record, index) => boolean`. On every render, each rule is evaluated against each row, and every class whose predicate returns `true` is applied to that row's `<tr>` (multiple rules can match the same row at once). `index` is the row's position within the currently filtered/sorted/grouped rows, not the original `data` array.
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
const rowClassRules: Record<string, (record: Employee, index: number) => boolean> = {
|
|
190
|
+
'row-high-earner': (record) => record.salary > 100000,
|
|
191
|
+
'row-even': (_record, index) => index % 2 === 0,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
<DataTable
|
|
195
|
+
columns={columns}
|
|
196
|
+
data={data}
|
|
197
|
+
rowKey="id"
|
|
198
|
+
rowClassRules={rowClassRules}
|
|
199
|
+
/>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
```css
|
|
203
|
+
.row-high-earner { background-color: #fef3c7; }
|
|
204
|
+
.row-even { background-color: #f8fafc; }
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Rules only apply to regular data rows — group header rows and `pinnedBottomRowData` rows are unaffected (style the latter with `pinnedRowClassName`).
|
|
208
|
+
|
|
209
|
+
### Row Click Events
|
|
210
|
+
|
|
211
|
+
Pass `onRowClick` to be notified when a data row is clicked; the grid adds a pointer cursor to rows automatically once it's set. The handler receives the row's record, its index within the currently rendered rows, and the underlying `MouseEvent` — call `event.stopPropagation()` inside interactive cell content (e.g. an actions button) if you don't want that click to also bubble up as a row click.
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
<DataTable
|
|
215
|
+
columns={columns}
|
|
216
|
+
data={data}
|
|
217
|
+
rowKey="id"
|
|
218
|
+
onRowClick={(record, index, event) => console.log('clicked row', index, record)}
|
|
219
|
+
/>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Row Spanning / Cell Merging
|
|
223
|
+
|
|
224
|
+
Pass `rowSpanColumns` — an ordered array of column keys — to merge a column's cell vertically across consecutive rows that share the same value, similar to a merged-cell spreadsheet. The order is a priority/hierarchy: a later key only continues merging while every key before it also matches on that run of rows. For example, with `rowSpanColumns={['city', 'department']}`, `city` merges across every consecutive row with the same city, and `department` merges only across consecutive rows that share *both* the same city and the same department — so department merges nest inside city merges rather than spanning across a city boundary.
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
const data: Employee[] = [
|
|
228
|
+
{ id: 1, city: 'Austin', department: 'Engineering', name: 'Ava Thompson' },
|
|
229
|
+
{ id: 2, city: 'Austin', department: 'Engineering', name: 'Liam Carter' },
|
|
230
|
+
{ id: 3, city: 'Austin', department: 'Design', name: 'Sofia Ramirez' },
|
|
231
|
+
{ id: 4, city: 'Denver', department: 'Sales', name: 'Noah Patel' },
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
<DataTable
|
|
235
|
+
columns={columns}
|
|
236
|
+
data={data}
|
|
237
|
+
rowKey="id"
|
|
238
|
+
rowSpanColumns={['city', 'department']}
|
|
239
|
+
/>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Here `city` spans 3 rows (Austin) then 1 row (Denver); `department` spans 2 rows (Engineering, within Austin) then 1 row each for Design and Sales.
|
|
243
|
+
|
|
244
|
+
Notes:
|
|
245
|
+
|
|
246
|
+
- Rows must already be sorted or grouped so that equal values for a spanned column are contiguous — spanning does not itself sort or reorder rows. Combine with `sortable` columns, `defaultGroupBy`, or pre-sorted `data` as needed.
|
|
247
|
+
- Values are compared with the column's `getValue` (if provided) or `record[key]`, same as sorting/filtering.
|
|
248
|
+
- Spanning only merges the cells of plain data rows; group header rows (from `groupBy`) always break a run, and `pinnedBottomRowData` rows are never merged.
|
|
249
|
+
- Because rows are virtualized, spans are computed within the currently rendered window rather than across the entire dataset — a run that starts above the visible viewport will restart at the top of the window instead of continuing an off-screen merge. In practice this is invisible during normal scrolling since the affected rows aren't on screen at the same time.
|
|
250
|
+
|
|
251
|
+
### Control Panel
|
|
252
|
+
|
|
253
|
+
Pass `controlPanel` to render a small toolbar above the grid (and above the grouping drop zone, if `groupBy` is also on) with a single **Settings** menu button. Clicking it opens a dropdown with every display option in one place, instead of a row of separate buttons:
|
|
254
|
+
|
|
255
|
+
- **Show pin/filter icons on hover** — toggles `displayPinnedIcon`. When off, neither the pin button nor the filter button reveals on hovering a column header, regardless of whether the column is `filterable`; when on, hovering a leaf column header's action area reveals its filter icon (if `filterable`) and pin icon.
|
|
256
|
+
- **Columns** — a checklist of every leaf column (by `header` label) to show or hide it. Hiding a column removes it from both the header and body, and adjusts any parent group header's `colSpan` automatically.
|
|
257
|
+
- **Reset to defaults** — restores pin/filter icon hover visibility and hidden columns to the values passed via `defaultDisplayPinnedIcon` / `defaultHiddenColumns` (not to "all columns visible" — it resets to *your configured defaults*, so a column you deliberately hid by default stays hidden after reset).
|
|
258
|
+
|
|
259
|
+
```tsx
|
|
260
|
+
<DataTable
|
|
261
|
+
columns={columns}
|
|
262
|
+
data={data}
|
|
263
|
+
rowKey="id"
|
|
264
|
+
controlPanel
|
|
265
|
+
defaultHiddenColumns={['role']}
|
|
266
|
+
onColumnVisibilityChange={(hiddenKeys) => console.log('hidden columns', hiddenKeys)}
|
|
267
|
+
/>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
`controlPanel` is opt-in and defaults to `false`, so existing grids render exactly as before unless you enable it. `defaultDisplayPinnedIcon` and `defaultHiddenColumns` are independent of `controlPanel` — you can use either to fix a display configuration (e.g. permanently hide a column, or suppress pin/filter icons everywhere) without showing the panel UI at all.
|
|
271
|
+
|
|
272
|
+
### Pagination
|
|
273
|
+
|
|
274
|
+
Pass `pagination` to render a pagination panel below the grid: a rows-per-page selector (`pageSizeOptions`, defaulting to `[10, 25, 50, 100]`), the current range/total (e.g. "1–10 of 42"), and first/previous/next/last page controls.
|
|
275
|
+
|
|
276
|
+
```tsx
|
|
277
|
+
<DataTable
|
|
278
|
+
columns={columns}
|
|
279
|
+
data={data}
|
|
280
|
+
rowKey="id"
|
|
281
|
+
pagination
|
|
282
|
+
defaultPageSize={25}
|
|
283
|
+
pageSizeOptions={[25, 50, 100]}
|
|
284
|
+
onPageChange={(page, pageSize) => console.log('page', page, 'size', pageSize)}
|
|
285
|
+
/>
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Pagination is applied after filtering and sorting, and before row grouping — so with `groupBy` also enabled, each page groups only the rows that landed on it. Changing a filter, the sort, or the page size automatically resets you to page 1. `pagination` is independent of virtualization (`rowHeight`/`overscan`): you can use either or both, though with a small `pageSize` a bounded, scrollable `containerStyle` is rarely necessary since a page's rows will usually fit without scrolling.
|
|
289
|
+
|
|
146
290
|
### Row Grouping
|
|
147
291
|
|
|
148
292
|
Set `groupBy` to enable a drop zone above the table. Dragging a sortable/leaf column header into it groups rows by that column's value, with collapsible group rows and per-group row counts. Multiple columns can be stacked to create nested groups; use `defaultGroupBy` to pre-group on mount.
|
|
149
293
|
|
|
294
|
+
### Column Reordering
|
|
295
|
+
|
|
296
|
+
Drag any header cell and drop it on another to reorder columns, similar to AG Grid. Dragging a leaf column reorders it among its siblings; dragging a grouped-header cell moves the whole group. A column can only be dropped among its own siblings — you can't drag a column out of one group and into another. Pass `onColumnReorder` to be notified of the resulting leaf column order (e.g. to persist a user's layout).
|
|
297
|
+
|
|
298
|
+
Reordering composes with pinning: pinned-left and pinned-right columns still sort to their respective edges regardless of drag order.
|
|
299
|
+
|
|
300
|
+
### Column Resizing
|
|
301
|
+
|
|
302
|
+
Hover the right edge of a resizable header to reveal a resize handle; drag it to resize the column live. All leaf columns are resizable by default — set `resizable: false` on a `ColumnConfig` to disable it for that column, and use `minWidth` / `maxWidth` to constrain how far it can be dragged. Pass `onColumnResize` to persist widths (e.g. to `localStorage`) between sessions.
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
const columns: ColumnConfig<Employee>[] = [
|
|
306
|
+
{ key: 'name', header: 'Name', minWidth: 120, maxWidth: 400 },
|
|
307
|
+
{ key: 'role', header: 'Role', resizable: false }, // fixed width, no handle
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
<DataTable
|
|
311
|
+
columns={columns}
|
|
312
|
+
data={data}
|
|
313
|
+
rowKey="id"
|
|
314
|
+
onColumnResize={(key, width) => console.log('resized', key, width)}
|
|
315
|
+
/>
|
|
316
|
+
```
|
|
317
|
+
|
|
150
318
|
### Column Pinning
|
|
151
319
|
|
|
152
320
|
Hovering a header reveals a pin icon. Clicking it cycles the column through unpinned → pinned left → pinned right → unpinned, locking it to the corresponding edge with a sticky position and shadow so it stays visible while scrolling horizontally. Columns marked `pinned: true` (or `pinned: 'left'`) start pinned left; `pinned: 'right'` starts pinned right.
|
|
@@ -194,6 +362,133 @@ The header row is always fixed in place (`position: sticky; top: 0`) relative to
|
|
|
194
362
|
|
|
195
363
|
Pass `bordered` to draw full borders around every cell instead of the default minimal row/column dividers, for a classic spreadsheet look.
|
|
196
364
|
|
|
365
|
+
### Performance with Large Datasets
|
|
366
|
+
|
|
367
|
+
Row rendering is virtualized: only the rows visible inside the scrollable container (plus a small `overscan` buffer) are ever mounted as actual `<tr>` elements, regardless of how many rows are in `data`. This keeps scrolling, sorting, and filtering smooth for grids with thousands of rows.
|
|
368
|
+
|
|
369
|
+
Virtualization requires a bounded, scrollable container and a known row height:
|
|
370
|
+
|
|
371
|
+
- Give the grid a `maxHeight` via `containerStyle` so `flexi-table-wrap` actually scrolls (see [Sticky Header](#sticky-header)) — without a bounded height, the browser page itself scrolls and every row has to be rendered.
|
|
372
|
+
- The default row height is `48px`, matching the built-in CSS. If you override row height via custom CSS, pass the same value through the `rowHeight` prop so the virtualizer's scroll math stays accurate — group header rows (from `groupBy`) are also sized to `rowHeight` internally for the same reason, so a custom `rowHeight` no longer causes drift (previously visible as blank gaps or an inability to fully reach the last rows once scrolled to the bottom).
|
|
373
|
+
- Tune `overscan` (default `8`) if you see blank flashes on very fast scrolling — a higher value trades a bit of extra rendering for smoother scroll.
|
|
374
|
+
|
|
375
|
+
Filtering is also optimized for large datasets: a filter column's unique values are computed once per column (not on every keystroke), and column lookups used during filtering/sorting/grouping are indexed by key instead of scanned linearly.
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Full Example (Every Feature)
|
|
380
|
+
|
|
381
|
+
A single grid wiring up every prop at once — nested/grouped headers, pinning, resizing, reordering, filtering, sorting, row grouping, row spanning, the control panel, pagination, pinned bottom totals, conditional row styling, row clicks, and virtualization:
|
|
382
|
+
|
|
383
|
+
```tsx
|
|
384
|
+
import { DataTable, type ColumnConfig } from 'flexi-datagrid';
|
|
385
|
+
import 'flexi-datagrid/dist/flexi-datagrid.css';
|
|
386
|
+
|
|
387
|
+
interface Employee {
|
|
388
|
+
id: number;
|
|
389
|
+
city: string;
|
|
390
|
+
department: string;
|
|
391
|
+
name: string;
|
|
392
|
+
role: string;
|
|
393
|
+
baseSalary: number;
|
|
394
|
+
bonus: number;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const data: Employee[] = [
|
|
398
|
+
{ id: 1, city: 'Austin', department: 'Engineering', name: 'Ava Thompson', role: 'Frontend Developer', baseSalary: 95000, bonus: 8000 },
|
|
399
|
+
{ id: 2, city: 'Austin', department: 'Engineering', name: 'Liam Carter', role: 'Backend Developer', baseSalary: 102000, bonus: 9500 },
|
|
400
|
+
{ id: 3, city: 'Austin', department: 'Design', name: 'Sofia Ramirez', role: 'Product Designer', baseSalary: 88000, bonus: 4000 },
|
|
401
|
+
{ id: 4, city: 'Denver', department: 'Sales', name: 'Noah Patel', role: 'Account Executive', baseSalary: 76000, bonus: 12000 },
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
const columns: ColumnConfig<Employee>[] = [
|
|
405
|
+
{ key: 'city', header: 'City', sortable: true, filterable: true, minWidth: 100, maxWidth: 240 },
|
|
406
|
+
{ key: 'department', header: 'Department', sortable: true, filterable: true, minWidth: 120 },
|
|
407
|
+
{
|
|
408
|
+
key: 'name',
|
|
409
|
+
header: 'Name',
|
|
410
|
+
sortable: true,
|
|
411
|
+
filterable: true,
|
|
412
|
+
pinned: 'left',
|
|
413
|
+
minWidth: 160,
|
|
414
|
+
maxWidth: 500,
|
|
415
|
+
style: { width: '220px' },
|
|
416
|
+
},
|
|
417
|
+
{ key: 'role', header: 'Role', sortable: true, filterable: true, resizable: false },
|
|
418
|
+
{
|
|
419
|
+
key: 'compensation',
|
|
420
|
+
header: 'Compensation',
|
|
421
|
+
children: [
|
|
422
|
+
{
|
|
423
|
+
key: 'baseSalary',
|
|
424
|
+
header: 'Base Salary',
|
|
425
|
+
sortable: true,
|
|
426
|
+
pinned: 'right',
|
|
427
|
+
render: (value) => `$${value.toLocaleString()}`,
|
|
428
|
+
pinnedRender: () => <strong>Total</strong>,
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
key: 'bonus',
|
|
432
|
+
header: 'Bonus',
|
|
433
|
+
sortable: true,
|
|
434
|
+
render: (value) => `$${value.toLocaleString()}`,
|
|
435
|
+
pinnedRender: (value) => <strong>${value.toLocaleString()}</strong>,
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
},
|
|
439
|
+
];
|
|
440
|
+
|
|
441
|
+
export default function EmployeeTable() {
|
|
442
|
+
return (
|
|
443
|
+
<DataTable<Employee>
|
|
444
|
+
columns={columns}
|
|
445
|
+
data={data}
|
|
446
|
+
rowKey="id"
|
|
447
|
+
bordered
|
|
448
|
+
containerStyle={{ maxHeight: 480 }}
|
|
449
|
+
tableStyle={{ minWidth: 900 }}
|
|
450
|
+
rowHeight={48}
|
|
451
|
+
overscan={8}
|
|
452
|
+
groupBy
|
|
453
|
+
defaultGroupBy={[]}
|
|
454
|
+
onGroupChange={(groupedKeys) => console.log('grouped by', groupedKeys)}
|
|
455
|
+
rowSpanColumns={['city', 'department']}
|
|
456
|
+
onColumnResize={(key, width) => console.log('resized', key, width)}
|
|
457
|
+
onColumnReorder={(orderedKeys) => console.log('reordered', orderedKeys)}
|
|
458
|
+
onPinChange={(key, isPinned, side) => console.log('pin change', key, isPinned, side)}
|
|
459
|
+
controlPanel
|
|
460
|
+
defaultDisplayPinnedIcon={true}
|
|
461
|
+
defaultHiddenColumns={[]}
|
|
462
|
+
onColumnVisibilityChange={(hiddenKeys) => console.log('hidden columns', hiddenKeys)}
|
|
463
|
+
pagination
|
|
464
|
+
defaultPageSize={25}
|
|
465
|
+
pageSizeOptions={[25, 50, 100]}
|
|
466
|
+
onPageChange={(page, pageSize) => console.log('page', page, 'size', pageSize)}
|
|
467
|
+
pinnedBottomRowData={[
|
|
468
|
+
{ id: -1, city: '', department: '', name: '', role: '', baseSalary: 0, bonus: data.reduce((sum, r) => sum + r.bonus, 0) },
|
|
469
|
+
]}
|
|
470
|
+
pinnedRowClassName="totals-row"
|
|
471
|
+
rowClassRules={{
|
|
472
|
+
'row-high-earner': (record) => record.baseSalary > 100000,
|
|
473
|
+
}}
|
|
474
|
+
onRowClick={(record, index) => console.log('clicked row', index, record.name)}
|
|
475
|
+
actions={(record) => (
|
|
476
|
+
<button onClick={(e) => { e.stopPropagation(); console.log('Edit', record.id); }}>Edit</button>
|
|
477
|
+
)}
|
|
478
|
+
/>
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Notes on combining features in this example:
|
|
484
|
+
|
|
485
|
+
- `city`/`department` are left as plain leaf columns (not pinned/nested) so `rowSpanColumns={['city', 'department']}` can merge them — `data` is already sorted by city then department, which the merge requires (see [Row Spanning](#row-spanning--cell-merging)).
|
|
486
|
+
- `compensation` demonstrates a nested/grouped header: `baseSalary` and `bonus` render under a shared "Compensation" header cell, and `baseSalary` is independently pinned right.
|
|
487
|
+
- `pinnedRender` on `baseSalary`/`bonus` overrides how the totals row renders those two cells only; every other column in `pinnedBottomRowData` falls back to its normal `render`.
|
|
488
|
+
- `groupBy` is enabled with an empty `defaultGroupBy` — the grid starts flat (so the row spans above are visible) but a user can still drag `role`, for example, into the drop zone to group by it on top of everything else.
|
|
489
|
+
- `controlPanel` adds the Settings menu button for pin/filter icon hover visibility and column visibility toggling on top of the `defaultDisplayPinnedIcon`/`defaultHiddenColumns` starting state — omit `controlPanel` (or leave it `false`) to keep those two props as a fixed, non-interactive configuration instead.
|
|
490
|
+
- `pagination` slices the filtered/sorted rows into pages of `defaultPageSize` (25 here) before grouping — combine cautiously with `rowSpanColumns`, since spans only merge cells within the current page's rows.
|
|
491
|
+
|
|
197
492
|
---
|
|
198
493
|
|
|
199
494
|
## 🛠 Local Development
|
|
@@ -14,6 +14,9 @@ export interface ColumnConfig<T> {
|
|
|
14
14
|
pinned?: boolean | 'left' | 'right';
|
|
15
15
|
children?: ColumnConfig<T>[];
|
|
16
16
|
getValue?: (record: T) => any;
|
|
17
|
+
resizable?: boolean;
|
|
18
|
+
minWidth?: number;
|
|
19
|
+
maxWidth?: number;
|
|
17
20
|
}
|
|
18
21
|
type PinSide = 'left' | 'right';
|
|
19
22
|
interface DataTableProps<T> {
|
|
@@ -31,6 +34,21 @@ interface DataTableProps<T> {
|
|
|
31
34
|
bordered?: boolean;
|
|
32
35
|
pinnedBottomRowData?: T[];
|
|
33
36
|
pinnedRowClassName?: string | ((record: T, index: number) => string);
|
|
37
|
+
rowHeight?: number;
|
|
38
|
+
overscan?: number;
|
|
39
|
+
onColumnResize?: (columnKey: string, width: number) => void;
|
|
40
|
+
onColumnReorder?: (orderedLeafKeys: string[]) => void;
|
|
41
|
+
rowClassRules?: Record<string, (record: T, index: number) => boolean>;
|
|
42
|
+
onRowClick?: (record: T, index: number, event: React.MouseEvent<HTMLTableRowElement>) => void;
|
|
43
|
+
rowSpanColumns?: string[];
|
|
44
|
+
controlPanel?: boolean;
|
|
45
|
+
defaultDisplayPinnedIcon?: boolean;
|
|
46
|
+
defaultHiddenColumns?: string[];
|
|
47
|
+
onColumnVisibilityChange?: (hiddenKeys: string[]) => void;
|
|
48
|
+
pagination?: boolean;
|
|
49
|
+
defaultPageSize?: number;
|
|
50
|
+
pageSizeOptions?: number[];
|
|
51
|
+
onPageChange?: (page: number, pageSize: number) => void;
|
|
34
52
|
}
|
|
35
|
-
export declare function DataTable<T>({ columns, data, rowKey, actions, isLoading, groupBy, defaultGroupBy, onPinChange, onGroupChange, containerStyle, tableStyle, bordered, pinnedBottomRowData, pinnedRowClassName, }: DataTableProps<T>): React.JSX.Element;
|
|
53
|
+
export declare function DataTable<T>({ columns, data, rowKey, actions, isLoading, groupBy, defaultGroupBy, onPinChange, onGroupChange, containerStyle, tableStyle, bordered, pinnedBottomRowData, pinnedRowClassName, rowHeight, overscan, onColumnResize, onColumnReorder, rowClassRules, onRowClick, rowSpanColumns, controlPanel, defaultDisplayPinnedIcon, defaultHiddenColumns, onColumnVisibilityChange, pagination, defaultPageSize, pageSizeOptions, onPageChange, }: DataTableProps<T>): React.JSX.Element;
|
|
36
54
|
export {};
|
package/dist/flexi-datagrid.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
.flexi-root{width:100%;position:relative}.flexi-root>*+*{margin-top:.75rem}.flexi-groupzone{box-sizing:border-box;background-color:#f8fafc80;border:1px dashed #e2e8f0;border-radius:.75rem;align-items:center;gap:.5rem;width:100%;min-height:52px;padding:.5rem 1rem;transition:all .2s;display:flex}.flexi-groupzone--dragover{background-color:#eff6ff80;border:1px solid #3b82f6;transform:scale(.99)}.flexi-groupzone-hint{color:#64748b;-webkit-user-select:none;user-select:none;font-size:12px;font-weight:500}.flexi-groupzone-chips{flex-wrap:wrap;align-items:center;gap:.375rem;display:flex}.flexi-groupzone-label{text-transform:uppercase;letter-spacing:.05em;color:#64748b;margin-right:.25rem;font-size:11px;font-weight:700}.flexi-chip{color:#64748b;background-color:#fff;border:1px solid #e2e8f0;border-radius:.5rem;align-items:center;gap:.375rem;padding:.25rem .25rem .25rem .5rem;font-size:12px;font-weight:600;display:flex;box-shadow:0 1px 2px #0000000d}.flexi-chip-remove{border-radius:5px;width:18px;height:18px}.flexi-table-wrap{box-sizing:border-box;background-color:#fff;border:1px solid #f1f5f9;border-radius:.75rem;width:100%;overflow:auto;box-shadow:0 1px 2px #0000000d}.flexi-table{caption-side:bottom;border-collapse:collapse;border-spacing:0;table-layout:fixed;width:100%;font-size:.875rem;position:relative}.flexi-table--bordered th,.flexi-table--bordered td{border:1px solid #e2e8f0}.flexi-thead{-webkit-user-select:none;user-select:none;z-index:40;position:sticky}.flexi-header-row{border-bottom:1px solid #f1f5f9}.flexi-group-spacer-th{border-right:1px solid #f1f5f9;width:48px}.flexi-th{text-align:center;vertical-align:middle;z-index:25;background-color:#f8fafc;border-bottom:1px solid #f1f5f9;border-right:1px solid #f1f5f9;height:48px;padding:0 1rem;font-size:12px;font-weight:700;position:sticky;top:0}.flexi-th--draggable{cursor:grab}.flexi-th--draggable:active{cursor:grabbing}.flexi-th--pinned{background-color:#f8fafc}.flexi-th-inner{justify-content:space-between;align-items:center;gap:.25rem;width:100%;min-width:0;display:flex}.flexi-th-label{white-space:nowrap;text-overflow:ellipsis;flex:auto;align-items:center;gap:.375rem;min-width:0;display:flex;overflow:hidden}.flexi-th-label--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.flexi-th-label--sortable:hover{color:#2563eb}.flexi-th-text{white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.flexi-sort-icon{color:#94a3b8;opacity:0;transition:color .15s,opacity .15s}.flexi-sort-icon--active{color:#2563eb;opacity:1}.flexi-th-inner:hover .flexi-sort-icon{opacity:1}.flexi-th-actions{flex:none;align-items:center;gap:2px;display:flex}.flexi-icon-btn{color:#64748b;background-color:#0000;border-radius:6px;justify-content:center;align-items:center;width:22px;height:22px;margin:0;padding:0;transition:background-color .15s,color .15s,transform .1s;display:inline-flex}.flexi-icon-btn:hover{color:#334155;background-color:#e2e8f0}.flexi-icon-btn:active{background-color:#cbd5e1;transform:scale(.92)}.flexi-icon-btn:focus-visible{outline-offset:1px;outline:2px solid #93c5fd}.flexi-filter-btn
|
|
1
|
+
.flexi-root{width:100%;position:relative}.flexi-root>*+*{margin-top:.75rem}.flexi-settings-menu{z-index:50;background-color:#fff;border:1px solid #e2e8f0;border-radius:.5rem;flex-direction:column;width:13rem;max-height:20rem;padding:.5rem;font-weight:400;display:flex;position:absolute;top:calc(100% + 4px);overflow-y:auto;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.flexi-settings-section{flex-direction:column;gap:.125rem;display:flex}.flexi-settings-heading{text-transform:uppercase;letter-spacing:.05em;color:#94a3b8;padding:.25rem .5rem;font-size:11px;font-weight:700}.flexi-settings-divider{background-color:#f1f5f9;height:1px;margin:.375rem 0}.flexi-settings-reset{color:#ef4444;border-radius:.375rem;align-items:center;gap:.375rem;padding:.375rem .5rem;font-size:12px;font-weight:600;display:inline-flex}.flexi-settings-reset:hover{background-color:#fef2f2}.flexi-groupzone{box-sizing:border-box;background-color:#f8fafc80;border:1px dashed #e2e8f0;border-radius:.75rem;align-items:center;gap:.5rem;width:100%;min-height:52px;padding:.5rem 1rem;transition:all .2s;display:flex}.flexi-groupzone--dragover{background-color:#eff6ff80;border:1px solid #3b82f6;transform:scale(.99)}.flexi-groupzone-hint{color:#64748b;-webkit-user-select:none;user-select:none;font-size:12px;font-weight:500}.flexi-groupzone-chips{flex-wrap:wrap;align-items:center;gap:.375rem;display:flex}.flexi-groupzone-label{text-transform:uppercase;letter-spacing:.05em;color:#64748b;margin-right:.25rem;font-size:11px;font-weight:700}.flexi-chip{color:#64748b;background-color:#fff;border:1px solid #e2e8f0;border-radius:.5rem;align-items:center;gap:.375rem;padding:.25rem .25rem .25rem .5rem;font-size:12px;font-weight:600;display:flex;box-shadow:0 1px 2px #0000000d}.flexi-chip-remove{border-radius:5px;width:18px;height:18px}.flexi-table-wrap{box-sizing:border-box;background-color:#fff;border:1px solid #f1f5f9;border-radius:.75rem;width:100%;overflow:auto;box-shadow:0 1px 2px #0000000d}.flexi-table{caption-side:bottom;border-collapse:collapse;border-spacing:0;table-layout:fixed;width:100%;font-size:.875rem;position:relative}.flexi-table--bordered th,.flexi-table--bordered td{border:1px solid #e2e8f0}.flexi-thead{-webkit-user-select:none;user-select:none;z-index:40;position:sticky}.flexi-header-row{border-bottom:1px solid #f1f5f9}.flexi-group-spacer-th{border-right:1px solid #f1f5f9;width:48px}.flexi-th{text-align:center;vertical-align:middle;z-index:25;background-color:#f8fafc;border-bottom:1px solid #f1f5f9;border-right:1px solid #f1f5f9;height:48px;padding:0 1rem;font-size:12px;font-weight:700;position:sticky;top:0}.flexi-th--draggable{cursor:grab}.flexi-th--draggable:active{cursor:grabbing}.flexi-th--pinned{background-color:#f8fafc}.flexi-th-inner{justify-content:space-between;align-items:center;gap:.25rem;width:100%;min-width:0;display:flex}.flexi-th-label{white-space:nowrap;text-overflow:ellipsis;flex:auto;align-items:center;gap:.375rem;min-width:0;display:flex;overflow:hidden}.flexi-th-label--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.flexi-th-label--sortable:hover{color:#2563eb}.flexi-th-text{white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.flexi-sort-icon,.flexi-filter-btn,.flexi-pin-btn{color:#94a3b8;opacity:0;transition:color .15s,opacity .15s}.flexi-sort-icon--active{color:#2563eb;opacity:1}.flexi-th-inner:hover .flexi-sort-icon,.flexi-th-inner:hover .flexi-filter-btn,.flexi-th-inner:hover .flexi-pin-btn{opacity:1}.flexi-th-actions{flex:none;align-items:center;gap:2px;display:flex}.flexi-th-resizer{cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none;z-index:5;width:6px;position:absolute;top:0;bottom:0;right:0}.flexi-th-resizer:hover,.flexi-th-resizer:active{background-color:#93c5fd}.flexi-icon-btn{color:#64748b;background-color:#0000;border-radius:6px;justify-content:center;align-items:center;width:22px;height:22px;margin:0;padding:0;transition:background-color .15s,color .15s,transform .1s;display:inline-flex}.flexi-icon-btn:hover{color:#334155;background-color:#e2e8f0}.flexi-icon-btn:active{background-color:#cbd5e1;transform:scale(.92)}.flexi-icon-btn:focus-visible{outline-offset:1px;outline:2px solid #93c5fd}.flexi-filter-btn--active{color:#2563eb;opacity:1}.flexi-filter-btn--active:hover{color:#2563eb;opacity:1;background-color:#dbeafe}.flexi-pin-btn--pinned{color:#3b82f6;opacity:1}.flexi-pin-btn--pinned:hover{color:#2563eb;opacity:1;background-color:#dbeafe}.flexi-actions-th{vertical-align:middle;text-align:right;height:48px;padding:0 1rem;font-size:12px;font-weight:700;box-shadow:-1px 0 #e2e8f0}.flexi-filter-dropdown{color:#334155;text-align:left;pointer-events:auto;background-color:#fff;border:1px solid #e2e8f0;border-radius:.5rem;flex-direction:column;width:13rem;padding:.5rem 0;font-weight:400;display:flex;position:absolute;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.flexi-filter-search{border-bottom:1px solid #f1f5f9;margin-bottom:.25rem;padding:0 .5rem .5rem}.flexi-input{color:#475569;box-sizing:border-box;border:1px solid #e2e8f0;border-radius:.25rem;width:100%;padding:.25rem .5rem;font-size:12px;font-weight:500}.flexi-input:focus{border-color:#3b82f6;outline:none}.flexi-filter-list{flex-direction:column;gap:.25rem;max-height:10rem;padding:0 .5rem;display:flex;overflow-y:auto}.flexi-filter-empty{color:#64748b;text-align:center;padding:.5rem;font-size:11px}.flexi-filter-option{cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:.25rem;align-items:center;gap:.5rem;padding:.375rem .5rem;font-size:12px;display:flex}.flexi-filter-option:hover{background-color:#f8fafc}.flexi-filter-option-text{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.flexi-checkbox{accent-color:#2563eb;border:1px solid #cbd5e1;border-radius:.25rem;width:.875rem;height:.875rem}.flexi-checkbox:focus{outline-offset:1px;outline:2px solid #3b82f6}.flexi-filter-footer{border-top:1px solid #f1f5f9;justify-content:flex-end;margin-top:.5rem;padding:.25rem .5rem 0;display:flex}.flexi-filter-clear{color:#ef4444;padding:.125rem .5rem;font-size:11px;font-weight:500}.flexi-filter-clear:hover{text-decoration:underline}.flexi-tbody{display:table-row-group}.flexi-tbody tr+tr{border-top:1px solid #f1f5f9}.flexi-empty-cell{text-align:center;color:#64748b;padding:2rem;font-size:12px;font-weight:500}.flexi-group-row{-webkit-user-select:none;user-select:none;background-color:#f8fafc99;height:48px;transition:background-color .15s}.flexi-group-row:hover{background-color:#f1f5f9cc}.flexi-group-cell{color:#334155;border-right:1px solid #f1f5f9;padding:.75rem 1rem;font-size:12px;font-weight:600}.flexi-group-toggle{text-align:left;border-radius:6px;align-items:center;gap:.375rem;margin:-4px -8px -4px -4px;padding:4px 8px 4px 4px;transition:background-color .15s;display:flex}.flexi-group-toggle:hover{background-color:#e2e8f099}.flexi-group-toggle:focus-visible{outline-offset:1px;outline:2px solid #93c5fd}.flexi-icon-muted{color:#64748b}.flexi-group-value{color:#1e293b;white-space:nowrap;text-overflow:ellipsis;width:10rem;font-weight:700;overflow:hidden}.flexi-group-count{color:#64748b;background-color:#e2e8f0b3;border-radius:9999px;margin-left:.25rem;padding:.05rem .375rem;font-size:10px;font-weight:500}.flexi-group-filler{background-color:#f8fafc4d}.flexi-row{border-bottom:1px solid #f1f5f9;height:48px;transition:background-color .15s}.flexi-row:hover{background-color:#f8fafccc}.flexi-row--clickable{cursor:pointer}.flexi-row-indent-cell{background-color:#fff;border-right:1px solid #f1f5f9;width:48px}.flexi-cell{vertical-align:middle;padding:.25rem 1rem;transition:background-color .15s}.flexi-row:hover .flexi-cell--pinned{background-color:#f1f5f9}.flexi-cell-text{color:#64748b;white-space:nowrap;text-overflow:ellipsis;font-size:12px;font-weight:500;display:block;overflow:hidden}.flexi-actions-cell{vertical-align:middle;text-align:right;background-color:#fff;padding:.25rem 1rem;box-shadow:-1px 0 #e2e8f0}.flexi-row:hover .flexi-actions-cell{background-color:#f8fafccc}.flexi-tbody--pinned-bottom{box-shadow:0 -2px 5px -2px #0000001a}.flexi-row--pinned-bottom{background-color:#f8fafc;font-weight:600}.flexi-row--pinned-bottom:hover,.flexi-row--pinned-bottom .flexi-cell,.flexi-row--pinned-bottom .flexi-actions-cell{background-color:#f8fafc}.flexi-row--pinned-bottom .flexi-cell-text{color:#334155}.flexi-actions-inner{opacity:0;justify-content:flex-end;gap:.25rem;transition:opacity .15s;display:flex}.flexi-row:hover .flexi-actions-inner{opacity:1}.flexi-icon-xs{width:.75rem;height:.75rem}.flexi-icon-sm{width:.875rem;height:.875rem}.flexi-icon-md{width:1rem;height:1rem}.flexi-pagination{color:#475569;box-sizing:border-box;background-color:#f8fafc80;border:1px solid #e2e8f0;border-radius:.75rem;justify-content:space-between;align-items:center;gap:1rem;width:100%;padding:.5rem .75rem;font-size:12px;font-weight:500;display:flex}.flexi-pagination-size{align-items:center;gap:.5rem;display:flex}.flexi-pagination-select{color:#334155;background-color:#fff;border:1px solid #e2e8f0;border-radius:.375rem;padding:.25rem .5rem;font-size:12px;font-weight:500}.flexi-pagination-info{text-align:center;flex:auto}.flexi-pagination-controls{align-items:center;gap:.25rem;display:flex}.flexi-pagination-controls .flexi-icon-btn:disabled{opacity:.4;cursor:not-allowed}.flexi-pagination-controls .flexi-icon-btn:disabled:hover{background-color:#0000}.flexi-pagination-page{white-space:nowrap;padding:0 .5rem}.flexi-root button{appearance:none;cursor:pointer;font:inherit;color:inherit;background:0 0;border:none;margin:0;padding:0;line-height:normal}.flexi-root input{font:inherit}.flexi-root table{border-collapse:collapse;border-spacing:0}
|
|
2
2
|
/*$vite$:1*/
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import './index.css';
|
|
2
|
-
export { DataTable } from './
|
|
3
|
-
export type { ColumnConfig } from './
|
|
2
|
+
export { DataTable } from './Datatable';
|
|
3
|
+
export type { ColumnConfig } from './Datatable';
|