@bmsuisse/ui 0.7.0 → 0.8.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.js CHANGED
@@ -880,7 +880,8 @@ function FormField({
880
880
  // src/patterns/combobox/Combobox.tsx
881
881
  import { Check as Check4, ChevronsUpDown, X as X3 } from "lucide-react";
882
882
  import { useEffect, useMemo, useRef, useState as useState3 } from "react";
883
- import { jsx as jsx24, jsxs as jsxs10 } from "react/jsx-runtime";
883
+
884
+ // src/lib/optionGrouping.ts
884
885
  function groupMembers(options, group) {
885
886
  return options.filter((o) => o.group === group && !o.disabled).map((o) => o.value);
886
887
  }
@@ -890,6 +891,24 @@ function groupCheckState(options, selectedValues, group) {
890
891
  if (selectedCount === 0) return "unchecked";
891
892
  return selectedCount === members.length ? "checked" : "indeterminate";
892
893
  }
894
+ function buildRenderChunks(visibleOptions) {
895
+ const chunks = [];
896
+ visibleOptions.forEach((option, index) => {
897
+ const row = { option, index };
898
+ const last = chunks[chunks.length - 1];
899
+ if (option.group && last?.kind === "group" && last.group === option.group) {
900
+ last.rows.push(row);
901
+ } else if (option.group) {
902
+ chunks.push({ kind: "group", group: option.group, rows: [row] });
903
+ } else {
904
+ chunks.push({ kind: "single", row });
905
+ }
906
+ });
907
+ return chunks;
908
+ }
909
+
910
+ // src/patterns/combobox/Combobox.tsx
911
+ import { jsx as jsx24, jsxs as jsxs10 } from "react/jsx-runtime";
893
912
  function resolveGroupTriggerLabel(options, selectedValues, groupLabel) {
894
913
  const selectedSet = new Set(selectedValues);
895
914
  const groupsPresent = Array.from(
@@ -909,21 +928,6 @@ function resolveGroupTriggerLabel(options, selectedValues, groupLabel) {
909
928
  if (fullyCoveredGroups.length === 0 || hasLeftover) return null;
910
929
  return fullyCoveredGroups.map(groupLabel).join(", ");
911
930
  }
912
- function buildRenderChunks(visibleOptions) {
913
- const chunks = [];
914
- visibleOptions.forEach((option, index) => {
915
- const row = { option, index };
916
- const last = chunks[chunks.length - 1];
917
- if (option.group && last?.kind === "group" && last.group === option.group) {
918
- last.rows.push(row);
919
- } else if (option.group) {
920
- chunks.push({ kind: "group", group: option.group, rows: [row] });
921
- } else {
922
- chunks.push({ kind: "single", row });
923
- }
924
- });
925
- return chunks;
926
- }
927
931
  function Combobox(props) {
928
932
  const {
929
933
  options,
@@ -1149,9 +1153,249 @@ function Combobox(props) {
1149
1153
  ] });
1150
1154
  }
1151
1155
 
1156
+ // src/patterns/tag-combobox/TagCombobox.tsx
1157
+ import { X as X4 } from "lucide-react";
1158
+ import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState4 } from "react";
1159
+ import { jsx as jsx25, jsxs as jsxs11 } from "react/jsx-runtime";
1160
+ function TagCombobox({
1161
+ options,
1162
+ value,
1163
+ onChange,
1164
+ placeholder = "Select\u2026",
1165
+ emptyMessage = "No matches.",
1166
+ disabled,
1167
+ loading = false,
1168
+ loadingMessage = "Loading\u2026",
1169
+ className,
1170
+ id,
1171
+ onSearchChange,
1172
+ container,
1173
+ groupLabels,
1174
+ ...rest
1175
+ }) {
1176
+ const testId = rest["data-testid"];
1177
+ const [open, setOpen] = useState4(false);
1178
+ const [search, setSearch] = useState4("");
1179
+ const [activeIndex, setActiveIndex] = useState4(0);
1180
+ const inputRef = useRef2(null);
1181
+ const fieldRef = useRef2(null);
1182
+ const selectedOptions = useMemo2(
1183
+ () => value.map((v) => options.find((o) => o.value === v) ?? { value: v, label: v }),
1184
+ [options, value]
1185
+ );
1186
+ const visibleOptions = useMemo2(() => {
1187
+ if (onSearchChange) return options;
1188
+ const term = search.trim().toLowerCase();
1189
+ if (!term) return options;
1190
+ return options.filter((option) => option.label.toLowerCase().includes(term));
1191
+ }, [options, search, onSearchChange]);
1192
+ useEffect2(() => {
1193
+ setActiveIndex((i) => Math.min(Math.max(i, 0), Math.max(visibleOptions.length - 1, 0)));
1194
+ }, [visibleOptions.length]);
1195
+ const renderChunks = useMemo2(() => buildRenderChunks(visibleOptions), [visibleOptions]);
1196
+ useEffect2(() => {
1197
+ if (!open) return;
1198
+ setSearch("");
1199
+ onSearchChange?.("");
1200
+ const firstSelected = value[0];
1201
+ const selectedIdx = firstSelected ? options.findIndex((o) => o.value === firstSelected) : -1;
1202
+ setActiveIndex(selectedIdx >= 0 ? selectedIdx : 0);
1203
+ }, [open]);
1204
+ function updateSearch(next) {
1205
+ setSearch(next);
1206
+ onSearchChange?.(next);
1207
+ if (!open) setOpen(true);
1208
+ }
1209
+ function toggleOption(option) {
1210
+ if (option.disabled) return;
1211
+ const next = value.includes(option.value) ? value.filter((v) => v !== option.value) : [...value, option.value];
1212
+ onChange(next);
1213
+ updateSearch("");
1214
+ inputRef.current?.focus();
1215
+ }
1216
+ function removeValue(v) {
1217
+ if (options.find((o) => o.value === v)?.disabled) return;
1218
+ onChange(value.filter((existing) => existing !== v));
1219
+ }
1220
+ const groupLabel = (group) => groupLabels?.[group] ?? group;
1221
+ function toggleGroup(group) {
1222
+ const members = groupMembers(options, group);
1223
+ const next = groupCheckState(options, value, group) === "checked" ? value.filter((v) => !members.includes(v)) : Array.from(/* @__PURE__ */ new Set([...value, ...members]));
1224
+ onChange(next);
1225
+ inputRef.current?.focus();
1226
+ }
1227
+ function handleKeyDown(event) {
1228
+ if (event.key === "ArrowDown") {
1229
+ event.preventDefault();
1230
+ setOpen(true);
1231
+ setActiveIndex((i) => Math.min(i + 1, Math.max(visibleOptions.length - 1, 0)));
1232
+ } else if (event.key === "ArrowUp") {
1233
+ event.preventDefault();
1234
+ setActiveIndex((i) => Math.max(i - 1, 0));
1235
+ } else if (event.key === "Enter") {
1236
+ event.preventDefault();
1237
+ const option = visibleOptions[activeIndex];
1238
+ if (option) toggleOption(option);
1239
+ } else if (event.key === "Escape") {
1240
+ setOpen(false);
1241
+ } else if (event.key === "Backspace" && search === "") {
1242
+ const last = value[value.length - 1];
1243
+ if (last !== void 0) removeValue(last);
1244
+ }
1245
+ }
1246
+ function renderOption({ option, index }) {
1247
+ const isSelected = value.includes(option.value);
1248
+ const isActive = index === activeIndex;
1249
+ return /* @__PURE__ */ jsxs11(
1250
+ "button",
1251
+ {
1252
+ type: "button",
1253
+ role: "option",
1254
+ "aria-selected": isSelected,
1255
+ disabled: option.disabled,
1256
+ "data-testid": testId ? `${testId}-option` : void 0,
1257
+ "data-option-value": option.value,
1258
+ onMouseEnter: () => setActiveIndex(index),
1259
+ onClick: () => toggleOption(option),
1260
+ className: cn(
1261
+ "flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none disabled:pointer-events-none disabled:opacity-50",
1262
+ isActive && "bg-accent text-accent-foreground"
1263
+ ),
1264
+ children: [
1265
+ /* @__PURE__ */ jsx25(
1266
+ "input",
1267
+ {
1268
+ type: "checkbox",
1269
+ checked: isSelected,
1270
+ readOnly: true,
1271
+ tabIndex: -1,
1272
+ className: "pointer-events-none h-3.5 w-3.5 shrink-0 accent-primary"
1273
+ }
1274
+ ),
1275
+ /* @__PURE__ */ jsx25("span", { className: "truncate", children: option.label })
1276
+ ]
1277
+ },
1278
+ option.value
1279
+ );
1280
+ }
1281
+ return /* @__PURE__ */ jsxs11(Popover, { open, onOpenChange: setOpen, children: [
1282
+ /* @__PURE__ */ jsx25(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs11(
1283
+ "div",
1284
+ {
1285
+ ref: fieldRef,
1286
+ "data-testid": testId,
1287
+ role: "combobox",
1288
+ "aria-expanded": open,
1289
+ "aria-disabled": disabled,
1290
+ className: cn(
1291
+ "flex min-h-9 w-full flex-wrap items-center gap-1.5 rounded-md border border-input bg-transparent px-2 py-1.5 text-sm",
1292
+ "focus-within:outline-none focus-within:ring-2 focus-within:ring-ring",
1293
+ disabled && "cursor-not-allowed opacity-50",
1294
+ className
1295
+ ),
1296
+ onClick: () => {
1297
+ if (!disabled) inputRef.current?.focus();
1298
+ },
1299
+ children: [
1300
+ selectedOptions.map((option) => /* @__PURE__ */ jsxs11(
1301
+ "span",
1302
+ {
1303
+ className: "inline-flex items-center gap-1 rounded-full bg-muted py-0.5 pr-1 pl-2.5 text-xs font-medium text-muted-foreground",
1304
+ children: [
1305
+ /* @__PURE__ */ jsx25("span", { className: "max-w-48 truncate", children: option.label }),
1306
+ !disabled && /* @__PURE__ */ jsx25(
1307
+ "button",
1308
+ {
1309
+ type: "button",
1310
+ "aria-label": `Remove ${option.label}`,
1311
+ className: "rounded-full p-0.5 hover:bg-accent hover:text-accent-foreground",
1312
+ onClick: (event) => {
1313
+ event.stopPropagation();
1314
+ removeValue(option.value);
1315
+ },
1316
+ children: /* @__PURE__ */ jsx25(X4, { className: "h-3 w-3" })
1317
+ }
1318
+ )
1319
+ ]
1320
+ },
1321
+ option.value
1322
+ )),
1323
+ /* @__PURE__ */ jsx25(
1324
+ "input",
1325
+ {
1326
+ ref: inputRef,
1327
+ id,
1328
+ value: search,
1329
+ disabled,
1330
+ placeholder: selectedOptions.length === 0 ? placeholder : void 0,
1331
+ "aria-label": placeholder,
1332
+ className: "min-w-24 flex-1 bg-transparent outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed",
1333
+ onFocus: () => setOpen(true),
1334
+ onChange: (event) => updateSearch(event.target.value),
1335
+ onKeyDown: handleKeyDown
1336
+ }
1337
+ )
1338
+ ]
1339
+ }
1340
+ ) }),
1341
+ /* @__PURE__ */ jsx25(
1342
+ PopoverContent,
1343
+ {
1344
+ align: "start",
1345
+ container,
1346
+ className: "w-[var(--radix-popover-trigger-width)] min-w-[10rem] p-2",
1347
+ onOpenAutoFocus: (event) => event.preventDefault(),
1348
+ onCloseAutoFocus: (event) => event.preventDefault(),
1349
+ onInteractOutside: (event) => {
1350
+ if (fieldRef.current?.contains(event.target)) event.preventDefault();
1351
+ },
1352
+ children: /* @__PURE__ */ jsx25("div", { role: "listbox", className: "flex max-h-60 flex-col gap-0.5 overflow-y-auto", children: loading ? /* @__PURE__ */ jsx25("p", { className: "py-2 text-center text-sm text-muted-foreground", children: loadingMessage }) : renderChunks.length === 0 ? /* @__PURE__ */ jsx25("p", { className: "py-2 text-center text-sm text-muted-foreground", children: emptyMessage }) : renderChunks.map((chunk) => {
1353
+ if (chunk.kind === "single") return renderOption(chunk.row);
1354
+ const headerState = groupCheckState(options, value, chunk.group);
1355
+ return (
1356
+ // The header and every one of its group's rows share this wrapper -- its
1357
+ // bounds are the sticky header's containing block, so `sticky top-0` keeps
1358
+ // the header pinned for the group's whole scroll extent instead of just
1359
+ // past its first row. Spacing above a group lives on this wrapper's
1360
+ // `pt-1`/`first:pt-0`, not on the header itself -- see `Combobox`'s own
1361
+ // identical structure for why both of these matter.
1362
+ /* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-0.5 pt-1 first:pt-0", children: [
1363
+ /* @__PURE__ */ jsxs11(
1364
+ "div",
1365
+ {
1366
+ "data-group-header": true,
1367
+ className: "sticky top-0 z-10 flex items-center gap-2 rounded-sm bg-muted px-2 py-1.5 text-sm font-semibold text-foreground",
1368
+ children: [
1369
+ /* @__PURE__ */ jsx25(
1370
+ "input",
1371
+ {
1372
+ type: "checkbox",
1373
+ checked: headerState === "checked",
1374
+ ref: (el) => {
1375
+ if (el) el.indeterminate = headerState === "indeterminate";
1376
+ },
1377
+ onChange: () => toggleGroup(chunk.group),
1378
+ onClick: (event) => event.stopPropagation(),
1379
+ className: "h-3.5 w-3.5 shrink-0 accent-primary",
1380
+ "aria-label": `Select all of ${groupLabel(chunk.group)}`
1381
+ }
1382
+ ),
1383
+ /* @__PURE__ */ jsx25("span", { className: "truncate", children: groupLabel(chunk.group) })
1384
+ ]
1385
+ }
1386
+ ),
1387
+ chunk.rows.map(renderOption)
1388
+ ] }, `group-${chunk.group}`)
1389
+ );
1390
+ }) })
1391
+ }
1392
+ )
1393
+ ] });
1394
+ }
1395
+
1152
1396
  // src/patterns/alert-box/AlertBox.tsx
