@thebuoyant-tsdev/mui-ts-library 3.33.1 → 3.34.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/README.de.md CHANGED
@@ -555,6 +555,15 @@ Dieses Projekt folgt [Semantic Versioning](https://semver.org/):
555
555
 
556
556
  ## Changelog
557
557
 
558
+ ### [3.34.0] — 2026-07-18
559
+
560
+ **Hinzugefügt**
561
+ - **KanbanBoard — `showSearchField`**: Neues Boolean-Prop, das ein eingebautes `size="small"`-Suchfeld mit Lupen-Icon über dem Board rendert. Das Board verwaltet den Filterzustand intern — kein zusätzliches Wiring nötig. Placeholder-Text über `translation.searchFieldPlaceholder` anpassbar. Das bestehende `filterText`-Prop funktioniert weiterhin für Consumer, die ein eigenes Suchfeld bauen.
562
+ - **KanbanBoard — `translation.searchFieldPlaceholder`**: Neuer Translation-Key (Standard `"Search by title or assignee…"`).
563
+ - **KanbanBoard — CSS-Klassen**: `MuiTsKanbanBoard-searchFieldWrapper` und `MuiTsKanbanBoard-searchField`. Siehe [Vollständiger Changelog](https://github.com/thebuoyant/mui-ts-library/blob/main/CHANGELOG.de.md) für Details.
564
+
565
+ ---
566
+
558
567
  ### [3.33.1] — 2026-07-18
559
568
 
560
569
  **Geändert**
package/README.md CHANGED
@@ -555,6 +555,15 @@ This project follows [Semantic Versioning](https://semver.org/):
555
555
 
556
556
  ## Changelog
557
557
 
558
+ ### [3.34.0] — 2026-07-18
559
+
560
+ **Added**
561
+ - **KanbanBoard — `showSearchField`**: New boolean prop that renders a built-in `size="small"` search field with a magnifier icon above the board. The board manages the filter state internally — no extra wiring needed. Placeholder text customizable via `translation.searchFieldPlaceholder`. The existing `filterText` prop still works for consumers who build their own search UI.
562
+ - **KanbanBoard — `translation.searchFieldPlaceholder`**: New translation key (default `"Search by title or assignee…"`).
563
+ - **KanbanBoard — CSS classes**: `MuiTsKanbanBoard-searchFieldWrapper` and `MuiTsKanbanBoard-searchField`. See [Full Changelog](https://github.com/thebuoyant/mui-ts-library/blob/main/CHANGELOG.md) for details.
564
+
565
+ ---
566
+
558
567
  ### [3.33.1] — 2026-07-18
559
568
 
560
569
  **Changed**
@@ -1,2 +1,2 @@
1
1
  import { type KanbanBoardProps } from "./KanbanBoard.types";
2
- export declare function KanbanBoard({ tasks, columns, onTasksChange, onCardClick, enableBuiltinDialogs, onTaskCreated, onTaskUpdated, onTaskDeleted, onTaskMoved, showPriority, showAssignee, showDueDate, showDueDateWarning, chipVariant, filterText, width, height, translation, }: KanbanBoardProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function KanbanBoard({ tasks, columns, onTasksChange, onCardClick, enableBuiltinDialogs, onTaskCreated, onTaskUpdated, onTaskDeleted, onTaskMoved, showPriority, showAssignee, showDueDate, showDueDateWarning, chipVariant, showSearchField, filterText, width, height, translation, }: KanbanBoardProps): import("react/jsx-runtime").JSX.Element;
@@ -5,138 +5,168 @@ import { KanbanBoardCardDialog as r } from "./KanbanBoardCardDialog.js";
5
5
  import { KanbanBoardColumn as i } from "./KanbanBoardColumn.js";
6
6
  import { DndContext as a, DragOverlay as o, KeyboardSensor as s, PointerSensor as c, closestCorners as l, useSensor as u, useSensors as d } from "@dnd-kit/core";
7
7
  import { arrayMove as f, sortableKeyboardCoordinates as p } from "@dnd-kit/sortable";
8
- import { Box as m } from "@mui/material";
9
- import { useEffect as h, useState as g } from "react";
10
- import { jsx as _, jsxs as v } from "react/jsx-runtime";
8
+ import m from "@mui/icons-material/Search";
9
+ import { Box as h, InputAdornment as g, TextField as ee } from "@mui/material";
10
+ import { useEffect as _, useState as v } from "react";
11
+ import { jsx as y, jsxs as b } from "react/jsx-runtime";
11
12
  //#region src/components/kanban-board/KanbanBoard.tsx
12
- function y({ tasks: y, columns: b, onTasksChange: x, onCardClick: S, enableBuiltinDialogs: C = !0, onTaskCreated: w, onTaskUpdated: T, onTaskDeleted: E, onTaskMoved: D, showPriority: O = !0, showAssignee: k = !0, showDueDate: A = !0, showDueDateWarning: j = !0, chipVariant: M = "outlined", filterText: N = "", width: P = "100%", height: F = "100%", translation: I }) {
13
- let L = {
13
+ function x({ tasks: x, columns: S, onTasksChange: C, onCardClick: w, enableBuiltinDialogs: T = !0, onTaskCreated: E, onTaskUpdated: D, onTaskDeleted: O, onTaskMoved: k, showPriority: A = !0, showAssignee: j = !0, showDueDate: M = !0, showDueDateWarning: N = !0, chipVariant: P = "outlined", showSearchField: F = !1, filterText: I = "", width: L = "100%", height: R = "100%", translation: z }) {
14
+ let B = {
14
15
  ...t,
15
- ...I
16
- }, [R, z] = g(y);
17
- h(() => {
18
- z(y);
19
- }, [y]);
20
- let [B, V] = g(null), [H, U] = g(null), W = d(u(c, { activationConstraint: { distance: 8 } }), u(s, { coordinateGetter: p }));
21
- function G({ active: e }) {
22
- V(R.find((t) => t.id === e.id) ?? null);
16
+ ...z
17
+ }, [V, H] = v(x);
18
+ _(() => {
19
+ H(x);
20
+ }, [x]);
21
+ let [U, W] = v(null), [G, K] = v(null), [q, J] = v(""), Y = d(u(c, { activationConstraint: { distance: 8 } }), u(s, { coordinateGetter: p }));
22
+ function X({ active: e }) {
23
+ W(V.find((t) => t.id === e.id) ?? null);
23
24
  }
24
- function K({ active: e, over: t }) {
25
+ function Z({ active: e, over: t }) {
25
26
  if (!t) return;
26
- let n = e.id, r = t.id, i = R.find((e) => e.id === n);
27
+ let n = e.id, r = t.id, i = V.find((e) => e.id === n);
27
28
  if (!i) return;
28
- let a = b.find((e) => e.id === r), o = R.find((e) => e.id === r), s = a?.id ?? o?.status;
29
- !s || i.status === s || z((e) => e.map((e) => e.id === n ? {
29
+ let a = S.find((e) => e.id === r), o = V.find((e) => e.id === r), s = a?.id ?? o?.status;
30
+ !s || i.status === s || H((e) => e.map((e) => e.id === n ? {
30
31
  ...e,
31
32
  status: s
32
33
  } : e));
33
34
  }
34
- function q({ active: e, over: t }) {
35
- let n = B?.status;
36
- if (V(null), !t) {
37
- z(y);
35
+ function Q({ active: e, over: t }) {
36
+ let n = U?.status;
37
+ if (W(null), !t) {
38
+ H(x);
38
39
  return;
39
40
  }
40
- let r = e.id, i = t.id, a = R.findIndex((e) => e.id === r), o = R.findIndex((e) => e.id === i), s;
41
- if (s = a !== -1 && o !== -1 && a !== o ? f(R, a, o) : [...R], z(s), x?.(s), n) {
41
+ let r = e.id, i = t.id, a = V.findIndex((e) => e.id === r), o = V.findIndex((e) => e.id === i), s;
42
+ if (s = a !== -1 && o !== -1 && a !== o ? f(V, a, o) : [...V], H(s), C?.(s), n) {
42
43
  let e = s.find((e) => e.id === r);
43
- e && e.status !== n && D?.(e, n, e.status);
44
+ e && e.status !== n && k?.(e, n, e.status);
44
45
  }
45
46
  }
46
- function J(e) {
47
- C ? U({
47
+ function te(e) {
48
+ T ? K({
48
49
  mode: "edit",
49
50
  task: e
50
- }) : S?.(e);
51
+ }) : w?.(e);
51
52
  }
52
- function Y(e) {
53
- U({
53
+ function ne(e) {
54
+ K({
54
55
  mode: "add",
55
56
  columnId: e
56
57
  });
57
58
  }
58
- function X(e) {
59
- let t = !R.find((t) => t.id === e.id), n = t ? [...R, e] : R.map((t) => t.id === e.id ? e : t);
60
- z(n), x?.(n), t ? w?.(e) : T?.(e), U(null);
59
+ function re(e) {
60
+ let t = !V.find((t) => t.id === e.id), n = t ? [...V, e] : V.map((t) => t.id === e.id ? e : t);
61
+ H(n), C?.(n), t ? E?.(e) : D?.(e), K(null);
61
62
  }
62
- function Z(e) {
63
- let t = R.filter((t) => t.id !== e);
64
- z(t), x?.(t), E?.(e), U(null);
63
+ function ie(e) {
64
+ let t = V.filter((t) => t.id !== e);
65
+ H(t), C?.(t), O?.(e), K(null);
65
66
  }
66
- function Q(e) {
67
- U({
67
+ function ae(e) {
68
+ K({
68
69
  mode: "delete",
69
70
  task: e
70
71
  });
71
72
  }
72
- let $ = N.trim().toLowerCase();
73
- function ee(e) {
73
+ let $ = (I || q).trim().toLowerCase();
74
+ function oe(e) {
74
75
  return $ ? e.title.toLowerCase().includes($) || (e.assignee?.toLowerCase().includes($) ?? !1) : !0;
75
76
  }
76
- return /* @__PURE__ */ v(m, {
77
+ return /* @__PURE__ */ b(h, {
77
78
  className: e.root,
78
79
  sx: {
79
- width: P,
80
- height: F,
80
+ width: L,
81
+ height: R,
81
82
  display: "flex",
82
83
  flexDirection: "column",
83
84
  overflow: "hidden"
84
85
  },
85
- children: [/* @__PURE__ */ v(a, {
86
- sensors: W,
87
- collisionDetection: l,
88
- onDragStart: G,
89
- onDragOver: K,
90
- onDragEnd: q,
91
- children: [/* @__PURE__ */ _(m, {
92
- className: e.columns,
86
+ children: [
87
+ F && /* @__PURE__ */ y(h, {
88
+ className: e.searchFieldWrapper,
93
89
  sx: {
94
- flex: 1,
95
- display: "flex",
96
- gap: 2,
97
- p: 2,
98
- overflowX: "auto",
99
- overflowY: "hidden",
100
- alignItems: "stretch"
90
+ px: 2,
91
+ pt: 1.5,
92
+ pb: .5
101
93
  },
102
- children: b.map((e) => {
103
- let t = R.filter((t) => t.status === e.id);
104
- return /* @__PURE__ */ _(i, {
105
- column: e,
106
- tasks: t.filter(ee),
107
- totalCount: t.length,
108
- showPriority: O,
109
- showAssignee: k,
110
- showDueDate: A,
111
- showDueDateWarning: j,
112
- chipVariant: M,
113
- t: L,
114
- enableBuiltinDialogs: C,
115
- onCardClick: J,
116
- onAddClick: Y
117
- }, e.id);
94
+ children: /* @__PURE__ */ y(ee, {
95
+ className: e.searchField,
96
+ size: "small",
97
+ placeholder: B.searchFieldPlaceholder,
98
+ value: q,
99
+ onChange: (e) => J(e.target.value),
100
+ slotProps: {
101
+ input: { startAdornment: /* @__PURE__ */ y(g, {
102
+ position: "start",
103
+ children: /* @__PURE__ */ y(m, {
104
+ fontSize: "small",
105
+ sx: { color: "text.disabled" }
106
+ })
107
+ }) },
108
+ htmlInput: { "aria-label": B.searchFieldPlaceholder }
109
+ },
110
+ sx: { width: 280 }
118
111
  })
119
- }), /* @__PURE__ */ _(o, { children: B && /* @__PURE__ */ _(n, {
120
- task: B,
121
- showPriority: O,
122
- showAssignee: k,
123
- showDueDate: A,
124
- showDueDateWarning: j,
125
- chipVariant: M,
126
- t: L,
127
- onCardClick: () => {},
128
- isOverlay: !0
129
- }) })]
130
- }), C && /* @__PURE__ */ _(r, {
131
- state: H,
132
- columns: b,
133
- t: L,
134
- onSave: X,
135
- onDelete: Z,
136
- onRequestDelete: Q,
137
- onClose: () => U(null)
138
- }, H ? `${H.mode}-${H.mode === "add" ? H.columnId : H.task.id}` : "closed")]
112
+ }),
113
+ /* @__PURE__ */ b(a, {
114
+ sensors: Y,
115
+ collisionDetection: l,
116
+ onDragStart: X,
117
+ onDragOver: Z,
118
+ onDragEnd: Q,
119
+ children: [/* @__PURE__ */ y(h, {
120
+ className: e.columns,
121
+ sx: {
122
+ flex: 1,
123
+ display: "flex",
124
+ gap: 2,
125
+ p: 2,
126
+ overflowX: "auto",
127
+ overflowY: "hidden",
128
+ alignItems: "stretch"
129
+ },
130
+ children: S.map((e) => {
131
+ let t = V.filter((t) => t.status === e.id);
132
+ return /* @__PURE__ */ y(i, {
133
+ column: e,
134
+ tasks: t.filter(oe),
135
+ totalCount: t.length,
136
+ showPriority: A,
137
+ showAssignee: j,
138
+ showDueDate: M,
139
+ showDueDateWarning: N,
140
+ chipVariant: P,
141
+ t: B,
142
+ enableBuiltinDialogs: T,
143
+ onCardClick: te,
144
+ onAddClick: ne
145
+ }, e.id);
146
+ })
147
+ }), /* @__PURE__ */ y(o, { children: U && /* @__PURE__ */ y(n, {
148
+ task: U,
149
+ showPriority: A,
150
+ showAssignee: j,
151
+ showDueDate: M,
152
+ showDueDateWarning: N,
153
+ chipVariant: P,
154
+ t: B,
155
+ onCardClick: () => {},
156
+ isOverlay: !0
157
+ }) })]
158
+ }),
159
+ T && /* @__PURE__ */ y(r, {
160
+ state: G,
161
+ columns: S,
162
+ t: B,
163
+ onSave: re,
164
+ onDelete: ie,
165
+ onRequestDelete: ae,
166
+ onClose: () => K(null)
167
+ }, G ? `${G.mode}-${G.mode === "add" ? G.columnId : G.task.id}` : "closed")
168
+ ]
139
169
  });
140
170
  }
141
171
  //#endregion
142
- export { y as KanbanBoard };
172
+ export { x as KanbanBoard };
@@ -37,6 +37,8 @@ export type KanbanBoardTranslation = {
37
37
  dialogFieldDueDate: string;
38
38
  dialogFieldStatus: string;
39
39
  noCardsLabel: string;
40
+ /** Placeholder text for the built-in search field (`showSearchField={true}`). */
41
+ searchFieldPlaceholder: string;
40
42
  };
41
43
  export declare const DEFAULT_KANBAN_BOARD_TRANSLATION: Required<KanbanBoardTranslation>;
42
44
  export type KanbanBoardProps = {
@@ -92,12 +94,23 @@ export type KanbanBoardProps = {
92
94
  * `"filled"` — solid background, more prominent.
93
95
  */
94
96
  chipVariant?: "outlined" | "filled";
97
+ /**
98
+ * When `true`, renders a built-in `size="small"` search field above the board columns.
99
+ * The board manages the search state internally — no extra wiring needed.
100
+ * Customize the placeholder via `translation.searchFieldPlaceholder`.
101
+ *
102
+ * For full control over placement, styling, or debouncing, leave this `false` (default)
103
+ * and pass `filterText` instead.
104
+ */
105
+ showSearchField?: boolean;
95
106
  /**
96
107
  * Filters visible cards by title and assignee (case-insensitive substring match).
97
108
  * The consumer is responsible for rendering the search input and passing the string.
98
109
  * An empty string or `undefined` shows all cards.
99
110
  * Column counters reflect the filtered count; WIP-limit checks always use the
100
111
  * unfiltered column total so the over-limit warning is never hidden by a filter.
112
+ *
113
+ * Alternative: set `showSearchField={true}` to let the board render a built-in field.
101
114
  */
102
115
  filterText?: string;
103
116
  /** Width of the board. Default: "100%". */
@@ -13,7 +13,8 @@ var e = {
13
13
  dialogFieldAssignee: "Assignee",
14
14
  dialogFieldDueDate: "Due date",
15
15
  dialogFieldStatus: "Status",
16
- noCardsLabel: "No cards"
16
+ noCardsLabel: "No cards",
17
+ searchFieldPlaceholder: "Search by title or assignee…"
17
18
  };
18
19
  //#endregion
19
20
  export { e as DEFAULT_KANBAN_BOARD_TRANSLATION };
@@ -46,4 +46,8 @@ export declare const kanbanBoardClasses: {
46
46
  readonly cardPriorityDot: "MuiTsKanbanBoard-cardPriorityDot";
47
47
  /** The "+ Add card" button at the bottom of each column. */
48
48
  readonly addButton: "MuiTsKanbanBoard-addButton";
49
+ /** The wrapper Box around the built-in search field (`showSearchField={true}`). */
50
+ readonly searchFieldWrapper: "MuiTsKanbanBoard-searchFieldWrapper";
51
+ /** The built-in search TextField (`showSearchField={true}`). */
52
+ readonly searchField: "MuiTsKanbanBoard-searchField";
49
53
  };
@@ -13,7 +13,9 @@ var e = {
13
13
  cardAssignee: "MuiTsKanbanBoard-cardAssignee",
14
14
  cardDueDate: "MuiTsKanbanBoard-cardDueDate",
15
15
  cardPriorityDot: "MuiTsKanbanBoard-cardPriorityDot",
16
- addButton: "MuiTsKanbanBoard-addButton"
16
+ addButton: "MuiTsKanbanBoard-addButton",
17
+ searchFieldWrapper: "MuiTsKanbanBoard-searchFieldWrapper",
18
+ searchField: "MuiTsKanbanBoard-searchField"
17
19
  };
18
20
  //#endregion
19
21
  export { e as kanbanBoardClasses };
@@ -1,11 +1,11 @@
1
1
  import { EMOJI_CATEGORIES as e } from "./util/emojis.js";
2
- import { Box as t, InputAdornment as n, Popover as r, TextField as i, Typography as a } from "@mui/material";
3
- import { useMemo as o, useState as s } from "react";
4
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
5
- import u from "@mui/icons-material/Search";
2
+ import t from "@mui/icons-material/Search";
3
+ import { Box as n, InputAdornment as r, Popover as i, TextField as a, Typography as o } from "@mui/material";
4
+ import { useMemo as s, useState as c } from "react";
5
+ import { jsx as l, jsxs as u } from "react/jsx-runtime";
6
6
  //#region src/components/rich-text-editor/RichTextEditorEmojiPicker.tsx
7
7
  function d({ anchorEl: d, open: f, onClose: p, onSelect: m, translation: h }) {
8
- let [g, _] = s(""), v = o(() => {
8
+ let [g, _] = c(""), v = s(() => {
9
9
  let t = g.trim().toLowerCase();
10
10
  return t ? e.map((e) => ({
11
11
  ...e,
@@ -18,7 +18,7 @@ function d({ anchorEl: d, open: f, onClose: p, onSelect: m, translation: h }) {
18
18
  function b() {
19
19
  p(), _("");
20
20
  }
21
- return /* @__PURE__ */ l(r, {
21
+ return /* @__PURE__ */ u(i, {
22
22
  open: f,
23
23
  anchorEl: d,
24
24
  onClose: b,
@@ -36,32 +36,32 @@ function d({ anchorEl: d, open: f, onClose: p, onSelect: m, translation: h }) {
36
36
  display: "flex",
37
37
  flexDirection: "column"
38
38
  } } },
39
- children: [/* @__PURE__ */ c(t, {
39
+ children: [/* @__PURE__ */ l(n, {
40
40
  sx: {
41
41
  p: 1,
42
42
  borderBottom: 1,
43
43
  borderColor: "divider"
44
44
  },
45
- children: /* @__PURE__ */ c(i, {
45
+ children: /* @__PURE__ */ l(a, {
46
46
  autoFocus: !0,
47
47
  fullWidth: !0,
48
48
  size: "small",
49
49
  placeholder: h.emojiSearchPlaceholder,
50
50
  value: g,
51
51
  onChange: (e) => _(e.target.value),
52
- slotProps: { input: { startAdornment: /* @__PURE__ */ c(n, {
52
+ slotProps: { input: { startAdornment: /* @__PURE__ */ l(r, {
53
53
  position: "start",
54
- children: /* @__PURE__ */ c(u, { fontSize: "small" })
54
+ children: /* @__PURE__ */ l(t, { fontSize: "small" })
55
55
  }) } }
56
56
  })
57
- }), /* @__PURE__ */ l(t, {
57
+ }), /* @__PURE__ */ u(n, {
58
58
  sx: {
59
59
  overflowY: "auto",
60
60
  flex: 1,
61
61
  px: 1,
62
62
  pb: 1
63
63
  },
64
- children: [v.map((e) => /* @__PURE__ */ l(t, { children: [/* @__PURE__ */ c(a, {
64
+ children: [v.map((e) => /* @__PURE__ */ u(n, { children: [/* @__PURE__ */ l(o, {
65
65
  variant: "caption",
66
66
  sx: {
67
67
  display: "block",
@@ -71,13 +71,13 @@ function d({ anchorEl: d, open: f, onClose: p, onSelect: m, translation: h }) {
71
71
  px: .5
72
72
  },
73
73
  children: e.label
74
- }), /* @__PURE__ */ c(t, {
74
+ }), /* @__PURE__ */ l(n, {
75
75
  sx: {
76
76
  display: "flex",
77
77
  flexWrap: "wrap",
78
78
  gap: .25
79
79
  },
80
- children: e.emojis.map((e) => /* @__PURE__ */ c(t, {
80
+ children: e.emojis.map((e) => /* @__PURE__ */ l(n, {
81
81
  component: "button",
82
82
  title: e.name,
83
83
  "aria-label": e.name,
@@ -94,7 +94,7 @@ function d({ anchorEl: d, open: f, onClose: p, onSelect: m, translation: h }) {
94
94
  },
95
95
  children: e.emoji
96
96
  }, e.emoji))
97
- })] }, e.label)), v.length === 0 && /* @__PURE__ */ c(a, {
97
+ })] }, e.label)), v.length === 0 && /* @__PURE__ */ l(o, {
98
98
  variant: "body2",
99
99
  color: "text.secondary",
100
100
  sx: {