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/dist/index.d.cts CHANGED
@@ -1902,6 +1902,17 @@ interface KanbanLane {
1902
1902
  title: string;
1903
1903
  /** Optional gate: returning false rejects a drop into this lane. */
1904
1904
  accepts?: (item: KanbanItem) => boolean;
1905
+ /**
1906
+ * Per-lane controls rendered in the lane heading, between the rule and the
1907
+ * count — an "add to this lane" button, an overflow menu, a lane filter.
1908
+ * Also rendered (right-aligned) on a `collapseWhenEmpty` lane's drop strip,
1909
+ * so a lane's actions do not disappear the moment it empties.
1910
+ *
1911
+ * The heading is a drop target but not a drag handle, so interactive nodes
1912
+ * here work without any `stopPropagation` dance — unlike controls rendered
1913
+ * inside a card by `renderCard`.
1914
+ */
1915
+ actions?: React$1.ReactNode;
1905
1916
  /**
1906
1917
  * When true and the lane currently holds no cards, it renders as a thin
1907
1918
  * full-width drop strip instead of a heading plus an empty column grid.
@@ -2031,6 +2042,22 @@ interface HazoUiKanbanProps<T extends KanbanItem = KanbanItem> {
2031
2042
  announcements?: Partial<KanbanAnnouncements>;
2032
2043
  /** Rendered when a column has zero items. Default: muted "—" placeholder. */
2033
2044
  emptyColumn?: React$1.ReactNode;
2045
+ /**
2046
+ * Rendered at the foot of every column body, below the cards (or below the
2047
+ * `emptyColumn` placeholder). Called once per rendered copy of a column, so
2048
+ * on a laned board it fires once per (lane, column) pair and `laneKey` says
2049
+ * which — that is what makes a per-lane "+ Add item" affordance possible
2050
+ * without the consumer having to reach into the board's internals.
2051
+ *
2052
+ * Return `null` for the columns that should not have one. The footer sits
2053
+ * inside the column's droppable but outside its `SortableContext`, so it
2054
+ * never participates in ordering and never becomes a drop target itself.
2055
+ */
2056
+ renderColumnFooter?: (ctx: {
2057
+ columnKey: string;
2058
+ /** `undefined` on a board without lanes. */
2059
+ laneKey?: string;
2060
+ }) => React$1.ReactNode;
2034
2061
  /** Class on the board root. */
2035
2062
  className?: string;
2036
2063
  /** Class on each card shell (the wrapper around renderCard output). */
@@ -2214,7 +2241,7 @@ declare function applyKanbanFilter<T extends KanbanItem>(items: T[], filter: Kan
2214
2241
  * event.revert() and the overlay entry is dropped.
2215
2242
  */
2216
2243
 
2217
- declare function HazoUiKanban<T extends KanbanItem = KanbanItem>({ columns, lanes, items, renderCard, onMove, onReorder, mobileBreakpoint, announcements, emptyColumn, className, cardClassName, itemLabel, editorFields, editorPriorities, editorTitle, renderCardEditor, hideEditorFooter, onCardSave, disableEdit, }: HazoUiKanbanProps<T>): React$1.JSX.Element;
2244
+ declare function HazoUiKanban<T extends KanbanItem = KanbanItem>({ columns, lanes, items, renderCard, onMove, onReorder, mobileBreakpoint, announcements, emptyColumn, renderColumnFooter, className, cardClassName, itemLabel, editorFields, editorPriorities, editorTitle, renderCardEditor, hideEditorFooter, onCardSave, disableEdit, }: HazoUiKanbanProps<T>): React$1.JSX.Element;
2218
2245
 
2219
2246
  declare const Table: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes<HTMLTableElement> & React$1.RefAttributes<HTMLTableElement>>;
2220
2247
  declare const TableHeader: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes<HTMLTableSectionElement> & React$1.RefAttributes<HTMLTableSectionElement>>;
package/dist/index.d.ts CHANGED
@@ -1902,6 +1902,17 @@ interface KanbanLane {
1902
1902
  title: string;
1903
1903
  /** Optional gate: returning false rejects a drop into this lane. */
1904
1904
  accepts?: (item: KanbanItem) => boolean;
1905
+ /**
1906
+ * Per-lane controls rendered in the lane heading, between the rule and the
1907
+ * count — an "add to this lane" button, an overflow menu, a lane filter.
1908
+ * Also rendered (right-aligned) on a `collapseWhenEmpty` lane's drop strip,
1909
+ * so a lane's actions do not disappear the moment it empties.
1910
+ *
1911
+ * The heading is a drop target but not a drag handle, so interactive nodes
1912
+ * here work without any `stopPropagation` dance — unlike controls rendered
1913
+ * inside a card by `renderCard`.
1914
+ */
1915
+ actions?: React$1.ReactNode;
1905
1916
  /**
1906
1917
  * When true and the lane currently holds no cards, it renders as a thin
1907
1918
  * full-width drop strip instead of a heading plus an empty column grid.
@@ -2031,6 +2042,22 @@ interface HazoUiKanbanProps<T extends KanbanItem = KanbanItem> {
2031
2042
  announcements?: Partial<KanbanAnnouncements>;
2032
2043
  /** Rendered when a column has zero items. Default: muted "—" placeholder. */
2033
2044
  emptyColumn?: React$1.ReactNode;
2045
+ /**
2046
+ * Rendered at the foot of every column body, below the cards (or below the
2047
+ * `emptyColumn` placeholder). Called once per rendered copy of a column, so
2048
+ * on a laned board it fires once per (lane, column) pair and `laneKey` says
2049
+ * which — that is what makes a per-lane "+ Add item" affordance possible
2050
+ * without the consumer having to reach into the board's internals.
2051
+ *
2052
+ * Return `null` for the columns that should not have one. The footer sits
2053
+ * inside the column's droppable but outside its `SortableContext`, so it
2054
+ * never participates in ordering and never becomes a drop target itself.
2055
+ */
2056
+ renderColumnFooter?: (ctx: {
2057
+ columnKey: string;
2058
+ /** `undefined` on a board without lanes. */
2059
+ laneKey?: string;
2060
+ }) => React$1.ReactNode;
2034
2061
  /** Class on the board root. */
2035
2062
  className?: string;
2036
2063
  /** Class on each card shell (the wrapper around renderCard output). */
@@ -2214,7 +2241,7 @@ declare function applyKanbanFilter<T extends KanbanItem>(items: T[], filter: Kan
2214
2241
  * event.revert() and the overlay entry is dropped.
2215
2242
  */
2216
2243
 
2217
- declare function HazoUiKanban<T extends KanbanItem = KanbanItem>({ columns, lanes, items, renderCard, onMove, onReorder, mobileBreakpoint, announcements, emptyColumn, className, cardClassName, itemLabel, editorFields, editorPriorities, editorTitle, renderCardEditor, hideEditorFooter, onCardSave, disableEdit, }: HazoUiKanbanProps<T>): React$1.JSX.Element;
2244
+ declare function HazoUiKanban<T extends KanbanItem = KanbanItem>({ columns, lanes, items, renderCard, onMove, onReorder, mobileBreakpoint, announcements, emptyColumn, renderColumnFooter, className, cardClassName, itemLabel, editorFields, editorPriorities, editorTitle, renderCardEditor, hideEditorFooter, onCardSave, disableEdit, }: HazoUiKanbanProps<T>): React$1.JSX.Element;
2218
2245
 
2219
2246
  declare const Table: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes<HTMLTableElement> & React$1.RefAttributes<HTMLTableElement>>;
2220
2247
  declare const TableHeader: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes<HTMLTableSectionElement> & React$1.RefAttributes<HTMLTableSectionElement>>;
package/dist/index.js CHANGED
@@ -9038,6 +9038,12 @@ function KanbanCard({
9038
9038
  backgroundColor: "hsl(var(--hazo-kanban-card-bg))",
9039
9039
  border: `1px solid hsl(var(--hazo-kanban-card-border))`,
9040
9040
  borderLeft: priority_var ? `4px solid hsl(var(${priority_var}))` : `1px solid hsl(var(--hazo-kanban-card-border))`,
9041
+ // Opt-in lift. A card whose background equals the column's (the case for
9042
+ // every palette where --card and --background resolve to the same value)
9043
+ // is separated by nothing but a 1px border, which reads as flat at a
9044
+ // glance. The fallback is `none`, so a consumer that never sets the token
9045
+ // renders exactly as before.
9046
+ boxShadow: "var(--hazo-kanban-card-shadow, none)",
9041
9047
  opacity: isDragging && !dragging ? 0.4 : 1,
9042
9048
  cursor: "grab"
9043
9049
  };
@@ -9046,6 +9052,9 @@ function KanbanCard({
9046
9052
  {
9047
9053
  ref: setNodeRef,
9048
9054
  style,
9055
+ "data-column-key": item.columnKey,
9056
+ "data-lane-key": item.laneKey,
9057
+ "data-section-key": item.sectionKey,
9049
9058
  className: cn(
9050
9059
  "cls_hazo_kanban_card group relative rounded-md p-3 mb-2 select-none",
9051
9060
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
@@ -9117,6 +9126,7 @@ function KanbanColumn({
9117
9126
  items,
9118
9127
  renderCard,
9119
9128
  emptyColumn,
9129
+ footer,
9120
9130
  cardClassName,
9121
9131
  showEdit,
9122
9132
  onEditRequest,
@@ -9162,6 +9172,8 @@ function KanbanColumn({
9162
9172
  "div",
9163
9173
  {
9164
9174
  ref: setNodeRef,
9175
+ "data-column-key": column.key,
9176
+ "data-lane-key": laneKey,
9165
9177
  className: cn(
9166
9178
  "cls_hazo_kanban_column flex flex-col rounded-md p-2",
9167
9179
  isOver && "cls_hazo_kanban_column_over bg-accent/30 ring-2 ring-ring/40"
@@ -9183,34 +9195,43 @@ function KanbanColumn({
9183
9195
  {
9184
9196
  items: ordered.map((i) => i.id),
9185
9197
  strategy: verticalListSortingStrategy,
9186
- children: /* @__PURE__ */ jsx("div", { className: "cls_hazo_kanban_column_body flex-1 min-h-[40px]", children: !sections ? items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "cls_hazo_kanban_column_empty text-xs text-muted-foreground py-2 px-1", children: emptyColumn }) : render_cards(items) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
9187
- expanded_sections.map((section) => /* @__PURE__ */ jsx(
9188
- KanbanSectionBand,
9189
- {
9190
- columnKey: column.key,
9191
- laneKey,
9192
- treeKey,
9193
- section,
9194
- items: bySection.get(section.key) ?? [],
9195
- emptyColumn,
9196
- renderCards: render_cards
9197
- },
9198
- section.key
9199
- )),
9200
- unmatched.length > 0 && /* @__PURE__ */ jsx("div", { className: "cls_hazo_kanban_section cls_hazo_kanban_section_unmatched", children: render_cards(unmatched) }),
9201
- collapsed_sections.length > 0 && /* @__PURE__ */ 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__ */ jsx(
9202
- KanbanSectionStrip,
9203
- {
9204
- columnKey: column.key,
9205
- laneKey,
9206
- treeKey,
9207
- section
9208
- },
9209
- section.key
9210
- )) })
9211
- ] }) })
9198
+ children: /* @__PURE__ */ jsx(
9199
+ "div",
9200
+ {
9201
+ "data-column-key": column.key,
9202
+ "data-lane-key": laneKey,
9203
+ className: "cls_hazo_kanban_column_body flex-1 min-h-[40px]",
9204
+ children: !sections ? items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "cls_hazo_kanban_column_empty text-xs text-muted-foreground py-2 px-1", children: emptyColumn }) : render_cards(items) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
9205
+ expanded_sections.map((section) => /* @__PURE__ */ jsx(
9206
+ KanbanSectionBand,
9207
+ {
9208
+ columnKey: column.key,
9209
+ laneKey,
9210
+ treeKey,
9211
+ section,
9212
+ items: bySection.get(section.key) ?? [],
9213
+ emptyColumn,
9214
+ renderCards: render_cards
9215
+ },
9216
+ section.key
9217
+ )),
9218
+ unmatched.length > 0 && /* @__PURE__ */ jsx("div", { className: "cls_hazo_kanban_section cls_hazo_kanban_section_unmatched", children: render_cards(unmatched) }),
9219
+ collapsed_sections.length > 0 && /* @__PURE__ */ 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__ */ jsx(
9220
+ KanbanSectionStrip,
9221
+ {
9222
+ columnKey: column.key,
9223
+ laneKey,
9224
+ treeKey,
9225
+ section
9226
+ },
9227
+ section.key
9228
+ )) })
9229
+ ] })
9230
+ }
9231
+ )
9212
9232
  }
9213
- )
9233
+ ),
9234
+ footer ? /* @__PURE__ */ jsx("div", { className: "cls_hazo_kanban_column_footer mt-1", children: footer }) : null
9214
9235
  ]
