blunt-ui 0.3.1 → 0.3.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # blunt-ui
2
2
 
3
- React + TypeScript + styled-components. Thick borders, offset shadows, no fluff. 9 components, 2 hooks.
3
+ React + TypeScript + styled-components. Thick borders, offset shadows, no fluff. 11 components, 3 hooks.
4
4
 
5
5
  **Live demo:** https://blunt-ui.vercel.app/
6
6
 
@@ -179,6 +179,106 @@ const { values, errors, handleChange, handleBlur, handleSubmit, reset } =
179
179
 
180
180
  The `name` on each input needs to match the key in `initialValues`. Use `reset()` to clear everything back to the start.
181
181
 
182
+ ## Table
183
+
184
+ Read-only table with sorting and pagination. You define columns, pass data, done. Sorting and pagination work in both uncontrolled mode (component handles state internally) and controlled mode (you own the state, useful when data comes from an API).
185
+
186
+ Sizes: `sm`, `md`, `lg`.
187
+
188
+ ```tsx
189
+ const columns = [
190
+ { key: "name", header: "Name", sortable: true },
191
+ { key: "role", header: "Role" },
192
+ { key: "status", header: "Status", render: (v) => <Badge>{v}</Badge> },
193
+ ];
194
+
195
+ <Table
196
+ columns={columns}
197
+ data={rows}
198
+ rowKey="id"
199
+ striped
200
+ bordered
201
+ pageSize={10}
202
+ emptyMessage="No results"
203
+ />;
204
+ ```
205
+
206
+ **Sorting** — add `sortable: true` to any column. By default the table sorts client-side. If you want to handle it yourself (e.g. send a query param to your backend), pass `sort` + `onSortChange` and it becomes controlled — the table just shows the sort indicator and tells you when it changed, you bring the sorted data.
207
+
208
+ **Pagination** — set `pageSize` and the table slices the data automatically. For server-side pagination, also pass `totalRows` so it knows how many pages there are, and control `page` + `onPageChange` yourself. If you need both sort and page in one callback, use `onChange` instead.
209
+
210
+ **Other stuff** — `loading` replaces rows with skeleton cells while data is loading. `stickyHeader` keeps the header in view when the table scrolls. `caption` adds a proper `<caption>` for accessibility.
211
+
212
+ Color props: `borderColor`, `headerColor`, `rowColor`, `stripeColor`.
213
+
214
+ ## DataTable
215
+
216
+ Editable table — good for things like spreadsheet-style input or inline CRUD. Each column can be editable or not, and you can mix text inputs with dropdowns.
217
+
218
+ ```tsx
219
+ const columns = [
220
+ { key: "name", header: "Name", editable: true },
221
+ { key: "qty", header: "Qty", editable: true, width: "80px" },
222
+ {
223
+ key: "status",
224
+ header: "Status",
225
+ editable: true,
226
+ options: [
227
+ { value: "todo", label: "To do" },
228
+ { value: "in_progress", label: "In progress" },
229
+ { value: "done", label: "Done" },
230
+ ],
231
+ },
232
+ ];
233
+
234
+ <DataTable
235
+ columns={columns}
236
+ defaultData={[{ name: "Widget", qty: "1", status: "todo" }]}
237
+ onChange={(rows) => console.log(rows)}
238
+ deletable
239
+ addRowLabel="Add item"
240
+ />;
241
+ ```
242
+
243
+ **Editing** — click a text cell to start editing, Enter to confirm, Escape to cancel. For select cells (columns with `options`), one click opens the dropdown directly; picking an option saves immediately. Tab and Shift+Tab move between editable cells. Tab past the last cell adds a new row automatically.
244
+
245
+ **Select columns** — add `options: [{ value, label }]` to a column. The cell stores the `value` but displays the `label`. The column still needs `editable: true`.
246
+
247
+ **Controlled vs uncontrolled** — same pattern as the rest of the library. Pass `defaultData` and forget about it, or pass `data` + `onChange` if you need to keep the data in your own state.
248
+
249
+ **Per-cell editability** — `editable` can be a function `(row, rowIndex) => boolean` if you need some cells to be editable based on row content.
250
+
251
+ `deletable` adds a remove button per row. `newRowFactory` lets you control what an empty new row looks like (useful if you need generated IDs or default values).
252
+
253
+ Color props: `borderColor`, `headerColor`.
254
+
255
+ ## useTable
256
+
257
+ Helper hook for when your table data comes from a server. It keeps track of the current sort and page so you can use them in a fetch call, and passes them back to `<Table>` as controlled props.
258
+
259
+ ```tsx
260
+ const { sort, page, onSortChange, onPageChange } = useTable({
261
+ defaultPage: 1,
262
+ });
263
+
264
+ useEffect(() => {
265
+ fetchProducts({ sort, page });
266
+ }, [sort, page]);
267
+
268
+ <Table
269
+ columns={columns}
270
+ data={serverData}
271
+ sort={sort}
272
+ onSortChange={onSortChange}
273
+ page={page}
274
+ onPageChange={onPageChange}
275
+ pageSize={20}
276
+ totalRows={totalCount}
277
+ />;
278
+ ```
279
+
280
+ When the user changes the sort column, the page resets to 1 automatically — that's usually what you want so you don't end up on page 5 of a different sort order. Pass `defaultSort` if you need a column sorted on first load.
281
+
182
282
  ## Design tokens
