hazo_ui 6.5.0 → 6.7.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 +75 -0
- package/dist/index.cjs +62 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +62 -33
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/CHANGE_LOG.md
CHANGED
|
@@ -5,6 +5,81 @@ 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
|
+
## v6.7.0 (2026-08-02)
|
|
9
|
+
|
|
10
|
+
### Added — `data-column-key` / `data-lane-key` on the kanban DOM
|
|
11
|
+
|
|
12
|
+
`cls_hazo_kanban_column` is one class shared by every column, so a consumer
|
|
13
|
+
whose columns carry meaning — an inbox is not a done pile — had no way to
|
|
14
|
+
style one differently. The only lever available was `:nth-child`, which
|
|
15
|
+
silently mis-paints the moment columns are reordered, filtered, or a lane
|
|
16
|
+
renders a subset. These attributes make the identity the component already
|
|
17
|
+
knows about addressable from CSS:
|
|
18
|
+
|
|
19
|
+
| Element | Attributes |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `.cls_hazo_kanban_column` | `data-column-key`, `data-lane-key` |
|
|
22
|
+
| `.cls_hazo_kanban_column_head` | `data-column-key`, `data-lane-key` |
|
|
23
|
+
| `.cls_hazo_kanban_column_body` | `data-column-key`, `data-lane-key` |
|
|
24
|
+
| `.cls_hazo_kanban_card` | `data-column-key`, `data-lane-key`, `data-section-key` |
|
|
25
|
+
|
|
26
|
+
`data-lane-key` is absent (not empty) on an unlaned board, so
|
|
27
|
+
`[data-lane-key]` is a usable "is this board laned" test. Both layout trees
|
|
28
|
+
always render — pair with the ancestor `.cls_hazo_kanban_desktop` /
|
|
29
|
+
`.cls_hazo_kanban_mobile` when a rule must apply to only one.
|
|
30
|
+
|
|
31
|
+
Purely additive: no class, layout, or default paint changed.
|
|
32
|
+
|
|
33
|
+
### Added — `--hazo-kanban-card-shadow`
|
|
34
|
+
|
|
35
|
+
Cards drew `background: hsl(var(--hazo-kanban-card-bg))` with a 1px border
|
|
36
|
+
and nothing else. Under any palette where `--card` and `--background` resolve
|
|
37
|
+
to the same value (the shadcn-derived default in `hazo_theme`, in both light
|
|
38
|
+
and dark), that border is the *only* thing separating a card from the column
|
|
39
|
+
behind it, and the board reads as flat.
|
|
40
|
+
|
|
41
|
+
The card's `box-shadow` now resolves `var(--hazo-kanban-card-shadow, none)`.
|
|
42
|
+
The fallback is `none`, so a consumer that never declares the token renders
|
|
43
|
+
byte-identically to v6.6.0. Set it (e.g. to `var(--shadow-sm)`) to give cards
|
|
44
|
+
a real lift.
|
|
45
|
+
|
|
46
|
+
## v6.6.0 (2026-08-02)
|
|
47
|
+
|
|
48
|
+
### Added — consumer slots on a laned `HazoUiKanban`
|
|
49
|
+
|
|
50
|
+
v6.5.0 gave the board a second, outer axis but no way to hang anything off
|
|
51
|
+
it. A lane could be dropped into and counted, and that was all — there was no
|
|
52
|
+
supported place to put "add an item to this lane", a per-lane overflow menu,
|
|
53
|
+
or an inline composer at the foot of a column. Both slots below are additive
|
|
54
|
+
and default to rendering nothing, so existing boards are byte-identical.
|
|
55
|
+
|
|
56
|
+
- **`KanbanLane.actions?: React.ReactNode`** — rendered in the lane heading
|
|
57
|
+
between the rule and the count, and right-aligned on a `collapseWhenEmpty`
|
|
58
|
+
lane's drop strip so a lane's controls do not vanish the moment it empties.
|
|
59
|
+
No `stopPropagation` is needed on controls placed here: a lane heading is a
|
|
60
|
+
`useDroppable` node, which registers a rect but attaches no pointer
|
|
61
|
+
handlers, unlike the `useSortable` card shell.
|
|
62
|
+
- **`HazoUiKanbanProps.renderColumnFooter?: (ctx) => React.ReactNode`** —
|
|
63
|
+
rendered under a column body, below the cards or the `emptyColumn`
|
|
64
|
+
placeholder. `ctx` is `{ columnKey, laneKey }` and the function is called
|
|
65
|
+
once per rendered copy of a column, so on a laned board it fires once per
|
|
66
|
+
(lane, column) pair — enough to build a composer scoped to one lane's
|
|
67
|
+
Inbox. Return `null` for the columns that should not have one.
|
|
68
|
+
- The footer sits inside the column's droppable but **outside** its
|
|
69
|
+
`SortableContext`, so it never joins the sort order or becomes a drop
|
|
70
|
+
target of its own.
|
|
71
|
+
- The unmatched-lane row is deliberately skipped: its lane key is the
|
|
72
|
+
private `__hazo_lane_unmatched__` sentinel, never a lane the consumer
|
|
73
|
+
declared, so a footer there would both leak an internal constant and ask
|
|
74
|
+
for controls scoped to a lane that does not exist.
|
|
75
|
+
|
|
76
|
+
New class hooks: `cls_hazo_kanban_lane_actions`,
|
|
77
|
+
`cls_hazo_kanban_column_footer`. The lane strip's title is now wrapped in its
|
|
78
|
+
own `cls_hazo_kanban_lane_title` span (it was a bare text node) so the
|
|
79
|
+
right-aligned actions have something to sit beside.
|
|
80
|
+
|
|
81
|
+
See `test-app/app/kanban/` §12.
|
|
82
|
+
|
|
8
83
|
## v6.5.0 (2026-08-02)
|
|
9
84
|
|
|
10
85
|
### Added — swimlanes for `HazoUiKanban`
|
package/dist/index.cjs
CHANGED
|
@@ -9108,6 +9108,12 @@ function KanbanCard({
|
|
|
9108
9108
|
backgroundColor: "hsl(var(--hazo-kanban-card-bg))",
|
|
9109
9109
|
border: `1px solid hsl(var(--hazo-kanban-card-border))`,
|
|
9110
9110
|
borderLeft: priority_var ? `4px solid hsl(var(${priority_var}))` : `1px solid hsl(var(--hazo-kanban-card-border))`,
|
|
9111
|
+
// Opt-in lift. A card whose background equals the column's (the case for
|
|
9112
|
+
// every palette where --card and --background resolve to the same value)
|
|
9113
|
+
// is separated by nothing but a 1px border, which reads as flat at a
|
|
9114
|
+
// glance. The fallback is `none`, so a consumer that never sets the token
|
|
9115
|
+
// renders exactly as before.
|
|
9116
|
+
boxShadow: "var(--hazo-kanban-card-shadow, none)",
|
|
9111
9117
|
opacity: isDragging && !dragging ? 0.4 : 1,
|
|
9112
9118
|
cursor: "grab"
|
|
9113
9119
|
};
|
|
@@ -9116,6 +9122,9 @@ function KanbanCard({
|
|
|
9116
9122
|
{
|
|
9117
9123
|
ref: setNodeRef,
|
|
9118
9124
|
style,
|
|
9125
|
+
"data-column-key": item.columnKey,
|
|
9126
|
+
"data-lane-key": item.laneKey,
|
|
9127
|
+
"data-section-key": item.sectionKey,
|
|
9119
9128
|
className: cn(
|
|
9120
9129
|
"cls_hazo_kanban_card group relative rounded-md p-3 mb-2 select-none",
|
|
9121
9130
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
@@ -9187,6 +9196,7 @@ function KanbanColumn({
|
|
|
9187
9196
|
items,
|
|
9188
9197
|
renderCard,
|
|
9189
9198
|
emptyColumn,
|
|
9199
|
+
footer,
|
|
9190
9200
|
cardClassName,
|
|
9191
9201
|
showEdit,
|
|
9192
9202
|
onEditRequest,
|
|
@@ -9232,6 +9242,8 @@ function KanbanColumn({
|
|
|
9232
9242
|
"div",
|
|
9233
9243
|
{
|
|
9234
9244
|
ref: setNodeRef,
|
|
9245
|
+
"data-column-key": column.key,
|
|
9246
|
+
"data-lane-key": laneKey,
|
|
9235
9247
|
className: cn(
|
|
9236
9248
|
"cls_hazo_kanban_column flex flex-col rounded-md p-2",
|
|
9237
9249
|
isOver && "cls_hazo_kanban_column_over bg-accent/30 ring-2 ring-ring/40"
|
|
@@ -9253,34 +9265,43 @@ function KanbanColumn({
|
|
|
9253
9265
|
{
|
|
9254
9266
|
items: ordered.map((i) => i.id),
|
|
9255
9267
|
strategy: sortable.verticalListSortingStrategy,
|
|
9256
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
section
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
section
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9268
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
9269
|
+
"div",
|
|
9270
|
+
{
|
|
9271
|
+
"data-column-key": column.key,
|
|
9272
|
+
"data-lane-key": laneKey,
|
|
9273
|
+
className: "cls_hazo_kanban_column_body flex-1 min-h-[40px]",
|
|
9274
|
+
children: !sections ? items.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_column_empty text-xs text-muted-foreground py-2 px-1", children: emptyColumn }) : render_cards(items) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
9275
|
+
expanded_sections.map((section) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
9276
|
+
KanbanSectionBand,
|
|
9277
|
+
{
|
|
9278
|
+
columnKey: column.key,
|
|
9279
|
+
laneKey,
|
|
9280
|
+
treeKey,
|
|
9281
|
+
section,
|
|
9282
|
+
items: bySection.get(section.key) ?? [],
|
|
9283
|
+
emptyColumn,
|
|
9284
|
+
renderCards: render_cards
|
|
9285
|
+
},
|
|
9286
|
+
section.key
|
|
9287
|
+
)),
|
|
9288
|
+
unmatched.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_section cls_hazo_kanban_section_unmatched", children: render_cards(unmatched) }),
|
|
9289
|
+
collapsed_sections.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_section_strips mt-2 flex flex-wrap gap-1 border-t border-dashed border-border pt-2", children: collapsed_sections.map((section) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
9290
|
+
KanbanSectionStrip,
|
|
9291
|
+
{
|
|
9292
|
+
columnKey: column.key,
|
|
9293
|
+
laneKey,
|
|
9294
|
+
treeKey,
|
|
9295
|
+
section
|
|
9296
|
+
},
|
|
9297
|
+
section.key
|
|
9298
|
+
)) })
|
|
9299
|
+
] })
|
|
9300
|
+
}
|
|
9301
|
+
)
|
|
9282
9302
|
}
|
|
9283
|
-
)
|
|
9303
|
+
),
|
|
9304
|
+
footer ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_column_footer mt-1", children: footer }) : null
|
|
9284
9305
|
]
|
|
9285
9306
|
}
|
|
9286
9307
|
);
|
|
@@ -9302,6 +9323,8 @@ function KanbanColumnHead({
|
|
|
9302
9323
|
"div",
|
|
9303
9324
|
{
|
|
9304
9325
|
ref: droppable ? setNodeRef : void 0,
|
|
9326
|
+
"data-column-key": columnKey,
|
|
9327
|
+
"data-lane-key": laneKey,
|
|
9305
9328
|
className: cn(
|
|
9306
9329
|
"cls_hazo_kanban_column_head mb-2 flex items-center justify-between rounded-sm px-1 text-xs uppercase tracking-wider text-muted-foreground",
|
|
9307
9330
|
droppable && "py-1",
|
|
@@ -9391,6 +9414,7 @@ function KanbanLaneHead({
|
|
|
9391
9414
|
children: [
|
|
9392
9415
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_lane_title text-xs font-semibold uppercase tracking-wider text-foreground", children: lane.title }),
|
|
9393
9416
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_lane_rule h-px flex-1 bg-border" }),
|
|
9417
|
+
lane.actions ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_lane_actions flex items-center gap-1", children: lane.actions }) : null,
|
|
9394
9418
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_lane_count rounded-full bg-muted px-2 py-0.5 text-[11px] tabular-nums text-foreground", children: count })
|
|
9395
9419
|
]
|
|
9396
9420
|
}
|
|
@@ -9404,15 +9428,18 @@ function KanbanLaneStrip({
|
|
|
9404
9428
|
id: lane_droppable_id(lane.key, treeKey),
|
|
9405
9429
|
data: { laneKey: lane.key, isLane: true }
|
|
9406
9430
|
});
|
|
9407
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
9431
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9408
9432
|
"div",
|
|
9409
9433
|
{
|
|
9410
9434
|
ref: setNodeRef,
|
|
9411
9435
|
className: cn(
|
|
9412
|
-
"cls_hazo_kanban_lane_strip mb-2 rounded-md border border-dashed border-border px-2 py-1 text-[11px] uppercase tracking-wider text-muted-foreground",
|
|
9436
|
+
"cls_hazo_kanban_lane_strip mb-2 flex items-center gap-3 rounded-md border border-dashed border-border px-2 py-1 text-[11px] uppercase tracking-wider text-muted-foreground",
|
|
9413
9437
|
isOver && "cls_hazo_kanban_lane_strip_over border-solid bg-accent/30 text-foreground ring-1 ring-ring/30"
|
|
9414
9438
|
),
|
|
9415
|
-
children:
|
|
9439
|
+
children: [
|
|
9440
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_lane_title", children: lane.title }),
|
|
9441
|
+
lane.actions ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_lane_actions ml-auto flex items-center gap-1", children: lane.actions }) : null
|
|
9442
|
+
]
|
|
9416
9443
|
}
|
|
9417
9444
|
);
|
|
9418
9445
|
}
|
|
@@ -9885,6 +9912,7 @@ function HazoUiKanban({
|
|
|
9885
9912
|
mobileBreakpoint = 640,
|
|
9886
9913
|
announcements,
|
|
9887
9914
|
emptyColumn = "\u2014",
|
|
9915
|
+
renderColumnFooter,
|
|
9888
9916
|
className,
|
|
9889
9917
|
cardClassName,
|
|
9890
9918
|
itemLabel,
|
|
@@ -10138,7 +10166,7 @@ function HazoUiKanban({
|
|
|
10138
10166
|
total += (grouped.get(bucket_key(lane_key, col.key)) ?? []).length;
|
|
10139
10167
|
return total;
|
|
10140
10168
|
}
|
|
10141
|
-
function render_column(col, tree_key, lane_key) {
|
|
10169
|
+
function render_column(col, tree_key, lane_key, with_footer = true) {
|
|
10142
10170
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
10143
10171
|
KanbanColumn,
|
|
10144
10172
|
{
|
|
@@ -10148,6 +10176,7 @@ function HazoUiKanban({
|
|
|
10148
10176
|
items: grouped.get(bucket_key(lane_key, col.key)) ?? [],
|
|
10149
10177
|
renderCard,
|
|
10150
10178
|
emptyColumn,
|
|
10179
|
+
footer: with_footer ? renderColumnFooter?.({ columnKey: col.key, laneKey: lane_key }) ?? null : null,
|
|
10151
10180
|
cardClassName,
|
|
10152
10181
|
showEdit: show_edit,
|
|
10153
10182
|
onEditRequest: handle_edit_request
|
|
@@ -10168,8 +10197,8 @@ function HazoUiKanban({
|
|
|
10168
10197
|
function render_unmatched_lane(tree_key, only_column) {
|
|
10169
10198
|
const count = only_column ? (grouped.get(bucket_key(UNMATCHED_LANE_KEY, only_column.key)) ?? []).length : lane_total(UNMATCHED_LANE_KEY);
|
|
10170
10199
|
if (count === 0) return null;
|
|
10171
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_lane cls_hazo_kanban_lane_unmatched", children: only_column ? render_column(only_column, tree_key, UNMATCHED_LANE_KEY) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_lane_grid grid gap-3", style: grid_style, children: columns.map(
|
|
10172
|
-
(col) => render_column(col, tree_key, UNMATCHED_LANE_KEY)
|
|
10200
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_lane cls_hazo_kanban_lane_unmatched", children: only_column ? render_column(only_column, tree_key, UNMATCHED_LANE_KEY, false) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_lane_grid grid gap-3", style: grid_style, children: columns.map(
|
|
10201
|
+
(col) => render_column(col, tree_key, UNMATCHED_LANE_KEY, false)
|
|
10173
10202
|
) }) });
|
|
10174
10203
|
}
|
|
10175
10204
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|