1153
1397
  import { AlertCircle, AlertTriangle, CheckCircle2, Info } from "lucide-react";
1154
- import { jsx as jsx25, jsxs as jsxs11 } from "react/jsx-runtime";
1398
+ import { jsx as jsx26, jsxs as jsxs12 } from "react/jsx-runtime";
1155
1399
  var variantStyles = {
1156
1400
  error: "border-destructive bg-destructive/10 text-destructive",
1157
1401
  warning: "border-amber-500 bg-amber-500/10 text-amber-700 dark:text-amber-300",
@@ -1159,24 +1403,24 @@ var variantStyles = {
1159
1403
  success: "border-emerald-500 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300"
1160
1404
  };
1161
1405
  var defaultIcons = {
1162
- error: /* @__PURE__ */ jsx25(AlertCircle, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
1163
- warning: /* @__PURE__ */ jsx25(AlertTriangle, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
1164
- info: /* @__PURE__ */ jsx25(Info, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
1165
- success: /* @__PURE__ */ jsx25(CheckCircle2, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" })
1406
+ error: /* @__PURE__ */ jsx26(AlertCircle, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
1407
+ warning: /* @__PURE__ */ jsx26(AlertTriangle, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
1408
+ info: /* @__PURE__ */ jsx26(Info, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
1409
+ success: /* @__PURE__ */ jsx26(CheckCircle2, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" })
1166
1410
  };
1167
1411
  var AlertBox = ({ variant, title, children, className, icon }) => {
1168
1412
  const resolvedIcon = icon === void 0 ? defaultIcons[variant] : icon;
1169
- return /* @__PURE__ */ jsx25("div", { className: cn("rounded-md border p-3 text-sm", variantStyles[variant], className), children: /* @__PURE__ */ jsxs11("div", { className: "flex flex-row items-start gap-2", children: [
1170
- resolvedIcon != null && /* @__PURE__ */ jsx25("span", { className: "mt-0.5 shrink-0", children: resolvedIcon }),
1171
- /* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-1", children: [
1172
- title && /* @__PURE__ */ jsx25("p", { className: "font-bold", children: title }),
1173
- /* @__PURE__ */ jsx25("div", { children })
1413
+ return /* @__PURE__ */ jsx26("div", { className: cn("rounded-md border p-3 text-sm", variantStyles[variant], className), children: /* @__PURE__ */ jsxs12("div", { className: "flex flex-row items-start gap-2", children: [
1414
+ resolvedIcon != null && /* @__PURE__ */ jsx26("span", { className: "mt-0.5 shrink-0", children: resolvedIcon }),
1415
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-1", children: [
1416
+ title && /* @__PURE__ */ jsx26("p", { className: "font-bold", children: title }),
1417
+ /* @__PURE__ */ jsx26("div", { children })
1174
1418
  ] })
1175
1419
  ] }) });
1176
1420
  };
1177
1421
 
1178
1422
  // src/patterns/status-badge/StatusBadge.tsx
1179
- import { jsx as jsx26 } from "react/jsx-runtime";
1423
+ import { jsx as jsx27 } from "react/jsx-runtime";
1180
1424
  var TONE_CLASSES = {
1181
1425
  success: "border-transparent bg-emerald-500/15 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-300",
1182
1426
  warning: "border-transparent bg-amber-500/15 text-amber-700 dark:bg-amber-500/20 dark:text-amber-300",
@@ -1223,11 +1467,11 @@ function resolveTone(status, tone, toneMap) {
1223
1467
  var StatusBadge = ({ status, label, tone, toneMap, className, ...props }) => {
1224
1468
  const resolvedTone = resolveTone(status, tone, toneMap);
1225
1469
  const displayLabel = label ?? deriveLabel(status);
1226
- return /* @__PURE__ */ jsx26(Badge, { variant: "outline", className: cn(TONE_CLASSES[resolvedTone], className), ...props, children: displayLabel });
1470
+ return /* @__PURE__ */ jsx27(Badge, { variant: "outline", className: cn(TONE_CLASSES[resolvedTone], className), ...props, children: displayLabel });
1227
1471
  };
1228
1472
 
1229
1473
  // src/patterns/button-group/ButtonGroup.tsx
1230
- import { jsx as jsx27, jsxs as jsxs12 } from "react/jsx-runtime";
1474
+ import { jsx as jsx28, jsxs as jsxs13 } from "react/jsx-runtime";
1231
1475
  function ButtonGroup({
1232
1476
  options,
1233
1477
  value,
@@ -1237,10 +1481,10 @@ function ButtonGroup({
1237
1481
  className,
1238
1482
  ...props
1239
1483
  }) {
1240
- return /* @__PURE__ */ jsx27("div", { role: "group", "aria-label": props["aria-label"], className: cn("inline-flex", className), "data-testid": props["data-testid"], children: options.map((option, index) => {
1484
+ return /* @__PURE__ */ jsx28("div", { role: "group", "aria-label": props["aria-label"], className: cn("inline-flex", className), "data-testid": props["data-testid"], children: options.map((option, index) => {
1241
1485
  const selected = option.value === value;
1242
1486
  const Icon2 = option.icon;
1243
- return /* @__PURE__ */ jsxs12(
1487
+ return /* @__PURE__ */ jsxs13(
1244
1488
  Button,
1245
1489
  {
1246
1490
  type: "button",
@@ -1258,7 +1502,7 @@ function ButtonGroup({
1258
1502
  index > 0 && "-ml-px"
1259
1503
  ),
1260
1504
  children: [
1261
- Icon2 && /* @__PURE__ */ jsx27(Icon2, { className: "h-4 w-4" }),
1505
+ Icon2 && /* @__PURE__ */ jsx28(Icon2, { className: "h-4 w-4" }),
1262
1506
  option.label
1263
1507
  ]
1264
1508
  },
@@ -1270,7 +1514,7 @@ function ButtonGroup({
1270
1514
  // src/patterns/loading-spinner/LoadingSpinner.tsx
1271
1515
  import { cva as cva4 } from "class-variance-authority";
1272
1516
  import { Loader2 } from "lucide-react";
1273
- import { jsx as jsx28, jsxs as jsxs13 } from "react/jsx-runtime";
1517
+ import { jsx as jsx29, jsxs as jsxs14 } from "react/jsx-runtime";
1274
1518
  var loadingSpinnerIconVariants = cva4("animate-spin text-muted-foreground", {
1275
1519
  variants: {
1276
1520
  size: {
@@ -1288,27 +1532,27 @@ var LoadingSpinner = ({
1288
1532
  label,
1289
1533
  className,
1290
1534
  ...props
1291
- }) => /* @__PURE__ */ jsxs13("span", { role: "status", className: cn("inline-flex items-center gap-2", className), ...props, children: [
1292
- /* @__PURE__ */ jsx28(Loader2, { className: cn(loadingSpinnerIconVariants({ size })), "aria-hidden": "true" }),
1293
- label ? /* @__PURE__ */ jsx28("span", { children: label }) : null
1535
+ }) => /* @__PURE__ */ jsxs14("span", { role: "status", className: cn("inline-flex items-center gap-2", className), ...props, children: [
1536
+ /* @__PURE__ */ jsx29(Loader2, { className: cn(loadingSpinnerIconVariants({ size })), "aria-hidden": "true" }),
1537
+ label ? /* @__PURE__ */ jsx29("span", { children: label }) : null
1294
1538
  ] });
1295
1539
  var LoadingOverlay = ({
1296
1540
  size = "lg",
1297
1541
  label,
1298
1542
  className,
1299
1543
  ...props
1300
- }) => /* @__PURE__ */ jsx28(
1544
+ }) => /* @__PURE__ */ jsx29(
1301
1545
  "div",
1302
1546
  {
1303
1547
  className: cn("flex h-full min-h-32 w-full items-center justify-center", className),
1304
1548
  ...props,
1305
- children: /* @__PURE__ */ jsx28(LoadingSpinner, { size, label })
1549
+ children: /* @__PURE__ */ jsx29(LoadingSpinner, { size, label })
1306
1550
  }
1307
1551
  );
1308
1552
 
1309
1553
  // src/patterns/search-bar/SearchBar.tsx
1310
- import { Search, X as X4 } from "lucide-react";
1311
- import { jsx as jsx29, jsxs as jsxs14 } from "react/jsx-runtime";
1554
+ import { Search, X as X5 } from "lucide-react";
1555
+ import { jsx as jsx30, jsxs as jsxs15 } from "react/jsx-runtime";
1312
1556
  function SearchBar({
1313
1557
  value,
1314
1558
  onChange,
@@ -1320,9 +1564,9 @@ function SearchBar({
1320
1564
  ...props
1321
1565
  }) {
1322
1566
  const showClear = onClear !== false && value.length > 0;
1323
- return /* @__PURE__ */ jsxs14("div", { className: cn("relative", className), children: [
1324
- /* @__PURE__ */ jsx29("span", { className: "pointer-events-none absolute top-1/2 left-4 -translate-y-1/2 text-muted-foreground", children: isLoading ? /* @__PURE__ */ jsx29(LoadingSpinner, { size: "sm" }) : /* @__PURE__ */ jsx29(Search, { className: "h-4 w-4", "aria-hidden": "true" }) }),
1325
- /* @__PURE__ */ jsx29(
1567
+ return /* @__PURE__ */ jsxs15("div", { className: cn("relative", className), children: [
1568
+ /* @__PURE__ */ jsx30("span", { className: "pointer-events-none absolute top-1/2 left-4 -translate-y-1/2 text-muted-foreground", children: isLoading ? /* @__PURE__ */ jsx30(LoadingSpinner, { size: "sm" }) : /* @__PURE__ */ jsx30(Search, { className: "h-4 w-4", "aria-hidden": "true" }) }),
1569
+ /* @__PURE__ */ jsx30(
1326
1570
  "input",
1327
1571
  {
1328
1572
  ref: inputRef,
@@ -1341,14 +1585,14 @@ function SearchBar({
1341
1585
  ...props
1342
1586
  }
1343
1587
  ),
1344
- showClear && /* @__PURE__ */ jsx29(
1588
+ showClear && /* @__PURE__ */ jsx30(
1345
1589
  "button",
1346
1590
  {
1347
1591
  type: "button",
1348
1592
  onClick: () => onClear ? onClear() : onChange(""),
1349
1593
  "aria-label": clearLabel,
1350
1594
  className: "absolute top-1/2 right-2 flex h-7 w-7 -translate-y-1/2 items-center justify-center rounded-full text-muted-foreground hover:bg-accent hover:text-foreground",
1351
- children: /* @__PURE__ */ jsx29(X4, { className: "h-4 w-4", "aria-hidden": "true" })
1595
+ children: /* @__PURE__ */ jsx30(X5, { className: "h-4 w-4", "aria-hidden": "true" })
1352
1596
  }
1353
1597
  )
1354
1598
  ] });
@@ -1363,7 +1607,7 @@ function useSidebarCollapsed() {
1363
1607
  }
1364
1608
 
1365
1609
  // src/patterns/sidebar/NavItem.tsx
1366
- import { jsx as jsx30, jsxs as jsxs15 } from "react/jsx-runtime";
1610
+ import { jsx as jsx31, jsxs as jsxs16 } from "react/jsx-runtime";
1367
1611
  function NavItem({
1368
1612
  as,
1369
1613
  icon: Icon2,
@@ -1375,7 +1619,7 @@ function NavItem({
1375
1619
  }) {
1376
1620
  const collapsed = useSidebarCollapsed();
1377
1621
  const Comp = as ?? "a";
1378
- const row = /* @__PURE__ */ jsxs15(
1622
+ const row = /* @__PURE__ */ jsxs16(
1379
1623
  Comp,
1380
1624
  {
1381
1625
  className: cn(
@@ -1387,7 +1631,7 @@ function NavItem({
1387
1631
  ),
1388
1632
  ...rest,
1389
1633
  children: [
1390
- Icon2 && /* @__PURE__ */ jsx30(
1634
+ Icon2 && /* @__PURE__ */ jsx31(
1391
1635
  Icon2,
1392
1636
  {
1393
1637
  className: cn("h-[18px] w-[18px] shrink-0", active ? "text-nav-primary" : "text-muted-foreground"),
@@ -1395,7 +1639,7 @@ function NavItem({
1395
1639
  "aria-hidden": true
1396
1640
  }
1397
1641
  ),
1398
- /* @__PURE__ */ jsx30(
1642
+ /* @__PURE__ */ jsx31(
1399
1643
  "span",
1400
1644
  {
1401
1645
  className: cn(
@@ -1410,9 +1654,9 @@ function NavItem({
1410
1654
  }
1411
1655
  );
1412
1656
  if (!collapsed) return row;
1413
- return /* @__PURE__ */ jsxs15(Tooltip, { children: [
1414
- /* @__PURE__ */ jsx30(TooltipTrigger, { asChild: true, children: row }),
1415
- /* @__PURE__ */ jsxs15(TooltipContent, { side: "right", className: "flex items-center gap-1.5", children: [
1657
+ return /* @__PURE__ */ jsxs16(Tooltip, { children: [
1658
+ /* @__PURE__ */ jsx31(TooltipTrigger, { asChild: true, children: row }),
1659
+ /* @__PURE__ */ jsxs16(TooltipContent, { side: "right", className: "flex items-center gap-1.5", children: [
1416
1660
  label,
1417
1661
  badge
1418
1662
  ] })
@@ -1421,8 +1665,8 @@ function NavItem({
1421
1665
 
1422
1666
  // src/patterns/sidebar/NavGroup.tsx
1423
1667
  import { ChevronDown as ChevronDown2 } from "lucide-react";
1424
- import { useState as useState4 } from "react";
1425
- import { jsx as jsx31, jsxs as jsxs16 } from "react/jsx-runtime";
1668
+ import { useState as useState5 } from "react";
1669
+ import { jsx as jsx32, jsxs as jsxs17 } from "react/jsx-runtime";
1426
1670
  function NavGroup({
1427
1671
  label,
1428
1672
  defaultCollapsed = false,
@@ -1431,15 +1675,15 @@ function NavGroup({
1431
1675
  children,
1432
1676
  className
1433
1677
  }) {
1434
- const [internalCollapsed, setInternalCollapsed] = useState4(defaultCollapsed);
1678
+ const [internalCollapsed, setInternalCollapsed] = useState5(defaultCollapsed);
1435
1679
  const groupCollapsed = collapsed ?? internalCollapsed;
1436
1680
  const railCollapsed = useSidebarCollapsed();
1437
1681
  const setGroupCollapsed = (next) => {
1438
1682
  onCollapsedChange?.(next);
1439
1683
  if (collapsed === void 0) setInternalCollapsed(next);
1440
1684
  };
1441
- return /* @__PURE__ */ jsxs16("div", { className: cn("flex flex-col gap-0.5", className), children: [
1442
- label && (railCollapsed ? /* @__PURE__ */ jsx31("div", { className: "mx-2 my-1 h-px bg-border/50", "aria-hidden": true }) : /* @__PURE__ */ jsxs16(
1685
+ return /* @__PURE__ */ jsxs17("div", { className: cn("flex flex-col gap-0.5", className), children: [
1686
+ label && (railCollapsed ? /* @__PURE__ */ jsx32("div", { className: "mx-2 my-1 h-px bg-border/50", "aria-hidden": true }) : /* @__PURE__ */ jsxs17(
1443
1687
  "button",
1444
1688
  {
1445
1689
  type: "button",
@@ -1447,8 +1691,8 @@ function NavGroup({
1447
1691
  "aria-expanded": !groupCollapsed,
1448
1692
  className: "group flex w-full items-center justify-between px-2.5 py-0.5 text-[11px] font-medium text-muted-foreground/80 hover:text-foreground",
1449
1693
  children: [
1450
- /* @__PURE__ */ jsx31("span", { children: label }),
1451
- /* @__PURE__ */ jsx31(
1694
+ /* @__PURE__ */ jsx32("span", { children: label }),
1695
+ /* @__PURE__ */ jsx32(
1452
1696
  ChevronDown2,
1453
1697
  {
1454
1698
  className: cn(
@@ -1461,7 +1705,7 @@ function NavGroup({
1461
1705
  ]
1462
1706
  }
1463
1707
  )),
1464
- !groupCollapsed && /* @__PURE__ */ jsx31("div", { className: "flex flex-col gap-0.5", children })
1708
+ !groupCollapsed && /* @__PURE__ */ jsx32("div", { className: "flex flex-col gap-0.5", children })
1465
1709
  ] });
1466
1710
  }
1467
1711
 
@@ -1469,11 +1713,11 @@ function NavGroup({
1469
1713
  import { PanelLeftClose, PanelLeftOpen } from "lucide-react";
1470
1714
  import {
1471
1715
  useCallback,
1472
- useEffect as useEffect2,
1473
- useRef as useRef2,
1474
- useState as useState5
1716
+ useEffect as useEffect3,
1717
+ useRef as useRef3,
1718
+ useState as useState6
1475
1719
  } from "react";
1476
- import { jsx as jsx32, jsxs as jsxs17 } from "react/jsx-runtime";
1720
+ import { jsx as jsx33, jsxs as jsxs18 } from "react/jsx-runtime";
1477
1721
  function resolve(value, collapsed) {
1478
1722
  return typeof value === "function" ? value(collapsed) : value;
1479
1723
  }
@@ -1492,9 +1736,9 @@ function Sidebar({
1492
1736
  children,
1493
1737
  className
1494
1738
  }) {
1495
- const [internalWidth, setInternalWidth] = useState5(defaultWidth);
1739
+ const [internalWidth, setInternalWidth] = useState6(defaultWidth);
1496
1740
  const currentWidth = width ?? internalWidth;
1497
- const [isDragging, setIsDragging] = useState5(false);
1741
+ const [isDragging, setIsDragging] = useState6(false);
1498
1742
  const setWidth = useCallback(
1499
1743
  (next) => {
1500
1744
  const clamped = Math.min(maxWidth, Math.max(minWidth, next));
@@ -1503,7 +1747,7 @@ function Sidebar({
1503
1747
  },
1504
1748
  [maxWidth, minWidth, onWidthChange, width]
1505
1749
  );
1506
- const dragStartRef = useRef2(null);
1750
+ const dragStartRef = useRef3(null);
1507
1751
  const handlePointerDown = (event) => {
1508
1752
  event.currentTarget.setPointerCapture(event.pointerId);
1509
1753
  dragStartRef.current = { x: event.clientX, width: currentWidth };
@@ -1520,7 +1764,7 @@ function Sidebar({
1520
1764
  dragStartRef.current = null;
1521
1765
  setIsDragging(false);
1522
1766
  };
1523
- return /* @__PURE__ */ jsx32(SidebarContextProvider, { value: { collapsed }, children: /* @__PURE__ */ jsx32(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxs17(
1767
+ return /* @__PURE__ */ jsx33(SidebarContextProvider, { value: { collapsed }, children: /* @__PURE__ */ jsx33(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxs18(
1524
1768
  "aside",
1525
1769
  {
1526
1770
  style: { width: collapsed ? railWidth : currentWidth },
@@ -1530,22 +1774,22 @@ function Sidebar({
1530
1774
  className
1531
1775
  ),
1532
1776
  children: [
1533
- (header || onCollapsedChange) && /* @__PURE__ */ jsxs17("div", { className: "flex h-16 items-center gap-2 border-b border-border px-3", children: [
1534
- header && /* @__PURE__ */ jsx32("div", { className: "min-w-0 flex-1", children: resolve(header, collapsed) }),
1535
- onCollapsedChange && /* @__PURE__ */ jsx32(
1777
+ (header || onCollapsedChange) && /* @__PURE__ */ jsxs18("div", { className: "flex h-16 items-center gap-2 border-b border-border px-3", children: [
1778
+ header && /* @__PURE__ */ jsx33("div", { className: "min-w-0 flex-1", children: resolve(header, collapsed) }),
1779
+ onCollapsedChange && /* @__PURE__ */ jsx33(
1536
1780
  "button",
1537
1781
  {
1538
1782
  type: "button",
1539
1783
  onClick: () => onCollapsedChange(!collapsed),
1540
1784
  "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
1541
1785
  className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground",
1542
- children: collapsed ? /* @__PURE__ */ jsx32(PanelLeftOpen, { className: "h-4 w-4", "aria-hidden": true }) : /* @__PURE__ */ jsx32(PanelLeftClose, { className: "h-4 w-4", "aria-hidden": true })
1786
+ children: collapsed ? /* @__PURE__ */ jsx33(PanelLeftOpen, { className: "h-4 w-4", "aria-hidden": true }) : /* @__PURE__ */ jsx33(PanelLeftClose, { className: "h-4 w-4", "aria-hidden": true })
1543
1787
  }
1544
1788
  )
1545
1789
  ] }),
1546
- /* @__PURE__ */ jsx32(SidebarNav, { children }),
1547
- footer && /* @__PURE__ */ jsx32("div", { className: "border-t border-border/50 px-2 py-2", children: resolve(footer, collapsed) }),
1548
- resizable && !collapsed && /* @__PURE__ */ jsx32(
1790
+ /* @__PURE__ */ jsx33(SidebarNav, { children }),
1791
+ footer && /* @__PURE__ */ jsx33("div", { className: "border-t border-border/50 px-2 py-2", children: resolve(footer, collapsed) }),
1792
+ resizable && !collapsed && /* @__PURE__ */ jsx33(
1549
1793
  "div",
1550
1794
  {
1551
1795
  onPointerDown: handlePointerDown,
@@ -1560,9 +1804,9 @@ function Sidebar({
1560
1804
  ) }) });
1561
1805
  }
1562
1806
  function SidebarNav({ children, className, ...rest }) {
1563
- const ref = useRef2(null);
1564
- const [showFade, setShowFade] = useState5(false);
1565
- useEffect2(() => {
1807
+ const ref = useRef3(null);
1808
+ const [showFade, setShowFade] = useState6(false);
1809
+ useEffect3(() => {
1566
1810
  const el = ref.current;
1567
1811
  if (!el) return;
1568
1812
  const update = () => setShowFade(el.scrollHeight - el.scrollTop - el.clientHeight > 1);
@@ -1575,8 +1819,8 @@ function SidebarNav({ children, className, ...rest }) {
1575
1819
  observer.disconnect();
1576
1820
  };
1577
1821
  }, []);
1578
- return /* @__PURE__ */ jsxs17("div", { className: "relative flex min-h-0 flex-1 flex-col", children: [
1579
- /* @__PURE__ */ jsx32(
1822
+ return /* @__PURE__ */ jsxs18("div", { className: "relative flex min-h-0 flex-1 flex-col", children: [
1823
+ /* @__PURE__ */ jsx33(
1580
1824
  "div",
1581
1825
  {
1582
1826
  ref,
@@ -1585,7 +1829,7 @@ function SidebarNav({ children, className, ...rest }) {
1585
1829
  children
1586
1830
  }
1587
1831
  ),
1588
- showFade && /* @__PURE__ */ jsx32(
1832
+ showFade && /* @__PURE__ */ jsx33(
1589
1833
  "div",
1590
1834
  {
1591
1835
  className: "pointer-events-none absolute right-0 bottom-0 left-0 h-6 bg-gradient-to-t from-sidebar to-transparent",
@@ -1679,6 +1923,7 @@ export {
1679
1923
  TabsContent,
1680
1924
  TabsList,
1681
1925
  TabsTrigger,
1926
+ TagCombobox,
1682
1927
  Textarea,
1683
1928
  Tooltip,
1684
1929
  TooltipContent,