hazo_ui 6.8.1 → 6.10.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 CHANGED
@@ -5,6 +5,84 @@ 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.10.0 (2026-08-15)
9
+
10
+ ### Added — `HazoUiKanban.renderColumnHeaderExtra`
11
+
12
+ `renderColumnFooter` already gave consumers a slot under a column's body;
13
+ there was no equivalent for the header. `renderColumnHeaderExtra` fills that
14
+ gap — a render-prop that fires once per rendered copy of a column and returns
15
+ a node placed inline in the header, next to the title and count badge:
16
+
17
+ ```tsx
18
+ <HazoUiKanban
19
+ columns={columns}
20
+ items={items}
21
+ renderCard={(item) => <Card item={item} />}
22
+ renderColumnHeaderExtra={({ columnKey, laneKey }) =>
23
+ columnKey !== "todo" ? null : (
24
+ <SegmentedToggle value={view} onChange={setView} options={["All", "Recent"]} />
25
+ )
26
+ }
27
+ />
28
+ ```
29
+
30
+ Same lane-scoping caveat as `renderColumnFooter`: on a laned board it fires
31
+ once per (lane, column) pair, and the unmatched-lane sentinel row never gets
32
+ one. The internal `render_column` parameter that gated this (`with_footer`)
33
+ is renamed `with_consumer_callbacks` since it now gates both render-props for
34
+ the same reason — leaking `UNMATCHED_LANE_KEY` to a consumer callback isn't
35
+ specific to the footer.
36
+
37
+ The returned node is wrapped so its pointer/mouse/key events stop
38
+ propagation before reaching dnd-kit, the same guard already used by the
39
+ card's pencil-edit affordance — this matters on a sectioned column, where the
40
+ header is itself a droppable, and is a harmless no-op elsewhere.
41
+
42
+ ## v6.9.0 (2026-08-06)
43
+
44
+ ### Added — `Badge`, `StatusBadge`, `makeStatusBadge`
45
+
46
+ A generic, token-backed pill component plus a status-mapping layer built on
47
+ top of it:
48
+
49
+ ```tsx
50
+ import { Badge, StatusBadge, makeStatusBadge } from "hazo_ui";
51
+
52
+ <Badge tone="success" dot>Active</Badge>
53
+
54
+ <StatusBadge
55
+ status={order.status}
56
+ statusMap={{
57
+ pending: { label: "Pending", tone: "warning", dot: true, pulse: true },
58
+ shipped: { label: "Shipped", tone: "info" },
59
+ done: { label: "Done", tone: "success" },
60
+ }}
61
+ fallback={{ label: "Unknown", tone: "neutral" }}
62
+ />
63
+
64
+ // Pre-bind a statusMap once per status enum:
65
+ const OrderStatusBadge = makeStatusBadge<OrderStatus>(orderStatusMap);
66
+ <OrderStatusBadge status={order.status} />
67
+ ```
68
+
69
+ `Badge` is a `cva`-driven soft-fill pill (`button.tsx`'s pattern) with 8
70
+ semantic `tone`s (`neutral`/`primary`/`secondary`/`accent`/`success`/
71
+ `warning`/`destructive`/`info`), all mapped to existing theme tokens — no
72
+ hardcoded hex. Optional `dot`/`pulse` render a leading status dot, colored via
73
+ a literal-string tone→class lookup (a templated `bg-${tone}` would compile to
74
+ zero CSS under Tailwind's JIT scanner). `className` passthrough via `cn()`
75
+ lets consumers override pill size or add mono/uppercase/border styling —
76
+ v1 deliberately has no solid/outline `variant` axis.
77
+
78
+ `StatusBadge<S extends string>` resolves `statusMap[status] ?? fallback ??
79
+ { label: String(status), tone: "neutral" }` and renders a `Badge`.
80
+ `makeStatusBadge(statusMap, fallback?)` pre-binds the map so call sites don't
81
+ repeat it. Generalises kinstripe/seo_monitor's per-app inline status-pill
82
+ duplication. New exports: `Badge`, `StatusBadge`, `makeStatusBadge`,
83
+ `badgeVariants`, and types `BadgeProps`, `BadgeTone`, `StatusBadgeItem`,
84
+ `StatusBadgeProps`.
85
+
8
86
  ## v6.8.0 (2026-08-03)
9
87
 
10
88
  ### Added — text prefixes: `PrefixConfig.insert_as_text` + `CommandItem.insert_text`
package/dist/index.cjs CHANGED
@@ -9217,6 +9217,7 @@ function KanbanColumn({
9217
9217
  renderCard,
9218
9218
  emptyColumn,
9219
9219
  footer,
9220
+ headerExtra,
9220
9221
  cardClassName,
9221
9222
  showEdit,
9222
9223
  onEditRequest,
@@ -9277,7 +9278,8 @@ function KanbanColumn({
9277
9278
  treeKey,
9278
9279
  title: column.title,
9279
9280
  count: items.length,
9280
- droppable: sections != null
9281
+ droppable: sections != null,
9282
+ headerExtra
9281
9283
  }
9282
9284
  ),
9283
9285
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -9332,7 +9334,8 @@ function KanbanColumnHead({
9332
9334
  treeKey,
9333
9335
  title,
9334
9336
  count,
9335
- droppable
9337
+ droppable,
9338
+ headerExtra
9336
9339
  }) {
9337
9340
  const { setNodeRef, isOver } = core$1.useDroppable({
9338
9341
  id: tree_droppable_id("columnhead", treeKey, laneKey, columnKey),
@@ -9346,13 +9349,25 @@ function KanbanColumnHead({
9346
9349
  "data-column-key": columnKey,
9347
9350
  "data-lane-key": laneKey,
9348
9351
  className: cn(
9349
- "cls_hazo_kanban_column_head mb-2 flex items-center justify-between rounded-sm px-1 text-xs uppercase tracking-wider text-muted-foreground",
9352
+ "cls_hazo_kanban_column_head mb-2 flex items-center justify-between gap-2 rounded-sm px-1 text-xs uppercase tracking-wider text-muted-foreground",
9350
9353
  droppable && "py-1",
9351
9354
  isOver && "cls_hazo_kanban_column_head_over bg-accent/30 text-foreground ring-1 ring-ring/40"
9352
9355
  ),
9353
9356
  children: [
9354
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_column_title", children: title }),
9355
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_column_count rounded-full bg-muted px-2 py-0.5 text-foreground", children: count })
9357
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cls_hazo_kanban_column_head_title_group flex min-w-0 items-center gap-2", children: [
9358
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_column_title truncate", children: title }),
9359
+ headerExtra ? /* @__PURE__ */ jsxRuntime.jsx(
9360
+ "span",
9361
+ {
9362
+ className: "cls_hazo_kanban_column_head_extra shrink-0",
9363
+ onPointerDown: (e) => e.stopPropagation(),
9364
+ onMouseDown: (e) => e.stopPropagation(),
9365
+ onKeyDown: (e) => e.stopPropagation(),
9366
+ children: headerExtra
9367
+ }
9368
+ ) : null
9369
+ ] }),
9370
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_column_count shrink-0 rounded-full bg-muted px-2 py-0.5 text-foreground", children: count })
9356
9371
  ]
9357
9372
  }
9358
9373
  );
@@ -9933,6 +9948,7 @@ function HazoUiKanban({
9933
9948
  announcements,
9934
9949
  emptyColumn = "\u2014",
9935
9950
  renderColumnFooter,
9951
+ renderColumnHeaderExtra,
9936
9952
  className,
9937
9953
  cardClassName,
9938
9954
  itemLabel,
@@ -10186,7 +10202,7 @@ function HazoUiKanban({
10186
10202
  total += (grouped.get(bucket_key(lane_key, col.key)) ?? []).length;
10187
10203
  return total;
10188
10204
  }
10189
- function render_column(col, tree_key, lane_key, with_footer = true) {
10205
+ function render_column(col, tree_key, lane_key, with_consumer_callbacks = true) {
10190
10206
  return /* @__PURE__ */ jsxRuntime.jsx(
10191
10207
  KanbanColumn,
10192
10208
  {
@@ -10196,7 +10212,8 @@ function HazoUiKanban({
10196
10212
  items: grouped.get(bucket_key(lane_key, col.key)) ?? [],
10197
10213
  renderCard,
10198
10214
  emptyColumn,
10199
- footer: with_footer ? renderColumnFooter?.({ columnKey: col.key, laneKey: lane_key }) ?? null : null,
10215
+ footer: with_consumer_callbacks ? renderColumnFooter?.({ columnKey: col.key, laneKey: lane_key }) ?? null : null,
10216
+ headerExtra: with_consumer_callbacks ? renderColumnHeaderExtra?.({ columnKey: col.key, laneKey: lane_key }) ?? null : null,
10200
10217
  cardClassName,
10201
10218
  showEdit: show_edit,
10202
10219
  onEditRequest: handle_edit_request
@@ -12537,6 +12554,86 @@ function useScrollRestore(treeId, viewName, options) {
12537
12554
  const load = React27.useCallback(() => loadScrollPos(key), [key]);
12538
12555
  return { save, load };
12539
12556
  }
12557
+ var badgeVariants = classVarianceAuthority.cva(
12558
+ "inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium whitespace-nowrap select-none",
12559
+ {
12560
+ variants: {
12561
+ tone: {
12562
+ neutral: "bg-muted text-muted-foreground",
12563
+ primary: "bg-primary/10 text-primary",
12564
+ secondary: "bg-secondary text-secondary-foreground",
12565
+ accent: "bg-accent text-accent-foreground",
12566
+ success: "bg-success/15 text-success",
12567
+ warning: "bg-warning/15 text-warning",
12568
+ destructive: "bg-destructive/15 text-destructive",
12569
+ info: "bg-info/15 text-info"
12570
+ }
12571
+ },
12572
+ defaultVariants: {
12573
+ tone: "neutral"
12574
+ }
12575
+ }
12576
+ );
12577
+ var TONE_DOT_CLASS = {
12578
+ neutral: "bg-muted-foreground",
12579
+ primary: "bg-primary",
12580
+ secondary: "bg-secondary-foreground",
12581
+ accent: "bg-accent-foreground",
12582
+ success: "bg-success",
12583
+ warning: "bg-warning",
12584
+ destructive: "bg-destructive",
12585
+ info: "bg-info"
12586
+ };
12587
+ var Badge = React27__namespace.forwardRef(
12588
+ ({ className, tone, dot = false, pulse = false, children, ...props }, ref) => {
12589
+ const resolvedTone = tone ?? "neutral";
12590
+ return /* @__PURE__ */ jsxRuntime.jsxs(
12591
+ "span",
12592
+ {
12593
+ ref,
12594
+ className: cn(badgeVariants({ tone: resolvedTone }), className),
12595
+ ...props,
12596
+ children: [
12597
+ dot && /* @__PURE__ */ jsxRuntime.jsx(
12598
+ "span",
12599
+ {
12600
+ "aria-hidden": true,
12601
+ className: cn(
12602
+ "inline-block h-1.5 w-1.5 rounded-full",
12603
+ TONE_DOT_CLASS[resolvedTone],
12604
+ pulse && "animate-pulse"
12605
+ )
12606
+ }
12607
+ ),
12608
+ children
12609
+ ]
12610
+ }
12611
+ );
12612
+ }
12613
+ );
12614
+ Badge.displayName = "Badge";
12615
+ function StatusBadgeInner({ status, statusMap, fallback, className, ...rest }, ref) {
12616
+ const meta = statusMap[status] ?? fallback ?? { label: String(status), tone: "neutral" };
12617
+ return /* @__PURE__ */ jsxRuntime.jsx(
12618
+ Badge,
12619
+ {
12620
+ ref,
12621
+ tone: meta.tone,
12622
+ dot: meta.dot,
12623
+ pulse: meta.pulse,
12624
+ className,
12625
+ ...rest,
12626
+ children: meta.label
12627
+ }
12628
+ );
12629
+ }
12630
+ StatusBadgeInner.displayName = "StatusBadge";
12631
+ var StatusBadge = React27__namespace.forwardRef(StatusBadgeInner);
12632
+ function makeStatusBadge(statusMap, fallback) {
12633
+ const Component = React27__namespace.forwardRef((props, ref) => /* @__PURE__ */ jsxRuntime.jsx(StatusBadge, { ref, ...props, statusMap, fallback }));
12634
+ Component.displayName = "StatusBadge(bound)";
12635
+ return Component;
12636
+ }
12540
12637
 
12541
12638
  Object.defineProperty(exports, "rawToast", {
12542
12639
  enumerable: true,
@@ -12580,6 +12677,7 @@ exports.AlertDialogTitle = AlertDialogTitle;
12580
12677
  exports.AlertDialogTrigger = AlertDialogTrigger;
12581
12678
  exports.AppFooter = AppFooter;
12582
12679
  exports.AppHeader = AppHeader;
12680
+ exports.Badge = Badge;
12583
12681
  exports.Button = Button;
12584
12682
  exports.ButtonGroup = ButtonGroup;
12585
12683
  exports.ButtonGroupSeparator = ButtonGroupSeparator;
@@ -12712,6 +12810,7 @@ exports.Slider = Slider;
12712
12810
  exports.Sparkline = Sparkline;
12713
12811
  exports.Spinner = Spinner;
12714
12812
  exports.StackedBars = StackedBars;
12813
+ exports.StatusBadge = StatusBadge;
12715
12814
  exports.Switch = Switch;
12716
12815
  exports.Table = Table2;
12717
12816
  exports.TableBody = TableBody;
@@ -12748,6 +12847,7 @@ exports.get_hazo_ui_config = get_hazo_ui_config;
12748
12847
  exports.get_logger = get_logger;
12749
12848
  exports.hasUrlParam = hasUrlParam;
12750
12849
  exports.isLucideValue = isLucideValue;
12850
+ exports.makeStatusBadge = makeStatusBadge;
12751
12851
  exports.median = median;
12752
12852
  exports.parse_commands_from_text = parse_commands_from_text;
12753
12853
  exports.permissionToast = permissionToast;