9215
9236
  }
9216
9237
  );
@@ -9232,6 +9253,8 @@ function KanbanColumnHead({
9232
9253
  "div",
9233
9254
  {
9234
9255
  ref: droppable ? setNodeRef : void 0,
9256
+ "data-column-key": columnKey,
9257
+ "data-lane-key": laneKey,
9235
9258
  className: cn(
9236
9259
  "cls_hazo_kanban_column_head mb-2 flex items-center justify-between rounded-sm px-1 text-xs uppercase tracking-wider text-muted-foreground",
9237
9260
  droppable && "py-1",
@@ -9321,6 +9344,7 @@ function KanbanLaneHead({
9321
9344
  children: [
9322
9345
  /* @__PURE__ */ jsx("span", { className: "cls_hazo_kanban_lane_title text-xs font-semibold uppercase tracking-wider text-foreground", children: lane.title }),
9323
9346
  /* @__PURE__ */ jsx("span", { className: "cls_hazo_kanban_lane_rule h-px flex-1 bg-border" }),
9347
+ lane.actions ? /* @__PURE__ */ jsx("span", { className: "cls_hazo_kanban_lane_actions flex items-center gap-1", children: lane.actions }) : null,
9324
9348
  /* @__PURE__ */ 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 })
9325
9349
  ]
9326
9350
  }
@@ -9334,15 +9358,18 @@ function KanbanLaneStrip({
9334
9358
  id: lane_droppable_id(lane.key, treeKey),
9335
9359
  data: { laneKey: lane.key, isLane: true }
9336
9360
  });
9337
- return /* @__PURE__ */ jsx(
9361
+ return /* @__PURE__ */ jsxs(
9338
9362
  "div",
9339
9363
  {
9340
9364
  ref: setNodeRef,
9341
9365
  className: cn(
9342
- "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",
9366
+ "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",
9343
9367
  isOver && "cls_hazo_kanban_lane_strip_over border-solid bg-accent/30 text-foreground ring-1 ring-ring/30"
9344
9368
  ),
9345
- children: lane.title
9369
+ children: [
9370
+ /* @__PURE__ */ jsx("span", { className: "cls_hazo_kanban_lane_title", children: lane.title }),
9371
+ lane.actions ? /* @__PURE__ */ jsx("span", { className: "cls_hazo_kanban_lane_actions ml-auto flex items-center gap-1", children: lane.actions }) : null
9372
+ ]
9346
9373
  }
9347
9374
  );
9348
9375
  }
@@ -9815,6 +9842,7 @@ function HazoUiKanban({
9815
9842
  mobileBreakpoint = 640,
9816
9843
  announcements,
9817
9844
  emptyColumn = "\u2014",
9845
+ renderColumnFooter,
9818
9846
  className,
9819
9847
  cardClassName,
9820
9848
  itemLabel,
@@ -10068,7 +10096,7 @@ function HazoUiKanban({
10068
10096
  total += (grouped.get(bucket_key(lane_key, col.key)) ?? []).length;
10069
10097
  return total;
10070
10098
  }
10071
- function render_column(col, tree_key, lane_key) {
10099
+ function render_column(col, tree_key, lane_key, with_footer = true) {
10072
10100
  return /* @__PURE__ */ jsx(
10073
10101
  KanbanColumn,
10074
10102
  {
@@ -10078,6 +10106,7 @@ function HazoUiKanban({
10078
10106
  items: grouped.get(bucket_key(lane_key, col.key)) ?? [],
10079
10107
  renderCard,
10080
10108
  emptyColumn,
10109
+ footer: with_footer ? renderColumnFooter?.({ columnKey: col.key, laneKey: lane_key }) ?? null : null,
10081
10110
  cardClassName,
10082
10111
  showEdit: show_edit,
10083
10112
  onEditRequest: handle_edit_request
@@ -10098,8 +10127,8 @@ function HazoUiKanban({
10098
10127
  function render_unmatched_lane(tree_key, only_column) {
10099
10128
  const count = only_column ? (grouped.get(bucket_key(UNMATCHED_LANE_KEY, only_column.key)) ?? []).length : lane_total(UNMATCHED_LANE_KEY);
10100
10129
  if (count === 0) return null;
10101
- return /* @__PURE__ */ 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__ */ jsx("div", { className: "cls_hazo_kanban_lane_grid grid gap-3", style: grid_style, children: columns.map(
10102
- (col) => render_column(col, tree_key, UNMATCHED_LANE_KEY)
10130
+ return /* @__PURE__ */ 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__ */ jsx("div", { className: "cls_hazo_kanban_lane_grid grid gap-3", style: grid_style, children: columns.map(
10131
+ (col) => render_column(col, tree_key, UNMATCHED_LANE_KEY, false)
10103
10132
  ) }) });
10104
10133
  }
10105
10134
  return /* @__PURE__ */ jsxs(