183
283
 
184
284
  Colors, spacing, font sizes, and border radius live in `src/consts.ts`. All components pull from there, so changing a token updates everything at once.
@@ -9,5 +9,6 @@ export declare const MixedEditable: Story;
9
9
  export declare const PerCellEditable: Story;
10
10
  export declare const CustomColors: Story;
11
11
  export declare const KeyboardNavigation: Story;
12
+ export declare const WithSelectColumn: Story;
12
13
  export declare const StartsEmpty: Story;
13
14
  export declare const Controlled: Story;
@@ -1,11 +1,16 @@
1
1
  import { CSSProperties, ReactNode } from 'react';
2
2
  import { TableSizes } from '../Table/Table.types';
3
3
  export type { TableSizes };
4
+ export interface DataTableSelectOption {
5
+ value: string;
6
+ label: string;
7
+ }
4
8
  export interface DataTableColumn<T = Record<string, unknown>> {
5
9
  key: keyof T & string;
6
10
  header: string;
7
11
  width?: string;
8
12
  editable?: boolean | ((row: T, rowIndex: number) => boolean);
13
+ options?: DataTableSelectOption[];
9
14
  render?: (value: unknown, row: T, rowIndex: number) => ReactNode;
10
15
  }
11
16
  export interface DataTableProps<T extends Record<string, unknown> = Record<string, unknown>> {
@@ -1,2 +1,2 @@
1
1
  export { DataTable } from './DataTable';
2
- export type { DataTableProps, DataTableColumn } from './DataTable.types';
2
+ export type { DataTableProps, DataTableColumn, DataTableSelectOption, } from './DataTable.types';
@@ -4,22 +4,13 @@ declare const meta: Meta<typeof Table>;
4
4
  export default meta;
5
5
  type Story = StoryObj<typeof Table>;
6
6
  export declare const Default: Story;
7
- export declare const Striped: Story;
8
- export declare const Bordered: Story;
9
7
  export declare const StripedAndBordered: Story;
10
- export declare const Small: Story;
11
- export declare const Large: Story;
12
- export declare const WithCaption: Story;
13
8
  export declare const WithCustomRender: Story;
14
9
  export declare const Sortable: Story;
15
10
  export declare const SortableControlled: Story;
11
+ export declare const WithUseTable: Story;
16
12
  export declare const Loading: Story;
17
13
  export declare const Empty: Story;
18
14
  export declare const Paginated: Story;
19
- export declare const PaginatedSortable: Story;
20
15
  export declare const StickyHeader: Story;
21
- export declare const ColumnWidths: Story;
22
- export declare const CustomBorderColor: Story;
23
- export declare const CustomHeaderColor: Story;
24
- export declare const CustomRowColors: Story;
25
16
  export declare const FullyCustomized: Story;
@@ -27,7 +27,7 @@ export interface TableProps<T extends Record<string, unknown> = Record<string, u
27
27
  caption?: string;
28
28
  emptyMessage?: string;
29
29
  loading?: boolean;
30
- sort?: SortState;
30
+ sort?: SortState | null;
31
31
  defaultSort?: SortState;
32
32
  onSortChange?: (sort: SortState | null) => void;
33
33
  pageSize?: number;
@@ -1,2 +1,4 @@
1
1
  export { Table } from './Table';
2
2
  export type { TableProps, TableColumn, TableSizes, SortState, SortDirection, TableChangeState, } from './Table.types';
3
+ export { useTable } from './useTable';
4
+ export type { UseTableOptions, UseTableReturn } from './useTable';
@@ -0,0 +1,12 @@
1
+ import { SortState } from './Table.types';
2
+ export interface UseTableOptions {
3
+ defaultSort?: SortState;
4
+ defaultPage?: number;
5
+ }
6
+ export interface UseTableReturn {
7
+ sort: SortState | null;
8
+ page: number;
9
+ onSortChange: (sort: SortState | null) => void;
10
+ onPageChange: (page: number) => void;
11
+ }
12
+ export declare function useTable({ defaultSort, defaultPage, }?: UseTableOptions): UseTableReturn;
@@ -1,7 +1,7 @@
1
1
  import { SortState } from './Table.types';
2
2
  interface UseTableSortProps<T> {
3
3
  data: T[];
4
- sort?: SortState;
4
+ sort?: SortState | null;
5
5
  defaultSort?: SortState;
6
6
  onSortChange?: (sort: SortState | null) => void;
7
7
  }