hazo_ui 2.11.0 → 2.16.0
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/CHANGE_LOG.md +181 -0
- package/README.md +244 -0
- package/dist/index.cjs +2149 -494
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +358 -1
- package/dist/index.d.ts +358 -1
- package/dist/index.js +2102 -459
- package/dist/index.js.map +1 -1
- package/dist/styles.css +8 -0
- package/package.json +1 -1
package/CHANGE_LOG.md
CHANGED
|
@@ -5,6 +5,187 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.16.0] - 2026-05-17
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Restyled `HazoUiMultiSortDialog` and `HazoUiMultiFilterDialog`** for a
|
|
12
|
+
modern, Linear/Notion-style appearance:
|
|
13
|
+
- Tighter dialog chrome: bordered header, padded body, bordered footer
|
|
14
|
+
bar with subtle muted background — replaces the flat, dated look.
|
|
15
|
+
- **Sort dialog**: each row now shows a numbered priority badge, a
|
|
16
|
+
segmented Asc/Desc pill (replaces the ambiguous switch), and a muted
|
|
17
|
+
delete affordance that reddens on hover.
|
|
18
|
+
- **Filter dialog**: rows have compact mathematical operator symbols
|
|
19
|
+
(`=`, `≠`, `>`, `<`, `≥`, `≤`) in a narrow select; inputs are 32px
|
|
20
|
+
tall for a denser layout; rows match the sort dialog visually.
|
|
21
|
+
- **Footer hierarchy fixed**: `Clear all` is a low-emphasis ghost
|
|
22
|
+
button on the left; `Cancel` (outline) and `Apply` (primary) are
|
|
23
|
+
grouped on the right — `Apply` is the rightmost / final action.
|
|
24
|
+
- Trigger buttons now show an active-count badge next to the icon.
|
|
25
|
+
- Empty state uses a dashed-border placeholder card; the "Add field"
|
|
26
|
+
affordance matches and disables itself when all fields are added.
|
|
27
|
+
- **Dialog animation**: removed `slide-in-from-left-1/2` /
|
|
28
|
+
`slide-out-to-left-1/2` classes from `DialogContent`. Modals now
|
|
29
|
+
fade + zoom in place from center — no more horizontal "drift" that
|
|
30
|
+
caught the dialog mid-animation looking off-screen.
|
|
31
|
+
|
|
32
|
+
### API
|
|
33
|
+
- All component props (including the color-override props on both
|
|
34
|
+
dialogs) are unchanged; this is a visual refresh, not a contract
|
|
35
|
+
change. Existing consumers do not need to update call sites.
|
|
36
|
+
|
|
37
|
+
## [2.15.1] - 2026-05-17
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
- **SSR/CSR hydration mismatch in HazoUiTable number/currency/percent
|
|
41
|
+
formatters.** `Intl.NumberFormat(undefined, …)` resolved to the
|
|
42
|
+
runtime's host locale, which differs between Node (server) and the
|
|
43
|
+
browser (client) — e.g. server `$4,812,500.00` vs. client
|
|
44
|
+
`US$4,812,500.00` when the browser default is `en-GB`. The library
|
|
45
|
+
now pins to `"en-US"` so both environments produce the same string.
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
- New `locale?: string` on `TableColumn` (BCP-47 tag, e.g. `"de-DE"`,
|
|
49
|
+
`"ja-JP"`) — opt-out for the new pinned default. Applies to `number`,
|
|
50
|
+
`currency`, and `percent` formatters. Date formatting was unaffected
|
|
51
|
+
and is unchanged (it uses `date-fns`, not `Intl`).
|
|
52
|
+
|
|
53
|
+
## [2.15.0] - 2026-05-17
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
- **Shift-click multi-sort on `HazoUiTable` headers.** Plain header
|
|
57
|
+
click still cycles asc → desc → none (single column). Shift-click
|
|
58
|
+
a header to append it as a secondary sort, cycling that column
|
|
59
|
+
asc → desc → removed without collapsing prior columns. Discoverable
|
|
60
|
+
via a `title=` tooltip on the sort button. `useTableState.cycleHeaderSort`
|
|
61
|
+
gains an `append?: boolean` parameter; new `cycleAppendColumn` helper
|
|
62
|
+
alongside the existing `cycleSingleColumn`.
|
|
63
|
+
- **Column-level `currency` override on `TableColumn`.** When `formatter`
|
|
64
|
+
is `"currency"`, the formatter reads `column.currency` (any ISO-4217
|
|
65
|
+
code, e.g. `"EUR"`, `"GBP"`) instead of the previously hard-coded
|
|
66
|
+
`"USD"`. Defaults to `"USD"` when omitted — fully backwards-compatible.
|
|
67
|
+
- **Structured `onLoad` error UX.** `HazoUiTableProps` gains
|
|
68
|
+
`error?: React.ReactNode | ((err: unknown) => React.ReactNode)`.
|
|
69
|
+
`useTableState` exposes `serverError` (always `null` outside server
|
|
70
|
+
mode). On `onLoad` rejection, the table renders the consumer's
|
|
71
|
+
`error` prop in place of rows; if none is provided, a default
|
|
72
|
+
`EmptyState` shows the thrown message. `console.warn` continues to
|
|
73
|
+
log every rejection. Branch added to both the desktop `TableBody`
|
|
74
|
+
and the mobile card view.
|
|
75
|
+
- Dev-app `/table` §9 demos the error state with a rejecting loader.
|
|
76
|
+
§2 copy mentions the new shift-click gesture.
|
|
77
|
+
|
|
78
|
+
### Internal
|
|
79
|
+
- Renamed `use_table_state` → `useTableState` (function only — the
|
|
80
|
+
filename stays `use_table_state.ts`). Aligns with the package's
|
|
81
|
+
other hooks (`useMediaQuery`, `useLoadingState`, `useErrorDisplay`)
|
|
82
|
+
and unblocks the `react-hooks/rules-of-hooks` lint rule.
|
|
83
|
+
- New ESLint v9 flat config (`eslint.config.js`) so `npm run lint`
|
|
84
|
+
works again. Scoped to `src/**/*.{ts,tsx}` with
|
|
85
|
+
`@typescript-eslint` and `react-hooks` rules.
|
|
86
|
+
|
|
87
|
+
### Notes
|
|
88
|
+
- The 200 ms search debounce default is unchanged — the spec §10
|
|
89
|
+
follow-up about tuning needs empirical data from the sister app.
|
|
90
|
+
Consumers can already override per-table via `searchDebounceMs`.
|
|
91
|
+
|
|
92
|
+
## [2.14.0] - 2026-05-17
|
|
93
|
+
|
|
94
|
+
### Added
|
|
95
|
+
- **`Table` shadcn primitive family.** Eight components (`Table`,
|
|
96
|
+
`TableHeader`, `TableBody`, `TableFooter`, `TableRow`, `TableHead`,
|
|
97
|
+
`TableCell`, `TableCaption`) re-exported under bare shadcn names.
|
|
98
|
+
Matches the canonical shadcn shape — drop-in for consumers that want
|
|
99
|
+
raw markup.
|
|
100
|
+
- **`HazoUiTable` composite.** Column-config-driven data table that
|
|
101
|
+
composes the primitive with an in-memory filter/sort/paginate
|
|
102
|
+
pipeline:
|
|
103
|
+
- `TableColumn` is the single source of truth for display, sort,
|
|
104
|
+
filter, and search metadata.
|
|
105
|
+
- Sortable headers cycle asc → desc → none and replace any
|
|
106
|
+
multi-column sort with single-column on click. Multi-column sort
|
|
107
|
+
via the optional `HazoUiMultiSortDialog` integration; indicators
|
|
108
|
+
show order numbers.
|
|
109
|
+
- Optional toolbar with debounced free-text search (200 ms),
|
|
110
|
+
`HazoUiMultiFilterDialog`, and `HazoUiMultiSortDialog`. Available
|
|
111
|
+
fields are derived from columns — declared once.
|
|
112
|
+
- Loading state via `SkeletonBar`; empty / no-results via
|
|
113
|
+
`EmptyState`.
|
|
114
|
+
- Optional pagination footer with "Showing X–Y of Z" + Prev/Next.
|
|
115
|
+
- Optional row click with full mouse + keyboard support (Enter,
|
|
116
|
+
Space) when `onRowClick` is set.
|
|
117
|
+
- Mobile card-per-row fallback below a configurable breakpoint
|
|
118
|
+
(default 768 px). Opt out with `mobileCardFallback={false}`.
|
|
119
|
+
- Optional `onLoad({ page, sort, filter })` for server-side data;
|
|
120
|
+
latest-request-wins.
|
|
121
|
+
- Dev-app `/table` route with eight demo sections covering every
|
|
122
|
+
acceptance scenario.
|
|
123
|
+
|
|
124
|
+
### Dependencies
|
|
125
|
+
- No new dependencies. Composes existing `Skeleton`, `EmptyState`,
|
|
126
|
+
`Card`, `Input`, `Button`, `HazoUiMultiSortDialog`,
|
|
127
|
+
`HazoUiMultiFilterDialog`, `useMediaQuery` from this package, plus
|
|
128
|
+
`date-fns` and `lucide-react` (already deps).
|
|
129
|
+
|
|
130
|
+
## [2.13.1] - 2026-05-16
|
|
131
|
+
|
|
132
|
+
### Changed
|
|
133
|
+
- **`KanbanEditor` is now built on `HazoUiDialog`** (was raw shadcn `Dialog` primitives). Picks up the standardized header (with `title` + `description` so Radix's missing-`Description` warning is gone naturally), the standardized Save/Cancel footer with built-in `actionButtonLoading` spinner + `actionButtonDisabled`, and a priority-tinted full-width header bar via `headerBar` + `headerBarColor` — reads `hsl(var(--hazo-kanban-priority-{p0..p3}))` for visual continuity with the card left borders. The Save button gets a `Check` icon for clarity.
|
|
134
|
+
- Auto-detect default editor now appends a **Status** select at the end (sourced from the kanban's `columns`), so users can move the card between columns from the editor out of the box without configuring `editorFields`.
|
|
135
|
+
- Field labels rendered slightly smaller and muted (`text-xs font-medium text-muted-foreground`) for a more compact SaaS density.
|
|
136
|
+
|
|
137
|
+
### Added
|
|
138
|
+
- New `'status'` value on `KanbanEditorFieldType`. Renders a `Select` pre-populated from the kanban's `columns` prop (label = `column.title`, value = `column.key`). Bound to `columnKey` by convention; saving with a different status moves the card to a new column through the consumer's `onCardSave` handler — same code path as `onMove`.
|
|
139
|
+
- Dev-app §7 editor declares `{ key: 'columnKey', type: 'status', label: 'Status' }` so the demo exercises the new field type.
|
|
140
|
+
|
|
141
|
+
### Removed
|
|
142
|
+
- The explicit `aria-describedby={undefined}` workaround on `DialogContent` from v2.13.0 — `HazoUiDialog` exposes a proper `description` prop that satisfies Radix without the escape hatch.
|
|
143
|
+
|
|
144
|
+
## [2.13.0] - 2026-05-16
|
|
145
|
+
|
|
146
|
+
### Changed (breaking, but pre-publish)
|
|
147
|
+
- **Renamed `HazoUiBoard` family to `HazoUiKanban`.** v2.12.0 was committed to git but never `npm publish`'d, so this is a hard rename with no deprecation shim. Affected exports: `HazoUiBoard` → `HazoUiKanban`, `HazoUiBoardFilter` → `HazoUiKanbanFilter`, `applyBoardFilter` → `applyKanbanFilter`, plus the corresponding type aliases (`BoardItem` → `KanbanItem`, etc.). CSS class prefixes `cls_hazo_board_*` → `cls_hazo_kanban_*`; CSS variables `--hazo-board-*` → `--hazo-kanban-*`. Dev-app route `/board` → `/kanban`.
|
|
148
|
+
|
|
149
|
+
### Added
|
|
150
|
+
- **Out-of-the-box card editor.** Each card now carries a pencil icon (top-right, opacity-0 idle, opacity-100 on hover/focus). Clicking opens a `HazoUiDialog`-shell editor. Three rendering modes:
|
|
151
|
+
1. `renderCardEditor` render-prop — full control over the body.
|
|
152
|
+
2. `editorFields` declarative config — library renders one row per field declaration. Six built-in types: `text`, `textarea`, `select`, `number`, `checkbox`, `priority`. Required-field gating disables Save when text/textarea/number fields are empty.
|
|
153
|
+
3. Neither — library auto-detects string-typed fields on the item (skipping `id`, `columnKey`, `priority`).
|
|
154
|
+
- **Async save lifecycle.** `onCardSave` returns `void | Promise<void>`. A returned Promise gates dialog close and shows a "Saving…" spinner; a rejected Promise keeps the dialog open with `ctx.error` populated. Consumer is responsible for updating `items` on success (symmetric with `onMove`).
|
|
155
|
+
- Seven new props on `HazoUiKanbanProps<T>`: `editorFields`, `editorPriorities`, `editorTitle`, `renderCardEditor`, `hideEditorFooter`, `onCardSave`, `disableEdit`.
|
|
156
|
+
- Four new public types: `KanbanEditorField`, `KanbanEditorFieldType`, `KanbanEditorCtx<T>`, `KanbanSaveEvent<T>`.
|
|
157
|
+
- Dev-app demo at `/kanban` extended with three new sections (§6 default editor, §7 declarative editor, §8 renderCardEditor override).
|
|
158
|
+
|
|
159
|
+
### Fixed
|
|
160
|
+
- Suppressed Radix `DialogContent` missing-`Description` warning in `KanbanEditor` by setting `aria-describedby={undefined}` explicitly. Caught during the v2.13.0 smoke test — every editor open emitted a console warning under React strict mode.
|
|
161
|
+
|
|
162
|
+
### Notes
|
|
163
|
+
- No new CSS variables — the editor uses existing shadcn Dialog tokens and input/select/checkbox/textarea/button variables.
|
|
164
|
+
- The pencil icon is hidden when `disableEdit={true}` OR `onCardSave` is not provided (no point letting the user edit if nothing happens on save).
|
|
165
|
+
- The pencil's `pointerdown`/`mousedown`/`keydown` events stop propagation so dnd-kit's PointerSensor doesn't interpret the click as the start of a drag.
|
|
166
|
+
- One shared `<KanbanEditor>` instance mounts at the orchestrator level, not one Dialog per card.
|
|
167
|
+
- No `SETUP_CHECKLIST.md` changes (no new setup steps for consumers).
|
|
168
|
+
|
|
169
|
+
## [2.12.0] - 2026-05-16
|
|
170
|
+
|
|
171
|
+
### Added
|
|
172
|
+
- **`HazoUiBoard`** — drag-drop kanban board primitive with mobile tab-strip / desktop column-grid layout switch via pure CSS. Wraps `@dnd-kit/*` internally; consumers see only the `HazoUi*` API. Optimistic-overlay state model: `items` is consumer-controlled; failed moves snap back via `handle.revert()` carried on the `BoardMoveEvent`.
|
|
173
|
+
- **`HazoUiBoardFilter`** — controlled-or-uncontrolled filter bar with free-text search, multi-select category chips (`ToggleGroup type="multiple"`), and single-select priority chips. Decoupled from `HazoUiBoard` — consumer applies the filter to `items` before passing in.
|
|
174
|
+
- **`applyBoardFilter`** — pure helper that filters `items` by search / categories / priority. Convenience export; consumers can swap in their own filter function.
|
|
175
|
+
- Keyboard navigation per `@dnd-kit/accessibility` patterns (Tab focus → Space pick up → Arrow keys → Space drop → Esc cancel).
|
|
176
|
+
- Custom screen-reader announcements that name the action and column; override-able via the `announcements` prop.
|
|
177
|
+
- New CSS custom properties in both `src/styles/globals.css` (dev-app input) and `src/styles/hazo-ui.css` (shipped to `dist/styles.css`): `--hazo-board-priority-p0..p3` (HSL channels), `--hazo-board-card-bg`, `--hazo-board-card-border`, `--hazo-board-column-gap`. Apps can re-theme by overriding any variable in their cascade.
|
|
178
|
+
- Dev-app demo at `dev-app/app/board/page.tsx` (route `/board`) with 5 sections: default kanban, controlled filter, uncontrolled filter, keyboard-only walkthrough, and optimistic-update + revert.
|
|
179
|
+
|
|
180
|
+
### Fixed (during the v2.12.0 cycle)
|
|
181
|
+
- **dev-app `next build` failure on pre-existing UI components.** `dev-app/package.json` pinned `@types/react` to `18.3.28` (workspace-root version) so type-check no longer fails in `src/components/ui/button-group.tsx` and `toggle-group.tsx` with "Type 'bigint' is not assignable to type 'ReactNode'". Pre-existing infrastructure issue surfaced during board demo wiring.
|
|
182
|
+
|
|
183
|
+
### Notes
|
|
184
|
+
- Sub-components `BoardColumn` and `BoardCard` are intentionally internal — the data-driven `HazoUiBoard` API (`columns`, `items`, `renderCard`) is the only entry point.
|
|
185
|
+
- `HazoUiBoard` passes a `React.useId()`-derived value as `DndContext`'s `id` prop so multiple boards on one page produce stable, SSR-deterministic `aria-describedby` IDs (no hydration warnings).
|
|
186
|
+
- No new dependencies (`@dnd-kit/*` was already in `dependencies` from prior versions).
|
|
187
|
+
- No `SETUP_CHECKLIST.md` changes (no new setup steps for consumers).
|
|
188
|
+
|
|
8
189
|
## [2.11.0] - 2026-05-16
|
|
9
190
|
|
|
10
191
|
### Fixed
|
package/README.md
CHANGED
|
@@ -202,6 +202,8 @@ The following components support both global config and prop-level color overrid
|
|
|
202
202
|
|
|
203
203
|
- **[HazoUiConfirmDialog](#hazouiconfirmdialog)** - A compact, opinionated confirmation dialog with accent top border, variant system (destructive, warning, info, success), async loading support, and configurable buttons. Perfect for delete confirmations, unsaved changes warnings, and simple acknowledgments.
|
|
204
204
|
|
|
205
|
+
- **[HazoUiTable](#hazouitable--column-config-driven-data-table-v2140)** - A column-config-driven data table built on a shadcn `Table` primitive family. Sortable headers, debounced search, multi-column filter / sort dialogs, pagination, row click (mouse + keyboard), loading / empty / no-results states, and a card-per-row mobile fallback. Optional server-side `onLoad`.
|
|
206
|
+
|
|
205
207
|
- **[Drawer](#drawer)** - A `vaul`-backed bottom sheet primitive for mobile UIs. Pair with `useMediaQuery` to swap between `Dialog` and `Drawer` based on viewport width.
|
|
206
208
|
|
|
207
209
|
### State Primitives (v2.10.0)
|
|
@@ -3317,6 +3319,248 @@ Returns `{ error: string | null; setError: (v: unknown) => void; clearError: ()
|
|
|
3317
3319
|
|
|
3318
3320
|
---
|
|
3319
3321
|
|
|
3322
|
+
## HazoUiKanban — Drag-Drop Kanban (v2.13.0+)
|
|
3323
|
+
|
|
3324
|
+
Drag-drop board with mobile tab-strip / desktop column-grid layouts,
|
|
3325
|
+
optimistic updates with revert, theme-able priority borders, and
|
|
3326
|
+
keyboard-driven DnD. Wraps `@dnd-kit` internally.
|
|
3327
|
+
|
|
3328
|
+
### Minimal usage
|
|
3329
|
+
|
|
3330
|
+
```tsx
|
|
3331
|
+
import {
|
|
3332
|
+
HazoUiKanban,
|
|
3333
|
+
HazoUiKanbanFilter,
|
|
3334
|
+
applyKanbanFilter,
|
|
3335
|
+
type KanbanColumn,
|
|
3336
|
+
type KanbanFilterValue,
|
|
3337
|
+
} from 'hazo_ui';
|
|
3338
|
+
|
|
3339
|
+
const COLUMNS: KanbanColumn[] = [
|
|
3340
|
+
{ key: 'todo', title: 'To Do' },
|
|
3341
|
+
{ key: 'in_progress', title: 'In Progress' },
|
|
3342
|
+
{ key: 'blocked', title: 'Blocked' },
|
|
3343
|
+
{ key: 'done', title: 'Done' },
|
|
3344
|
+
];
|
|
3345
|
+
|
|
3346
|
+
function Actions({ initial }) {
|
|
3347
|
+
const [items, setItems] = useState(initial);
|
|
3348
|
+
const [filter, setFilter] = useState<KanbanFilterValue>({
|
|
3349
|
+
search: '', categories: [], priority: null,
|
|
3350
|
+
});
|
|
3351
|
+
const visible = useMemo(() => applyKanbanFilter(items, filter), [items, filter]);
|
|
3352
|
+
|
|
3353
|
+
return (
|
|
3354
|
+
<>
|
|
3355
|
+
<HazoUiKanbanFilter
|
|
3356
|
+
categories={['On-page SEO', 'Technical SEO', 'Content']}
|
|
3357
|
+
priorities={['P0', 'P1', 'P2', 'P3']}
|
|
3358
|
+
value={filter}
|
|
3359
|
+
onChange={setFilter}
|
|
3360
|
+
/>
|
|
3361
|
+
<HazoUiKanban
|
|
3362
|
+
columns={COLUMNS}
|
|
3363
|
+
items={visible}
|
|
3364
|
+
renderCard={(action) => <ActionCard action={action} />}
|
|
3365
|
+
itemLabel={(action) => `action ${action.id}`}
|
|
3366
|
+
onMove={async (event) => {
|
|
3367
|
+
try {
|
|
3368
|
+
const res = await fetch(`/api/v1/actions/${event.itemId}`, {
|
|
3369
|
+
method: 'PATCH',
|
|
3370
|
+
body: JSON.stringify({ status: event.toColumn }),
|
|
3371
|
+
});
|
|
3372
|
+
if (!res.ok) throw new Error('PATCH failed');
|
|
3373
|
+
setItems(prev => prev.map(it =>
|
|
3374
|
+
it.id === event.itemId ? { ...it, columnKey: event.toColumn } : it,
|
|
3375
|
+
));
|
|
3376
|
+
} catch {
|
|
3377
|
+
event.revert();
|
|
3378
|
+
}
|
|
3379
|
+
}}
|
|
3380
|
+
/>
|
|
3381
|
+
</>
|
|
3382
|
+
);
|
|
3383
|
+
}
|
|
3384
|
+
```
|
|
3385
|
+
|
|
3386
|
+
### Optimistic update + revert
|
|
3387
|
+
|
|
3388
|
+
`onMove` fires after the library has already moved the card visually. The
|
|
3389
|
+
event carries `revert()` — call it when your API request fails and the
|
|
3390
|
+
overlay snaps back to whatever `items` says.
|
|
3391
|
+
|
|
3392
|
+
### Theming (CSS custom properties)
|
|
3393
|
+
|
|
3394
|
+
| Variable | Default | Purpose |
|
|
3395
|
+
|---|---|---|
|
|
3396
|
+
| `--hazo-kanban-priority-p0` | `0 84% 60%` (red) | Left border for `P0` cards |
|
|
3397
|
+
| `--hazo-kanban-priority-p1` | `45 93% 55%` (yellow) | Left border for `P1` cards |
|
|
3398
|
+
| `--hazo-kanban-priority-p2` | `217 91% 60%` (blue) | Left border for `P2` cards |
|
|
3399
|
+
| `--hazo-kanban-priority-p3` | `220 9% 64%` (grey) | Left border for `P3` cards |
|
|
3400
|
+
| `--hazo-kanban-card-bg` | `var(--card)` | Card background |
|
|
3401
|
+
| `--hazo-kanban-card-border` | `var(--border)` | Card border |
|
|
3402
|
+
| `--hazo-kanban-column-gap` | `0.75rem` | Gap between columns |
|
|
3403
|
+
|
|
3404
|
+
Values are HSL channels (no `hsl()` wrapper) to match the shadcn theme
|
|
3405
|
+
convention. Custom priorities like `'critical'` work — define
|
|
3406
|
+
`--hazo-kanban-priority-critical` and pass `priority: 'critical'`.
|
|
3407
|
+
|
|
3408
|
+
### Visual reference
|
|
3409
|
+
|
|
3410
|
+
Run `npm run dev:test-app` and visit `/board` for five demo scenarios
|
|
3411
|
+
(default kanban, controlled / uncontrolled filter, keyboard-only flow,
|
|
3412
|
+
and optimistic + revert).
|
|
3413
|
+
|
|
3414
|
+
### Out-of-the-box card editor
|
|
3415
|
+
|
|
3416
|
+
Each card carries a pencil icon (top-right, opacity-0 idle, opacity-100
|
|
3417
|
+
on hover/focus). Clicking it opens a `HazoUiDialog` editor:
|
|
3418
|
+
|
|
3419
|
+
```tsx
|
|
3420
|
+
<HazoUiKanban
|
|
3421
|
+
columns={COLUMNS}
|
|
3422
|
+
items={items}
|
|
3423
|
+
renderCard={(action) => <ActionCard action={action} />}
|
|
3424
|
+
// Declarative field config — library renders one row per declaration.
|
|
3425
|
+
editorFields={[
|
|
3426
|
+
{ key: 'title', type: 'textarea', required: true },
|
|
3427
|
+
{ key: 'category', type: 'select', options: CATEGORIES },
|
|
3428
|
+
{ key: 'priority', type: 'priority' },
|
|
3429
|
+
]}
|
|
3430
|
+
onCardSave={async (event) => {
|
|
3431
|
+
const res = await fetch(`/api/v1/actions/${event.itemId}`, {
|
|
3432
|
+
method: 'PATCH',
|
|
3433
|
+
body: JSON.stringify(event.next),
|
|
3434
|
+
});
|
|
3435
|
+
if (!res.ok) throw new Error('PATCH failed');
|
|
3436
|
+
setItems(prev => prev.map(it =>
|
|
3437
|
+
it.id === event.itemId ? event.next : it,
|
|
3438
|
+
));
|
|
3439
|
+
}}
|
|
3440
|
+
/>
|
|
3441
|
+
```
|
|
3442
|
+
|
|
3443
|
+
Field types: `text`, `textarea`, `select`, `number`, `checkbox`,
|
|
3444
|
+
`priority` (a select pre-populated with `editorPriorities` —
|
|
3445
|
+
defaults to `["P0","P1","P2","P3"]`).
|
|
3446
|
+
|
|
3447
|
+
If you don't pass `editorFields`, the library auto-detects all
|
|
3448
|
+
string-valued fields on the item (except `id`, `columnKey`, `priority`)
|
|
3449
|
+
and renders each as a text input with a humanized label.
|
|
3450
|
+
|
|
3451
|
+
#### Custom form via renderCardEditor
|
|
3452
|
+
|
|
3453
|
+
When the declarative config isn't enough, replace the dialog body
|
|
3454
|
+
with your own form:
|
|
3455
|
+
|
|
3456
|
+
```tsx
|
|
3457
|
+
<HazoUiKanban
|
|
3458
|
+
// ...
|
|
3459
|
+
renderCardEditor={(item, ctx) => (
|
|
3460
|
+
<MyComplexForm
|
|
3461
|
+
value={ctx.draft}
|
|
3462
|
+
onChange={(next) => ctx.setDraft(next)}
|
|
3463
|
+
saving={ctx.saving}
|
|
3464
|
+
error={ctx.error}
|
|
3465
|
+
/>
|
|
3466
|
+
)}
|
|
3467
|
+
onCardSave={async (event) => { /* ... */ }}
|
|
3468
|
+
/>
|
|
3469
|
+
```
|
|
3470
|
+
|
|
3471
|
+
`ctx` provides `draft`, `setDraft`, `save()`, `close()`, `saving`, `error`,
|
|
3472
|
+
and `isDirty`. The library still renders the dialog shell, title, and
|
|
3473
|
+
the default Save/Cancel footer. To render your own buttons too, pass
|
|
3474
|
+
`hideEditorFooter={true}` and call `ctx.save()`/`ctx.close()` from
|
|
3475
|
+
within your form.
|
|
3476
|
+
|
|
3477
|
+
#### Save lifecycle
|
|
3478
|
+
|
|
3479
|
+
`onCardSave` returns `void | Promise<void>`. A returned Promise gates
|
|
3480
|
+
the dialog close and shows a `Saving…` spinner; a rejected Promise
|
|
3481
|
+
keeps the dialog open with `ctx.error` populated. Consumer is
|
|
3482
|
+
responsible for updating `items` on success — symmetric with `onMove`.
|
|
3483
|
+
|
|
3484
|
+
If `onCardSave` is not provided, the pencil is hidden entirely
|
|
3485
|
+
(read-only kanban). To explicitly hide editing without removing
|
|
3486
|
+
`onCardSave`, pass `disableEdit={true}`.
|
|
3487
|
+
|
|
3488
|
+
---
|
|
3489
|
+
|
|
3490
|
+
## HazoUiTable — Column-config-driven Data Table (v2.14.0+, v2.15 additions)
|
|
3491
|
+
|
|
3492
|
+
A higher-level data table that composes the shadcn `Table` primitive
|
|
3493
|
+
family with the existing `HazoUiMultiSortDialog` /
|
|
3494
|
+
`HazoUiMultiFilterDialog` and an in-memory filter / sort / paginate
|
|
3495
|
+
pipeline.
|
|
3496
|
+
|
|
3497
|
+
```tsx
|
|
3498
|
+
import { HazoUiTable, type TableColumn } from 'hazo_ui';
|
|
3499
|
+
|
|
3500
|
+
interface Run {
|
|
3501
|
+
id: string;
|
|
3502
|
+
date: Date;
|
|
3503
|
+
status: 'ok' | 'fail';
|
|
3504
|
+
count: number;
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
const columns: TableColumn<Run>[] = [
|
|
3508
|
+
{ key: 'date', label: 'Date', sortable: true, formatter: 'date', filterType: 'date' },
|
|
3509
|
+
{
|
|
3510
|
+
key: 'status',
|
|
3511
|
+
label: 'Status',
|
|
3512
|
+
sortable: true,
|
|
3513
|
+
filterType: 'combobox',
|
|
3514
|
+
filterConfig: {
|
|
3515
|
+
comboboxOptions: [
|
|
3516
|
+
{ label: 'OK', value: 'ok' },
|
|
3517
|
+
{ label: 'Fail', value: 'fail' },
|
|
3518
|
+
],
|
|
3519
|
+
},
|
|
3520
|
+
},
|
|
3521
|
+
{ key: 'count', label: 'Count', sortable: true, formatter: 'number', align: 'right' },
|
|
3522
|
+
// v2.15+ — column-level currency override (defaults to USD)
|
|
3523
|
+
{ key: 'revenue', label: 'Revenue', sortable: true, formatter: 'currency', currency: 'EUR', align: 'right' },
|
|
3524
|
+
];
|
|
3525
|
+
|
|
3526
|
+
<HazoUiTable<Run>
|
|
3527
|
+
columns={columns}
|
|
3528
|
+
rows={runs}
|
|
3529
|
+
getRowKey={(r) => r.id}
|
|
3530
|
+
enableSearch
|
|
3531
|
+
enableSortDialog
|
|
3532
|
+
enableFilterDialog
|
|
3533
|
+
pagination={{ pageSize: 25 }}
|
|
3534
|
+
onRowClick={(r) => router.push(`/runs/${r.id}`)}
|
|
3535
|
+
/>
|
|
3536
|
+
```
|
|
3537
|
+
|
|
3538
|
+
Features:
|
|
3539
|
+
|
|
3540
|
+
- Header click cycles asc → desc → none. **Shift-click** a second
|
|
3541
|
+
header to append it as a secondary sort (v2.15). Multi-column sort
|
|
3542
|
+
also available via the optional sort dialog.
|
|
3543
|
+
- Free-text search across string-typed columns (debounced 200 ms).
|
|
3544
|
+
- Per-column filters via the filter dialog — declarable per column.
|
|
3545
|
+
- Loading state via `SkeletonBar`, empty / no-results via `EmptyState`.
|
|
3546
|
+
- Pagination footer with Prev / Next.
|
|
3547
|
+
- Row click with mouse and keyboard (Enter / Space) when `onRowClick`
|
|
3548
|
+
is set.
|
|
3549
|
+
- Mobile card-per-row fallback below `mobileBreakpoint` (default
|
|
3550
|
+
768 px). Opt out with `mobileCardFallback={false}`.
|
|
3551
|
+
- Optional `onLoad({ page, sort, filter })` for server-driven
|
|
3552
|
+
pagination — latest-request-wins.
|
|
3553
|
+
- Structured error UX (v2.15): pass `error={node | (err) => node}`
|
|
3554
|
+
to render a custom failure state when `onLoad` rejects; otherwise
|
|
3555
|
+
the table falls back to an `EmptyState` showing the thrown message.
|
|
3556
|
+
- Per-column `currency` (v2.15) for ISO-4217 codes other than USD.
|
|
3557
|
+
|
|
3558
|
+
The package also re-exports the bare shadcn primitives — `Table`,
|
|
3559
|
+
`TableHeader`, `TableBody`, `TableFooter`, `TableHead`, `TableRow`,
|
|
3560
|
+
`TableCell`, `TableCaption` — for consumers that prefer raw markup.
|
|
3561
|
+
|
|
3562
|
+
---
|
|
3563
|
+
|
|
3320
3564
|
## Troubleshooting
|
|
3321
3565
|
|
|
3322
3566
|
### Styles not applying (Tailwind v4)
|