@bmsuisse/ui 0.6.3 → 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.d.ts +167 -2
- package/dist/index.js +537 -46
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
-
|
|
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
|
|
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__ */
|
|
1163
|
-
warning: /* @__PURE__ */
|
|
1164
|
-
info: /* @__PURE__ */
|
|
1165
|
-
success: /* @__PURE__ */
|
|
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__ */
|
|
1170
|
-
resolvedIcon != null && /* @__PURE__ */
|
|
1171
|
-
/* @__PURE__ */
|
|
1172
|
-
title && /* @__PURE__ */
|
|
1173
|
-
/* @__PURE__ */
|
|
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
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
1292
|
-
/* @__PURE__ */
|
|
1293
|
-
label ? /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
1311
|
-
import { jsx as
|
|
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__ */
|
|
1324
|
-
/* @__PURE__ */
|
|
1325
|
-
/* @__PURE__ */
|
|
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,255 @@ function SearchBar({
|
|
|
1341
1585
|
...props
|
|
1342
1586
|
}
|
|
1343
1587
|
),
|
|
1344
|
-
showClear && /* @__PURE__ */
|
|
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__ */
|
|
1595
|
+
children: /* @__PURE__ */ jsx30(X5, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
1596
|
+
}
|
|
1597
|
+
)
|
|
1598
|
+
] });
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
// src/patterns/sidebar/context.ts
|
|
1602
|
+
import { createContext, useContext } from "react";
|
|
1603
|
+
var SidebarContext = createContext({ collapsed: false });
|
|
1604
|
+
var SidebarContextProvider = SidebarContext.Provider;
|
|
1605
|
+
function useSidebarCollapsed() {
|
|
1606
|
+
return useContext(SidebarContext).collapsed;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
// src/patterns/sidebar/NavItem.tsx
|
|
1610
|
+
import { jsx as jsx31, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1611
|
+
function NavItem({
|
|
1612
|
+
as,
|
|
1613
|
+
icon: Icon2,
|
|
1614
|
+
label,
|
|
1615
|
+
active = false,
|
|
1616
|
+
badge,
|
|
1617
|
+
className,
|
|
1618
|
+
...rest
|
|
1619
|
+
}) {
|
|
1620
|
+
const collapsed = useSidebarCollapsed();
|
|
1621
|
+
const Comp = as ?? "a";
|
|
1622
|
+
const row = /* @__PURE__ */ jsxs16(
|
|
1623
|
+
Comp,
|
|
1624
|
+
{
|
|
1625
|
+
className: cn(
|
|
1626
|
+
"group relative flex items-center rounded-lg px-2.5 py-2 text-[13px] font-medium transition-colors",
|
|
1627
|
+
"focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-inset focus-visible:outline-none",
|
|
1628
|
+
collapsed ? "justify-center gap-0" : "gap-2.5",
|
|
1629
|
+
active ? "bg-nav-primary/8 font-semibold text-foreground" : "text-foreground/55 hover:bg-muted/60 hover:text-foreground",
|
|
1630
|
+
className
|
|
1631
|
+
),
|
|
1632
|
+
...rest,
|
|
1633
|
+
children: [
|
|
1634
|
+
Icon2 && /* @__PURE__ */ jsx31(
|
|
1635
|
+
Icon2,
|
|
1636
|
+
{
|
|
1637
|
+
className: cn("h-[18px] w-[18px] shrink-0", active ? "text-nav-primary" : "text-muted-foreground"),
|
|
1638
|
+
strokeWidth: active ? 2 : 1.75,
|
|
1639
|
+
"aria-hidden": true
|
|
1640
|
+
}
|
|
1641
|
+
),
|
|
1642
|
+
/* @__PURE__ */ jsx31(
|
|
1643
|
+
"span",
|
|
1644
|
+
{
|
|
1645
|
+
className: cn(
|
|
1646
|
+
"truncate transition-[opacity,max-width] duration-150",
|
|
1647
|
+
collapsed ? "max-w-0 opacity-0" : "max-w-[10rem] opacity-100"
|
|
1648
|
+
),
|
|
1649
|
+
children: label
|
|
1650
|
+
}
|
|
1651
|
+
),
|
|
1652
|
+
!collapsed && badge
|
|
1653
|
+
]
|
|
1654
|
+
}
|
|
1655
|
+
);
|
|
1656
|
+
if (!collapsed) return row;
|
|
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: [
|
|
1660
|
+
label,
|
|
1661
|
+
badge
|
|
1662
|
+
] })
|
|
1663
|
+
] });
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// src/patterns/sidebar/NavGroup.tsx
|
|
1667
|
+
import { ChevronDown as ChevronDown2 } from "lucide-react";
|
|
1668
|
+
import { useState as useState5 } from "react";
|
|
1669
|
+
import { jsx as jsx32, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1670
|
+
function NavGroup({
|
|
1671
|
+
label,
|
|
1672
|
+
defaultCollapsed = false,
|
|
1673
|
+
collapsed,
|
|
1674
|
+
onCollapsedChange,
|
|
1675
|
+
children,
|
|
1676
|
+
className
|
|
1677
|
+
}) {
|
|
1678
|
+
const [internalCollapsed, setInternalCollapsed] = useState5(defaultCollapsed);
|
|
1679
|
+
const groupCollapsed = collapsed ?? internalCollapsed;
|
|
1680
|
+
const railCollapsed = useSidebarCollapsed();
|
|
1681
|
+
const setGroupCollapsed = (next) => {
|
|
1682
|
+
onCollapsedChange?.(next);
|
|
1683
|
+
if (collapsed === void 0) setInternalCollapsed(next);
|
|
1684
|
+
};
|
|
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(
|
|
1687
|
+
"button",
|
|
1688
|
+
{
|
|
1689
|
+
type: "button",
|
|
1690
|
+
onClick: () => setGroupCollapsed(!groupCollapsed),
|
|
1691
|
+
"aria-expanded": !groupCollapsed,
|
|
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",
|
|
1693
|
+
children: [
|
|
1694
|
+
/* @__PURE__ */ jsx32("span", { children: label }),
|
|
1695
|
+
/* @__PURE__ */ jsx32(
|
|
1696
|
+
ChevronDown2,
|
|
1697
|
+
{
|
|
1698
|
+
className: cn(
|
|
1699
|
+
"h-4 w-4 opacity-40 transition-transform group-hover:opacity-100",
|
|
1700
|
+
groupCollapsed && "-rotate-90"
|
|
1701
|
+
),
|
|
1702
|
+
"aria-hidden": true
|
|
1703
|
+
}
|
|
1704
|
+
)
|
|
1705
|
+
]
|
|
1706
|
+
}
|
|
1707
|
+
)),
|
|
1708
|
+
!groupCollapsed && /* @__PURE__ */ jsx32("div", { className: "flex flex-col gap-0.5", children })
|
|
1709
|
+
] });
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
// src/patterns/sidebar/Sidebar.tsx
|
|
1713
|
+
import { PanelLeftClose, PanelLeftOpen } from "lucide-react";
|
|
1714
|
+
import {
|
|
1715
|
+
useCallback,
|
|
1716
|
+
useEffect as useEffect3,
|
|
1717
|
+
useRef as useRef3,
|
|
1718
|
+
useState as useState6
|
|
1719
|
+
} from "react";
|
|
1720
|
+
import { jsx as jsx33, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1721
|
+
function resolve(value, collapsed) {
|
|
1722
|
+
return typeof value === "function" ? value(collapsed) : value;
|
|
1723
|
+
}
|
|
1724
|
+
function Sidebar({
|
|
1725
|
+
collapsed = false,
|
|
1726
|
+
onCollapsedChange,
|
|
1727
|
+
resizable = false,
|
|
1728
|
+
width,
|
|
1729
|
+
onWidthChange,
|
|
1730
|
+
defaultWidth = 240,
|
|
1731
|
+
minWidth = 180,
|
|
1732
|
+
maxWidth = 420,
|
|
1733
|
+
railWidth = 56,
|
|
1734
|
+
header,
|
|
1735
|
+
footer,
|
|
1736
|
+
children,
|
|
1737
|
+
className
|
|
1738
|
+
}) {
|
|
1739
|
+
const [internalWidth, setInternalWidth] = useState6(defaultWidth);
|
|
1740
|
+
const currentWidth = width ?? internalWidth;
|
|
1741
|
+
const [isDragging, setIsDragging] = useState6(false);
|
|
1742
|
+
const setWidth = useCallback(
|
|
1743
|
+
(next) => {
|
|
1744
|
+
const clamped = Math.min(maxWidth, Math.max(minWidth, next));
|
|
1745
|
+
onWidthChange?.(clamped);
|
|
1746
|
+
if (width === void 0) setInternalWidth(clamped);
|
|
1747
|
+
},
|
|
1748
|
+
[maxWidth, minWidth, onWidthChange, width]
|
|
1749
|
+
);
|
|
1750
|
+
const dragStartRef = useRef3(null);
|
|
1751
|
+
const handlePointerDown = (event) => {
|
|
1752
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
1753
|
+
dragStartRef.current = { x: event.clientX, width: currentWidth };
|
|
1754
|
+
setIsDragging(true);
|
|
1755
|
+
};
|
|
1756
|
+
const handlePointerMove = (event) => {
|
|
1757
|
+
if (!dragStartRef.current) return;
|
|
1758
|
+
setWidth(dragStartRef.current.width + (event.clientX - dragStartRef.current.x));
|
|
1759
|
+
};
|
|
1760
|
+
const endDrag = (event) => {
|
|
1761
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
1762
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
1763
|
+
}
|
|
1764
|
+
dragStartRef.current = null;
|
|
1765
|
+
setIsDragging(false);
|
|
1766
|
+
};
|
|
1767
|
+
return /* @__PURE__ */ jsx33(SidebarContextProvider, { value: { collapsed }, children: /* @__PURE__ */ jsx33(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxs18(
|
|
1768
|
+
"aside",
|
|
1769
|
+
{
|
|
1770
|
+
style: { width: collapsed ? railWidth : currentWidth },
|
|
1771
|
+
className: cn(
|
|
1772
|
+
"relative flex h-full shrink-0 flex-col border-r border-border bg-sidebar",
|
|
1773
|
+
!isDragging && "transition-[width] duration-150",
|
|
1774
|
+
className
|
|
1775
|
+
),
|
|
1776
|
+
children: [
|
|
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(
|
|
1780
|
+
"button",
|
|
1781
|
+
{
|
|
1782
|
+
type: "button",
|
|
1783
|
+
onClick: () => onCollapsedChange(!collapsed),
|
|
1784
|
+
"aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
|
|
1785
|
+
className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
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 })
|
|
1787
|
+
}
|
|
1788
|
+
)
|
|
1789
|
+
] }),
|
|
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(
|
|
1793
|
+
"div",
|
|
1794
|
+
{
|
|
1795
|
+
onPointerDown: handlePointerDown,
|
|
1796
|
+
onPointerMove: handlePointerMove,
|
|
1797
|
+
onPointerUp: endDrag,
|
|
1798
|
+
onDoubleClick: () => setWidth(defaultWidth),
|
|
1799
|
+
className: "absolute top-0 right-0 h-full w-1.5 cursor-col-resize touch-none hover:bg-primary/40"
|
|
1800
|
+
}
|
|
1801
|
+
)
|
|
1802
|
+
]
|
|
1803
|
+
}
|
|
1804
|
+
) }) });
|
|
1805
|
+
}
|
|
1806
|
+
function SidebarNav({ children, className, ...rest }) {
|
|
1807
|
+
const ref = useRef3(null);
|
|
1808
|
+
const [showFade, setShowFade] = useState6(false);
|
|
1809
|
+
useEffect3(() => {
|
|
1810
|
+
const el = ref.current;
|
|
1811
|
+
if (!el) return;
|
|
1812
|
+
const update = () => setShowFade(el.scrollHeight - el.scrollTop - el.clientHeight > 1);
|
|
1813
|
+
update();
|
|
1814
|
+
el.addEventListener("scroll", update);
|
|
1815
|
+
const observer = new ResizeObserver(update);
|
|
1816
|
+
observer.observe(el);
|
|
1817
|
+
return () => {
|
|
1818
|
+
el.removeEventListener("scroll", update);
|
|
1819
|
+
observer.disconnect();
|
|
1820
|
+
};
|
|
1821
|
+
}, []);
|
|
1822
|
+
return /* @__PURE__ */ jsxs18("div", { className: "relative flex min-h-0 flex-1 flex-col", children: [
|
|
1823
|
+
/* @__PURE__ */ jsx33(
|
|
1824
|
+
"div",
|
|
1825
|
+
{
|
|
1826
|
+
ref,
|
|
1827
|
+
className: cn("flex flex-1 flex-col gap-3 overflow-y-auto px-2 py-2", className),
|
|
1828
|
+
...rest,
|
|
1829
|
+
children
|
|
1830
|
+
}
|
|
1831
|
+
),
|
|
1832
|
+
showFade && /* @__PURE__ */ jsx33(
|
|
1833
|
+
"div",
|
|
1834
|
+
{
|
|
1835
|
+
className: "pointer-events-none absolute right-0 bottom-0 left-0 h-6 bg-gradient-to-t from-sidebar to-transparent",
|
|
1836
|
+
"aria-hidden": true
|
|
1352
1837
|
}
|
|
1353
1838
|
)
|
|
1354
1839
|
] });
|
|
@@ -1396,6 +1881,8 @@ export {
|
|
|
1396
1881
|
LoadingOverlay,
|
|
1397
1882
|
LoadingSpinner,
|
|
1398
1883
|
Modal,
|
|
1884
|
+
NavGroup,
|
|
1885
|
+
NavItem,
|
|
1399
1886
|
Popover,
|
|
1400
1887
|
PopoverAnchor,
|
|
1401
1888
|
PopoverContent,
|
|
@@ -1419,6 +1906,8 @@ export {
|
|
|
1419
1906
|
SheetPortal,
|
|
1420
1907
|
SheetTitle,
|
|
1421
1908
|
SheetTrigger,
|
|
1909
|
+
Sidebar,
|
|
1910
|
+
SidebarNav,
|
|
1422
1911
|
Skeleton,
|
|
1423
1912
|
StatusBadge,
|
|
1424
1913
|
Switch,
|
|
@@ -1434,6 +1923,7 @@ export {
|
|
|
1434
1923
|
TabsContent,
|
|
1435
1924
|
TabsList,
|
|
1436
1925
|
TabsTrigger,
|
|
1926
|
+
TagCombobox,
|
|
1437
1927
|
Textarea,
|
|
1438
1928
|
Tooltip,
|
|
1439
1929
|
TooltipContent,
|
|
@@ -1443,6 +1933,7 @@ export {
|
|
|
1443
1933
|
buttonVariants,
|
|
1444
1934
|
cn,
|
|
1445
1935
|
loadingSpinnerIconVariants,
|
|
1446
|
-
sheetVariants
|
|
1936
|
+
sheetVariants,
|
|
1937
|
+
useSidebarCollapsed
|
|
1447
1938
|
};
|
|
1448
1939
|
//# sourceMappingURL=index.js.map
|