@optilogic/core 1.2.2 → 1.2.3
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.cjs +662 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +143 -5
- package/dist/index.d.ts +143 -5
- package/dist/index.js +658 -53
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/combobox.tsx +340 -0
- package/src/components/data-grid/DataGrid.tsx +66 -24
- package/src/components/data-grid/components/HeaderCell.tsx +5 -1
- package/src/components/data-grid/hooks/useColumnResize.ts +30 -1
- package/src/components/data-grid/types.ts +5 -0
- package/src/components/multi-select.tsx +375 -0
- package/src/components/select.tsx +18 -2
- package/src/components/tabs.tsx +92 -28
- package/src/index.ts +16 -0
package/dist/index.cjs
CHANGED
|
@@ -388,17 +388,26 @@ var SelectItem = React20__namespace.forwardRef(({ className, children, ...props
|
|
|
388
388
|
{
|
|
389
389
|
ref,
|
|
390
390
|
className: cn(
|
|
391
|
-
"relative flex w-full cursor-default select-none items-
|
|
391
|
+
"relative flex w-full cursor-default select-none items-start rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
392
392
|
className
|
|
393
393
|
),
|
|
394
394
|
...props,
|
|
395
395
|
children: [
|
|
396
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.ItemIndicator, { children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }) }) }),
|
|
396
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center mt-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.ItemIndicator, { children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }) }) }),
|
|
397
397
|
/* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.ItemText, { children })
|
|
398
398
|
]
|
|
399
399
|
}
|
|
400
400
|
));
|
|
401
401
|
SelectItem.displayName = SelectPrimitive__namespace.Item.displayName;
|
|
402
|
+
var SelectItemDescription = React20__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
403
|
+
"span",
|
|
404
|
+
{
|
|
405
|
+
ref,
|
|
406
|
+
className: cn("text-xs text-muted-foreground", className),
|
|
407
|
+
...props
|
|
408
|
+
}
|
|
409
|
+
));
|
|
410
|
+
SelectItemDescription.displayName = "SelectItemDescription";
|
|
402
411
|
var SelectSeparator = React20__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
403
412
|
SelectPrimitive__namespace.Separator,
|
|
404
413
|
{
|
|
@@ -409,41 +418,85 @@ var SelectSeparator = React20__namespace.forwardRef(({ className, ...props }, re
|
|
|
409
418
|
));
|
|
410
419
|
SelectSeparator.displayName = SelectPrimitive__namespace.Separator.displayName;
|
|
411
420
|
var Tabs = TabsPrimitive__namespace.Root;
|
|
412
|
-
var
|
|
421
|
+
var tabsListVariants = classVarianceAuthority.cva(
|
|
422
|
+
"inline-flex h-10 items-center justify-start bg-transparent",
|
|
423
|
+
{
|
|
424
|
+
variants: {
|
|
425
|
+
variant: {
|
|
426
|
+
default: "border-b border-border",
|
|
427
|
+
pill: "gap-1 rounded-lg bg-muted p-1",
|
|
428
|
+
unstyled: ""
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
defaultVariants: {
|
|
432
|
+
variant: "default"
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
);
|
|
436
|
+
var tabsTriggerVariants = classVarianceAuthority.cva(
|
|
437
|
+
[
|
|
438
|
+
"inline-flex items-center justify-center whitespace-nowrap",
|
|
439
|
+
"px-4 py-2.5 text-sm font-medium",
|
|
440
|
+
"transition-colors",
|
|
441
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
442
|
+
"disabled:pointer-events-none disabled:opacity-50"
|
|
443
|
+
],
|
|
444
|
+
{
|
|
445
|
+
variants: {
|
|
446
|
+
variant: {
|
|
447
|
+
default: [
|
|
448
|
+
"-mb-px",
|
|
449
|
+
"border-transparent text-muted-foreground",
|
|
450
|
+
"hover:text-foreground hover:border-muted-foreground/50",
|
|
451
|
+
"data-[state=active]:border-foreground data-[state=active]:text-foreground"
|
|
452
|
+
],
|
|
453
|
+
pill: [
|
|
454
|
+
"rounded-md",
|
|
455
|
+
"text-muted-foreground",
|
|
456
|
+
"hover:text-foreground",
|
|
457
|
+
"data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm"
|
|
458
|
+
],
|
|
459
|
+
unstyled: [
|
|
460
|
+
"text-muted-foreground",
|
|
461
|
+
"hover:text-foreground",
|
|
462
|
+
"data-[state=active]:text-foreground"
|
|
463
|
+
]
|
|
464
|
+
},
|
|
465
|
+
indicatorSize: {
|
|
466
|
+
sm: "border-b",
|
|
467
|
+
default: "border-b-2",
|
|
468
|
+
lg: "border-b-[3px]"
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
compoundVariants: [
|
|
472
|
+
{ variant: "pill", indicatorSize: "sm", className: "border-b-0" },
|
|
473
|
+
{ variant: "pill", indicatorSize: "default", className: "border-b-0" },
|
|
474
|
+
{ variant: "pill", indicatorSize: "lg", className: "border-b-0" },
|
|
475
|
+
{ variant: "unstyled", indicatorSize: "sm", className: "border-b-0" },
|
|
476
|
+
{ variant: "unstyled", indicatorSize: "default", className: "border-b-0" },
|
|
477
|
+
{ variant: "unstyled", indicatorSize: "lg", className: "border-b-0" }
|
|
478
|
+
],
|
|
479
|
+
defaultVariants: {
|
|
480
|
+
variant: "default",
|
|
481
|
+
indicatorSize: "default"
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
);
|
|
485
|
+
var TabsList = React20__namespace.forwardRef(({ className, variant, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
413
486
|
TabsPrimitive__namespace.List,
|
|
414
487
|
{
|
|
415
488
|
ref,
|
|
416
|
-
className: cn(
|
|
417
|
-
"inline-flex h-10 items-center justify-start",
|
|
418
|
-
"border-b border-border",
|
|
419
|
-
"bg-transparent",
|
|
420
|
-
className
|
|
421
|
-
),
|
|
489
|
+
className: cn(tabsListVariants({ variant }), className),
|
|
422
490
|
...props
|
|
423
491
|
}
|
|
424
492
|
));
|
|
425
493
|
TabsList.displayName = TabsPrimitive__namespace.List.displayName;
|
|
426
|
-
var TabsTrigger = React20__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
494
|
+
var TabsTrigger = React20__namespace.forwardRef(({ className, variant, indicatorSize, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
427
495
|
TabsPrimitive__namespace.Trigger,
|
|
428
496
|
{
|
|
429
497
|
ref,
|
|
430
498
|
className: cn(
|
|
431
|
-
|
|
432
|
-
"inline-flex items-center justify-center whitespace-nowrap",
|
|
433
|
-
"px-4 py-2.5 text-sm font-medium",
|
|
434
|
-
"transition-colors",
|
|
435
|
-
// Border-bottom indicator style
|
|
436
|
-
"border-b-2 -mb-px",
|
|
437
|
-
// Default state
|
|
438
|
-
"border-transparent text-muted-foreground",
|
|
439
|
-
// Hover state
|
|
440
|
-
"hover:text-foreground hover:border-muted-foreground/50",
|
|
441
|
-
// Active/selected state
|
|
442
|
-
"data-[state=active]:border-foreground data-[state=active]:text-foreground",
|
|
443
|
-
// Focus styles
|
|
444
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
445
|
-
// Disabled styles
|
|
446
|
-
"disabled:pointer-events-none disabled:opacity-50",
|
|
499
|
+
tabsTriggerVariants({ variant, indicatorSize }),
|
|
447
500
|
className
|
|
448
501
|
),
|
|
449
502
|
...props
|
|
@@ -456,7 +509,6 @@ var TabsContent = React20__namespace.forwardRef(({ className, ...props }, ref) =
|
|
|
456
509
|
ref,
|
|
457
510
|
className: cn(
|
|
458
511
|
"mt-2",
|
|
459
|
-
// Focus styles
|
|
460
512
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
461
513
|
className
|
|
462
514
|
),
|
|
@@ -3419,6 +3471,7 @@ function HeaderCell({
|
|
|
3419
3471
|
sorting,
|
|
3420
3472
|
filter,
|
|
3421
3473
|
isResizable,
|
|
3474
|
+
fillWidth,
|
|
3422
3475
|
onSort,
|
|
3423
3476
|
onFilterChange,
|
|
3424
3477
|
onResizeMouseDown,
|
|
@@ -3462,7 +3515,8 @@ function HeaderCell({
|
|
|
3462
3515
|
"div",
|
|
3463
3516
|
{
|
|
3464
3517
|
className: cn(
|
|
3465
|
-
"relative
|
|
3518
|
+
"relative border-r border-border last:border-r-0",
|
|
3519
|
+
!fillWidth && "flex-shrink-0",
|
|
3466
3520
|
"bg-muted select-none",
|
|
3467
3521
|
isResizing && "bg-accent/20"
|
|
3468
3522
|
),
|
|
@@ -4402,11 +4456,15 @@ function useColumnResizeManager(options) {
|
|
|
4402
4456
|
resizableColumns,
|
|
4403
4457
|
onColumnResize,
|
|
4404
4458
|
onColumnResizeStart,
|
|
4405
|
-
onColumnResizeEnd
|
|
4459
|
+
onColumnResizeEnd,
|
|
4460
|
+
fillWidth,
|
|
4461
|
+
containerWidth,
|
|
4462
|
+
effectiveColumnWidths
|
|
4406
4463
|
} = options;
|
|
4407
4464
|
const [resizingColumn, setResizingColumn] = React20.useState(null);
|
|
4408
4465
|
const startXRef = React20.useRef(0);
|
|
4409
4466
|
const startWidthRef = React20.useRef(0);
|
|
4467
|
+
const startEffectiveWidthsRef = React20.useRef({});
|
|
4410
4468
|
const getColumn = React20.useCallback(
|
|
4411
4469
|
(columnKey) => {
|
|
4412
4470
|
return columns.find((c) => c.key === columnKey);
|
|
@@ -4430,9 +4488,20 @@ function useColumnResizeManager(options) {
|
|
|
4430
4488
|
resizingColumn,
|
|
4431
4489
|
startWidthRef.current + deltaX
|
|
4432
4490
|
);
|
|
4491
|
+
if (fillWidth && containerWidth) {
|
|
4492
|
+
const otherCols = columns.filter((c) => c.key !== resizingColumn);
|
|
4493
|
+
const startEffective = startEffectiveWidthsRef.current;
|
|
4494
|
+
const otherTotal = otherCols.reduce((s, c) => s + (startEffective[c.key] || 0), 0);
|
|
4495
|
+
const remaining = containerWidth - newWidth;
|
|
4496
|
+
for (const col of otherCols) {
|
|
4497
|
+
const proportion = (startEffective[col.key] || 0) / otherTotal;
|
|
4498
|
+
const adjusted = clampWidth(col.key, Math.floor(remaining * proportion));
|
|
4499
|
+
onColumnResize(col.key, adjusted);
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4433
4502
|
onColumnResize(resizingColumn, newWidth);
|
|
4434
4503
|
},
|
|
4435
|
-
[resizingColumn, clampWidth, onColumnResize]
|
|
4504
|
+
[resizingColumn, clampWidth, onColumnResize, fillWidth, containerWidth, columns]
|
|
4436
4505
|
);
|
|
4437
4506
|
const handleMouseUp = React20.useCallback(() => {
|
|
4438
4507
|
if (!resizingColumn) return;
|
|
@@ -4466,6 +4535,9 @@ function useColumnResizeManager(options) {
|
|
|
4466
4535
|
document.body.style.userSelect = "none";
|
|
4467
4536
|
document.body.style.cursor = "col-resize";
|
|
4468
4537
|
onColumnResizeStart?.(columnKey);
|
|
4538
|
+
if (fillWidth && effectiveColumnWidths) {
|
|
4539
|
+
startEffectiveWidthsRef.current = { ...effectiveColumnWidths };
|
|
4540
|
+
}
|
|
4469
4541
|
};
|
|
4470
4542
|
const handleDoubleClick = (event) => {
|
|
4471
4543
|
if (!isResizable) return;
|
|
@@ -4491,7 +4563,9 @@ function useColumnResizeManager(options) {
|
|
|
4491
4563
|
resizingColumn,
|
|
4492
4564
|
onColumnResize,
|
|
4493
4565
|
onColumnResizeStart,
|
|
4494
|
-
onColumnResizeEnd
|
|
4566
|
+
onColumnResizeEnd,
|
|
4567
|
+
fillWidth,
|
|
4568
|
+
effectiveColumnWidths
|
|
4495
4569
|
]
|
|
4496
4570
|
);
|
|
4497
4571
|
return {
|
|
@@ -4716,6 +4790,7 @@ function DataGrid({
|
|
|
4716
4790
|
tooltipMinLength = 30,
|
|
4717
4791
|
enableKeyboardNavigation = true,
|
|
4718
4792
|
showColumnBorders = true,
|
|
4793
|
+
fillWidth,
|
|
4719
4794
|
enableInternalSorting = true,
|
|
4720
4795
|
enableInternalFiltering = true,
|
|
4721
4796
|
// Controlled sorting
|
|
@@ -4770,6 +4845,16 @@ function DataGrid({
|
|
|
4770
4845
|
const headerRef = React20__namespace.useRef(null);
|
|
4771
4846
|
const [headerHeight, setHeaderHeight] = React20__namespace.useState(40);
|
|
4772
4847
|
const [hoveredCell, setHoveredCell] = React20__namespace.useState(null);
|
|
4848
|
+
const [containerWidth, setContainerWidth] = React20__namespace.useState(0);
|
|
4849
|
+
React20__namespace.useLayoutEffect(() => {
|
|
4850
|
+
if (!fillWidth || !parentRef.current) return;
|
|
4851
|
+
const observer = new ResizeObserver((entries) => {
|
|
4852
|
+
const w = entries[0]?.contentRect.width;
|
|
4853
|
+
if (w && w > 0) setContainerWidth(w);
|
|
4854
|
+
});
|
|
4855
|
+
observer.observe(parentRef.current);
|
|
4856
|
+
return () => observer.disconnect();
|
|
4857
|
+
}, [fillWidth]);
|
|
4773
4858
|
const visibleColumns = React20__namespace.useMemo(
|
|
4774
4859
|
() => columns.filter((col) => !col.hidden),
|
|
4775
4860
|
[columns]
|
|
@@ -4823,14 +4908,6 @@ function DataGrid({
|
|
|
4823
4908
|
visibleColumns
|
|
4824
4909
|
]);
|
|
4825
4910
|
processedDataRef.current = processedData;
|
|
4826
|
-
const { resizingColumn, getResizeProps } = useColumnResizeManager({
|
|
4827
|
-
columns: visibleColumns,
|
|
4828
|
-
columnWidths: state.columnWidths,
|
|
4829
|
-
resizableColumns,
|
|
4830
|
-
onColumnResize: actions.setColumnWidth,
|
|
4831
|
-
onColumnResizeStart,
|
|
4832
|
-
onColumnResizeEnd
|
|
4833
|
-
});
|
|
4834
4911
|
const shouldVirtualize = virtualized ?? processedData.length > 100;
|
|
4835
4912
|
React20__namespace.useLayoutEffect(() => {
|
|
4836
4913
|
if (headerRef.current && shouldVirtualize) {
|
|
@@ -4844,12 +4921,38 @@ function DataGrid({
|
|
|
4844
4921
|
overscan: DEFAULT_OVERSCAN,
|
|
4845
4922
|
enabled: shouldVirtualize
|
|
4846
4923
|
});
|
|
4847
|
-
const tableWidth = React20__namespace.useMemo(() => {
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4924
|
+
const { tableWidth, effectiveColumnWidths } = React20__namespace.useMemo(() => {
|
|
4925
|
+
const rawWidths = {};
|
|
4926
|
+
let rawTotal = 0;
|
|
4927
|
+
for (const col of visibleColumns) {
|
|
4928
|
+
const w = state.columnWidths[col.key] || col.width || estimateColumnWidth(col);
|
|
4929
|
+
rawWidths[col.key] = w;
|
|
4930
|
+
rawTotal += w;
|
|
4931
|
+
}
|
|
4932
|
+
if (!fillWidth || !containerWidth || rawTotal >= containerWidth) {
|
|
4933
|
+
return { tableWidth: rawTotal, effectiveColumnWidths: rawWidths };
|
|
4934
|
+
}
|
|
4935
|
+
const scale = containerWidth / rawTotal;
|
|
4936
|
+
const scaled = {};
|
|
4937
|
+
for (const col of visibleColumns) {
|
|
4938
|
+
let w = Math.floor(rawWidths[col.key] * scale);
|
|
4939
|
+
if (col.minWidth) w = Math.max(w, col.minWidth);
|
|
4940
|
+
if (col.maxWidth) w = Math.min(w, col.maxWidth);
|
|
4941
|
+
scaled[col.key] = w;
|
|
4942
|
+
}
|
|
4943
|
+
return { tableWidth: containerWidth, effectiveColumnWidths: scaled };
|
|
4944
|
+
}, [visibleColumns, state.columnWidths, fillWidth, containerWidth]);
|
|
4945
|
+
const { resizingColumn, getResizeProps } = useColumnResizeManager({
|
|
4946
|
+
columns: visibleColumns,
|
|
4947
|
+
columnWidths: state.columnWidths,
|
|
4948
|
+
resizableColumns,
|
|
4949
|
+
onColumnResize: actions.setColumnWidth,
|
|
4950
|
+
onColumnResizeStart,
|
|
4951
|
+
onColumnResizeEnd,
|
|
4952
|
+
fillWidth,
|
|
4953
|
+
containerWidth,
|
|
4954
|
+
effectiveColumnWidths
|
|
4955
|
+
});
|
|
4853
4956
|
const visibleRowCount = React20__namespace.useMemo(() => {
|
|
4854
4957
|
if (!parentRef.current) return 10;
|
|
4855
4958
|
return Math.floor(parentRef.current.clientHeight / DEFAULT_ROW_HEIGHT);
|
|
@@ -5081,7 +5184,7 @@ function DataGrid({
|
|
|
5081
5184
|
},
|
|
5082
5185
|
role: "row",
|
|
5083
5186
|
children: visibleColumns.map((column, colIndex) => {
|
|
5084
|
-
const width =
|
|
5187
|
+
const width = effectiveColumnWidths[column.key];
|
|
5085
5188
|
const resizeProps = getResizeProps(column.key);
|
|
5086
5189
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5087
5190
|
HeaderCell,
|
|
@@ -5092,6 +5195,7 @@ function DataGrid({
|
|
|
5092
5195
|
sorting: getColumnSort(column.key),
|
|
5093
5196
|
filter: getColumnFilter(column.key),
|
|
5094
5197
|
isResizable: resizableColumns && column.resizable !== false,
|
|
5198
|
+
fillWidth,
|
|
5095
5199
|
onSort: () => handleSort(column.key),
|
|
5096
5200
|
onFilterChange: (filter) => handleFilterChange(column.key, filter),
|
|
5097
5201
|
onResizeMouseDown: resizeProps.handleMouseDown,
|
|
@@ -5140,7 +5244,7 @@ function DataGrid({
|
|
|
5140
5244
|
"aria-rowindex": virtualRow.index + 1,
|
|
5141
5245
|
"aria-selected": isSelected,
|
|
5142
5246
|
children: visibleColumns.map((column, colIndex) => {
|
|
5143
|
-
const width =
|
|
5247
|
+
const width = effectiveColumnWidths[column.key];
|
|
5144
5248
|
const isEditingThisCell = state.editingCell?.rowIndex === virtualRow.index && state.editingCell?.columnKey === column.key;
|
|
5145
5249
|
const isFocused = state.focusedCell?.rowIndex === virtualRow.index && state.focusedCell?.columnKey === column.key;
|
|
5146
5250
|
const cellContent = renderCell(
|
|
@@ -5155,7 +5259,8 @@ function DataGrid({
|
|
|
5155
5259
|
"div",
|
|
5156
5260
|
{
|
|
5157
5261
|
className: cn(
|
|
5158
|
-
"
|
|
5262
|
+
"px-3 py-2 text-sm overflow-hidden",
|
|
5263
|
+
!fillWidth && "flex-shrink-0",
|
|
5159
5264
|
showColumnBorders && "border-r border-border last:border-r-0",
|
|
5160
5265
|
isFocused && !isEditingThisCell && "ring-2 ring-inset ring-primary",
|
|
5161
5266
|
isEditingThisCell && "ring-2 ring-inset ring-primary bg-background",
|
|
@@ -5267,7 +5372,7 @@ function DataGrid({
|
|
|
5267
5372
|
}
|
|
5268
5373
|
)
|
|
5269
5374
|
] }) }),
|
|
5270
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-auto min-h-0", children: [
|
|
5375
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { ref: parentRef, className: "flex-1 overflow-auto min-h-0", children: [
|
|
5271
5376
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5272
5377
|
"div",
|
|
5273
5378
|
{
|
|
@@ -5278,7 +5383,7 @@ function DataGrid({
|
|
|
5278
5383
|
style: { width: tableWidth ? `${tableWidth}px` : "100%" },
|
|
5279
5384
|
role: "row",
|
|
5280
5385
|
children: visibleColumns.map((column, colIndex) => {
|
|
5281
|
-
const width =
|
|
5386
|
+
const width = effectiveColumnWidths[column.key];
|
|
5282
5387
|
const resizeProps = getResizeProps(column.key);
|
|
5283
5388
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5284
5389
|
HeaderCell,
|
|
@@ -5289,6 +5394,7 @@ function DataGrid({
|
|
|
5289
5394
|
sorting: getColumnSort(column.key),
|
|
5290
5395
|
filter: getColumnFilter(column.key),
|
|
5291
5396
|
isResizable: resizableColumns && column.resizable !== false,
|
|
5397
|
+
fillWidth,
|
|
5292
5398
|
onSort: () => handleSort(column.key),
|
|
5293
5399
|
onFilterChange: (filter) => handleFilterChange(column.key, filter),
|
|
5294
5400
|
onResizeMouseDown: resizeProps.handleMouseDown,
|
|
@@ -5318,14 +5424,15 @@ function DataGrid({
|
|
|
5318
5424
|
onDoubleClick: () => onRowDoubleClick?.(row, rowIndex),
|
|
5319
5425
|
role: "row",
|
|
5320
5426
|
children: visibleColumns.map((column) => {
|
|
5321
|
-
const width =
|
|
5427
|
+
const width = effectiveColumnWidths[column.key];
|
|
5322
5428
|
const isEditingThisCell = state.editingCell?.rowIndex === rowIndex && state.editingCell?.columnKey === column.key;
|
|
5323
5429
|
const isFocused = state.focusedCell?.rowIndex === rowIndex && state.focusedCell?.columnKey === column.key;
|
|
5324
5430
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5325
5431
|
"div",
|
|
5326
5432
|
{
|
|
5327
5433
|
className: cn(
|
|
5328
|
-
"
|
|
5434
|
+
"px-3 py-2 text-sm overflow-hidden",
|
|
5435
|
+
!fillWidth && "flex-shrink-0",
|
|
5329
5436
|
showColumnBorders && "border-r border-border last:border-r-0",
|
|
5330
5437
|
isFocused && !isEditingThisCell && "ring-2 ring-inset ring-primary",
|
|
5331
5438
|
isEditingThisCell && "ring-2 ring-inset ring-primary bg-background",
|
|
@@ -5650,6 +5757,504 @@ function Autocomplete({
|
|
|
5650
5757
|
)
|
|
5651
5758
|
] });
|
|
5652
5759
|
}
|
|
5760
|
+
function MultiSelect({
|
|
5761
|
+
options,
|
|
5762
|
+
value,
|
|
5763
|
+
onChange,
|
|
5764
|
+
placeholder = "Select items...",
|
|
5765
|
+
searchPlaceholder = "Search...",
|
|
5766
|
+
emptyText = "No options found.",
|
|
5767
|
+
disabled = false,
|
|
5768
|
+
className,
|
|
5769
|
+
clearable = true,
|
|
5770
|
+
maxDisplayItems = 3,
|
|
5771
|
+
showSelectAll = false,
|
|
5772
|
+
selectAllLabel = "Select all"
|
|
5773
|
+
}) {
|
|
5774
|
+
const [open, setOpen] = React20__namespace.useState(false);
|
|
5775
|
+
const [search, setSearch] = React20__namespace.useState("");
|
|
5776
|
+
const inputRef = React20__namespace.useRef(null);
|
|
5777
|
+
const safeOptions = options ?? [];
|
|
5778
|
+
const safeValue = value ?? [];
|
|
5779
|
+
const selectedSet = React20__namespace.useMemo(() => new Set(safeValue), [safeValue]);
|
|
5780
|
+
const filteredOptions = React20__namespace.useMemo(() => {
|
|
5781
|
+
if (!search.trim()) return safeOptions;
|
|
5782
|
+
const searchLower = search.toLowerCase();
|
|
5783
|
+
return safeOptions.filter(
|
|
5784
|
+
(opt) => opt.label.toLowerCase().includes(searchLower) || opt.description?.toLowerCase().includes(searchLower)
|
|
5785
|
+
);
|
|
5786
|
+
}, [safeOptions, search]);
|
|
5787
|
+
const groupedOptions = React20__namespace.useMemo(() => {
|
|
5788
|
+
const groups = {};
|
|
5789
|
+
const ungrouped = [];
|
|
5790
|
+
filteredOptions.forEach((opt) => {
|
|
5791
|
+
if (opt.group) {
|
|
5792
|
+
if (!groups[opt.group]) groups[opt.group] = [];
|
|
5793
|
+
groups[opt.group].push(opt);
|
|
5794
|
+
} else {
|
|
5795
|
+
ungrouped.push(opt);
|
|
5796
|
+
}
|
|
5797
|
+
});
|
|
5798
|
+
return { groups, ungrouped };
|
|
5799
|
+
}, [filteredOptions]);
|
|
5800
|
+
const hasGroups = Object.keys(groupedOptions.groups).length > 0;
|
|
5801
|
+
const selectableFiltered = React20__namespace.useMemo(
|
|
5802
|
+
() => filteredOptions.filter((opt) => !opt.disabled),
|
|
5803
|
+
[filteredOptions]
|
|
5804
|
+
);
|
|
5805
|
+
const allFilteredSelected = React20__namespace.useMemo(
|
|
5806
|
+
() => selectableFiltered.length > 0 && selectableFiltered.every((opt) => selectedSet.has(opt.value)),
|
|
5807
|
+
[selectableFiltered, selectedSet]
|
|
5808
|
+
);
|
|
5809
|
+
const someFilteredSelected = React20__namespace.useMemo(
|
|
5810
|
+
() => !allFilteredSelected && selectableFiltered.some((opt) => selectedSet.has(opt.value)),
|
|
5811
|
+
[selectableFiltered, selectedSet, allFilteredSelected]
|
|
5812
|
+
);
|
|
5813
|
+
const handleToggle = React20__namespace.useCallback(
|
|
5814
|
+
(optionValue) => {
|
|
5815
|
+
const next = selectedSet.has(optionValue) ? safeValue.filter((v) => v !== optionValue) : [...safeValue, optionValue];
|
|
5816
|
+
onChange?.(next);
|
|
5817
|
+
},
|
|
5818
|
+
[onChange, safeValue, selectedSet]
|
|
5819
|
+
);
|
|
5820
|
+
const handleRemove = React20__namespace.useCallback(
|
|
5821
|
+
(optionValue, e) => {
|
|
5822
|
+
e.stopPropagation();
|
|
5823
|
+
onChange?.(safeValue.filter((v) => v !== optionValue));
|
|
5824
|
+
},
|
|
5825
|
+
[onChange, safeValue]
|
|
5826
|
+
);
|
|
5827
|
+
const handleClearAll = React20__namespace.useCallback(
|
|
5828
|
+
(e) => {
|
|
5829
|
+
e.stopPropagation();
|
|
5830
|
+
onChange?.([]);
|
|
5831
|
+
},
|
|
5832
|
+
[onChange]
|
|
5833
|
+
);
|
|
5834
|
+
const handleSelectAll = React20__namespace.useCallback(() => {
|
|
5835
|
+
if (allFilteredSelected) {
|
|
5836
|
+
const filteredValues = new Set(selectableFiltered.map((o) => o.value));
|
|
5837
|
+
onChange?.(safeValue.filter((v) => !filteredValues.has(v)));
|
|
5838
|
+
} else {
|
|
5839
|
+
const existing = new Set(safeValue);
|
|
5840
|
+
const next = [...safeValue];
|
|
5841
|
+
for (const opt of selectableFiltered) {
|
|
5842
|
+
if (!existing.has(opt.value)) {
|
|
5843
|
+
next.push(opt.value);
|
|
5844
|
+
}
|
|
5845
|
+
}
|
|
5846
|
+
onChange?.(next);
|
|
5847
|
+
}
|
|
5848
|
+
}, [allFilteredSelected, selectableFiltered, safeValue, onChange]);
|
|
5849
|
+
React20__namespace.useEffect(() => {
|
|
5850
|
+
if (open) {
|
|
5851
|
+
const timeout = setTimeout(() => inputRef.current?.focus(), 0);
|
|
5852
|
+
return () => clearTimeout(timeout);
|
|
5853
|
+
} else {
|
|
5854
|
+
setSearch("");
|
|
5855
|
+
}
|
|
5856
|
+
}, [open]);
|
|
5857
|
+
const handleKeyDown = React20__namespace.useCallback((e) => {
|
|
5858
|
+
if (e.key === "Escape") setOpen(false);
|
|
5859
|
+
}, []);
|
|
5860
|
+
const selectedLabels = React20__namespace.useMemo(
|
|
5861
|
+
() => safeValue.map((v) => safeOptions.find((o) => o.value === v)?.label ?? v).slice(0, maxDisplayItems),
|
|
5862
|
+
[safeValue, safeOptions, maxDisplayItems]
|
|
5863
|
+
);
|
|
5864
|
+
const overflow = safeValue.length - maxDisplayItems;
|
|
5865
|
+
const isSearching = search.trim().length > 0;
|
|
5866
|
+
const renderOption = (option) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5867
|
+
"button",
|
|
5868
|
+
{
|
|
5869
|
+
type: "button",
|
|
5870
|
+
disabled: option.disabled,
|
|
5871
|
+
onClick: () => handleToggle(option.value),
|
|
5872
|
+
className: cn(
|
|
5873
|
+
"relative flex w-full cursor-pointer select-none items-start gap-2 rounded-sm px-2 py-1.5 text-sm outline-none",
|
|
5874
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
5875
|
+
option.disabled && "pointer-events-none opacity-50"
|
|
5876
|
+
),
|
|
5877
|
+
children: [
|
|
5878
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5879
|
+
Checkbox,
|
|
5880
|
+
{
|
|
5881
|
+
checked: selectedSet.has(option.value),
|
|
5882
|
+
tabIndex: -1,
|
|
5883
|
+
className: "mt-0.5 pointer-events-none"
|
|
5884
|
+
}
|
|
5885
|
+
),
|
|
5886
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
5887
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate", children: option.label }),
|
|
5888
|
+
option.description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground truncate", children: option.description })
|
|
5889
|
+
] })
|
|
5890
|
+
]
|
|
5891
|
+
},
|
|
5892
|
+
option.value
|
|
5893
|
+
);
|
|
5894
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: setOpen, children: [
|
|
5895
|
+
/* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, disabled, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5896
|
+
"button",
|
|
5897
|
+
{
|
|
5898
|
+
type: "button",
|
|
5899
|
+
role: "combobox",
|
|
5900
|
+
"aria-expanded": open,
|
|
5901
|
+
"aria-haspopup": "listbox",
|
|
5902
|
+
disabled,
|
|
5903
|
+
className: cn(
|
|
5904
|
+
"flex min-h-9 w-full items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-1.5 text-sm shadow-sm ring-offset-background",
|
|
5905
|
+
"hover:border-input-hover",
|
|
5906
|
+
"focus:outline-none focus:ring-1 focus:ring-ring",
|
|
5907
|
+
"disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:border-input",
|
|
5908
|
+
className
|
|
5909
|
+
),
|
|
5910
|
+
children: [
|
|
5911
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-1 flex-wrap gap-1 items-center min-w-0", children: safeValue.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: placeholder }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5912
|
+
selectedLabels.map((label, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5913
|
+
"span",
|
|
5914
|
+
{
|
|
5915
|
+
className: "inline-flex items-center gap-0.5 rounded-sm bg-accent px-1.5 py-0.5 text-xs font-medium text-accent-foreground",
|
|
5916
|
+
children: [
|
|
5917
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate max-w-[100px]", children: label }),
|
|
5918
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5919
|
+
"span",
|
|
5920
|
+
{
|
|
5921
|
+
role: "button",
|
|
5922
|
+
tabIndex: -1,
|
|
5923
|
+
onClick: (e) => handleRemove(safeValue[i], e),
|
|
5924
|
+
className: "rounded-sm hover:bg-foreground/10 p-0.5",
|
|
5925
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3 w-3" })
|
|
5926
|
+
}
|
|
5927
|
+
)
|
|
5928
|
+
]
|
|
5929
|
+
},
|
|
5930
|
+
safeValue[i]
|
|
5931
|
+
)),
|
|
5932
|
+
overflow > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
5933
|
+
"+",
|
|
5934
|
+
overflow
|
|
5935
|
+
] })
|
|
5936
|
+
] }) }),
|
|
5937
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-shrink-0", children: [
|
|
5938
|
+
clearable && safeValue.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5939
|
+
"span",
|
|
5940
|
+
{
|
|
5941
|
+
role: "button",
|
|
5942
|
+
tabIndex: -1,
|
|
5943
|
+
onClick: handleClearAll,
|
|
5944
|
+
className: "rounded-sm hover:bg-muted p-0.5",
|
|
5945
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
5946
|
+
}
|
|
5947
|
+
),
|
|
5948
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4 opacity-50" })
|
|
5949
|
+
] })
|
|
5950
|
+
]
|
|
5951
|
+
}
|
|
5952
|
+
) }),
|
|
5953
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5954
|
+
PopoverContent,
|
|
5955
|
+
{
|
|
5956
|
+
className: "w-[--radix-popover-trigger-width] p-0",
|
|
5957
|
+
align: "start",
|
|
5958
|
+
sideOffset: 4,
|
|
5959
|
+
onKeyDown: handleKeyDown,
|
|
5960
|
+
children: [
|
|
5961
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center border-b border-border px-3", children: [
|
|
5962
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5963
|
+
"input",
|
|
5964
|
+
{
|
|
5965
|
+
ref: inputRef,
|
|
5966
|
+
type: "text",
|
|
5967
|
+
value: search,
|
|
5968
|
+
onChange: (e) => setSearch(e.target.value),
|
|
5969
|
+
placeholder: searchPlaceholder,
|
|
5970
|
+
className: "flex h-9 w-full bg-transparent py-2 text-sm outline-none placeholder:text-muted-foreground"
|
|
5971
|
+
}
|
|
5972
|
+
),
|
|
5973
|
+
search && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5974
|
+
"button",
|
|
5975
|
+
{
|
|
5976
|
+
type: "button",
|
|
5977
|
+
onClick: () => setSearch(""),
|
|
5978
|
+
className: "p-1 hover:bg-muted rounded-sm",
|
|
5979
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
5980
|
+
}
|
|
5981
|
+
)
|
|
5982
|
+
] }),
|
|
5983
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-h-[300px] overflow-y-auto p-1", children: [
|
|
5984
|
+
showSelectAll && selectableFiltered.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5985
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5986
|
+
"button",
|
|
5987
|
+
{
|
|
5988
|
+
type: "button",
|
|
5989
|
+
onClick: handleSelectAll,
|
|
5990
|
+
className: cn(
|
|
5991
|
+
"relative flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none",
|
|
5992
|
+
"hover:bg-accent hover:text-accent-foreground"
|
|
5993
|
+
),
|
|
5994
|
+
children: [
|
|
5995
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5996
|
+
Checkbox,
|
|
5997
|
+
{
|
|
5998
|
+
checked: allFilteredSelected ? true : someFilteredSelected ? "indeterminate" : false,
|
|
5999
|
+
tabIndex: -1,
|
|
6000
|
+
className: "pointer-events-none"
|
|
6001
|
+
}
|
|
6002
|
+
),
|
|
6003
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: isSearching ? `${selectAllLabel} (filtered)` : selectAllLabel })
|
|
6004
|
+
]
|
|
6005
|
+
}
|
|
6006
|
+
),
|
|
6007
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "-mx-1 my-1 h-px bg-border" })
|
|
6008
|
+
] }),
|
|
6009
|
+
filteredOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-6 text-center text-sm text-muted-foreground", children: emptyText }) : hasGroups ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
6010
|
+
groupedOptions.ungrouped.map(renderOption),
|
|
6011
|
+
Object.entries(groupedOptions.groups).map(([group, opts]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
6012
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1.5 text-xs font-semibold text-muted-foreground", children: group }),
|
|
6013
|
+
opts.map(renderOption)
|
|
6014
|
+
] }, group))
|
|
6015
|
+
] }) : filteredOptions.map(renderOption)
|
|
6016
|
+
] })
|
|
6017
|
+
]
|
|
6018
|
+
}
|
|
6019
|
+
)
|
|
6020
|
+
] });
|
|
6021
|
+
}
|
|
6022
|
+
MultiSelect.displayName = "MultiSelect";
|
|
6023
|
+
function Combobox({
|
|
6024
|
+
options,
|
|
6025
|
+
value,
|
|
6026
|
+
onChange,
|
|
6027
|
+
onInputChange,
|
|
6028
|
+
placeholder = "Type or select...",
|
|
6029
|
+
emptyText = "No options found.",
|
|
6030
|
+
disabled = false,
|
|
6031
|
+
className,
|
|
6032
|
+
clearable = false,
|
|
6033
|
+
allowCustomValue = true
|
|
6034
|
+
}) {
|
|
6035
|
+
const [open, setOpen] = React20__namespace.useState(false);
|
|
6036
|
+
const [inputValue, setInputValue] = React20__namespace.useState("");
|
|
6037
|
+
const inputRef = React20__namespace.useRef(null);
|
|
6038
|
+
const wrapperRef = React20__namespace.useRef(null);
|
|
6039
|
+
const selectedOption = React20__namespace.useMemo(
|
|
6040
|
+
() => options.find((opt) => opt.value === value),
|
|
6041
|
+
[options, value]
|
|
6042
|
+
);
|
|
6043
|
+
React20__namespace.useEffect(() => {
|
|
6044
|
+
if (!open) {
|
|
6045
|
+
setInputValue(selectedOption?.label ?? value ?? "");
|
|
6046
|
+
}
|
|
6047
|
+
}, [value, selectedOption, open]);
|
|
6048
|
+
const filteredOptions = React20__namespace.useMemo(() => {
|
|
6049
|
+
if (!inputValue.trim()) return options;
|
|
6050
|
+
const searchLower = inputValue.toLowerCase();
|
|
6051
|
+
return options.filter(
|
|
6052
|
+
(opt) => opt.label.toLowerCase().includes(searchLower) || opt.description?.toLowerCase().includes(searchLower)
|
|
6053
|
+
);
|
|
6054
|
+
}, [options, inputValue]);
|
|
6055
|
+
const groupedOptions = React20__namespace.useMemo(() => {
|
|
6056
|
+
const groups = {};
|
|
6057
|
+
const ungrouped = [];
|
|
6058
|
+
filteredOptions.forEach((opt) => {
|
|
6059
|
+
if (opt.group) {
|
|
6060
|
+
if (!groups[opt.group]) groups[opt.group] = [];
|
|
6061
|
+
groups[opt.group].push(opt);
|
|
6062
|
+
} else {
|
|
6063
|
+
ungrouped.push(opt);
|
|
6064
|
+
}
|
|
6065
|
+
});
|
|
6066
|
+
return { groups, ungrouped };
|
|
6067
|
+
}, [filteredOptions]);
|
|
6068
|
+
const hasGroups = Object.keys(groupedOptions.groups).length > 0;
|
|
6069
|
+
const handleInputChange = React20__namespace.useCallback(
|
|
6070
|
+
(e) => {
|
|
6071
|
+
const newValue = e.target.value;
|
|
6072
|
+
setInputValue(newValue);
|
|
6073
|
+
onInputChange?.(newValue);
|
|
6074
|
+
if (!open) setOpen(true);
|
|
6075
|
+
},
|
|
6076
|
+
[onInputChange, open]
|
|
6077
|
+
);
|
|
6078
|
+
const handleSelect = React20__namespace.useCallback(
|
|
6079
|
+
(optionValue) => {
|
|
6080
|
+
const option = options.find((o) => o.value === optionValue);
|
|
6081
|
+
onChange?.(optionValue);
|
|
6082
|
+
setInputValue(option?.label ?? optionValue);
|
|
6083
|
+
setOpen(false);
|
|
6084
|
+
},
|
|
6085
|
+
[onChange, options]
|
|
6086
|
+
);
|
|
6087
|
+
const handleClear = React20__namespace.useCallback(
|
|
6088
|
+
(e) => {
|
|
6089
|
+
e.stopPropagation();
|
|
6090
|
+
e.preventDefault();
|
|
6091
|
+
onChange?.(void 0);
|
|
6092
|
+
setInputValue("");
|
|
6093
|
+
inputRef.current?.focus();
|
|
6094
|
+
},
|
|
6095
|
+
[onChange]
|
|
6096
|
+
);
|
|
6097
|
+
const handleFocus = React20__namespace.useCallback(() => {
|
|
6098
|
+
setOpen(true);
|
|
6099
|
+
inputRef.current?.select();
|
|
6100
|
+
}, []);
|
|
6101
|
+
const handleBlur = React20__namespace.useCallback(() => {
|
|
6102
|
+
setTimeout(() => {
|
|
6103
|
+
if (!wrapperRef.current?.contains(document.activeElement)) {
|
|
6104
|
+
setOpen(false);
|
|
6105
|
+
}
|
|
6106
|
+
if (allowCustomValue && inputValue.trim()) {
|
|
6107
|
+
const matchingOption = options.find(
|
|
6108
|
+
(opt) => opt.label.toLowerCase() === inputValue.toLowerCase()
|
|
6109
|
+
);
|
|
6110
|
+
if (matchingOption) {
|
|
6111
|
+
onChange?.(matchingOption.value);
|
|
6112
|
+
setInputValue(matchingOption.label);
|
|
6113
|
+
} else {
|
|
6114
|
+
onChange?.(inputValue.trim());
|
|
6115
|
+
}
|
|
6116
|
+
} else if (!allowCustomValue) {
|
|
6117
|
+
setInputValue(selectedOption?.label ?? "");
|
|
6118
|
+
}
|
|
6119
|
+
}, 200);
|
|
6120
|
+
}, [allowCustomValue, inputValue, options, onChange, selectedOption]);
|
|
6121
|
+
const handleKeyDown = React20__namespace.useCallback(
|
|
6122
|
+
(e) => {
|
|
6123
|
+
if (e.key === "Escape") {
|
|
6124
|
+
setOpen(false);
|
|
6125
|
+
setInputValue(selectedOption?.label ?? value ?? "");
|
|
6126
|
+
inputRef.current?.blur();
|
|
6127
|
+
} else if (e.key === "Enter" && open) {
|
|
6128
|
+
e.preventDefault();
|
|
6129
|
+
if (filteredOptions.length === 1) {
|
|
6130
|
+
handleSelect(filteredOptions[0].value);
|
|
6131
|
+
} else if (allowCustomValue && inputValue.trim()) {
|
|
6132
|
+
const exactMatch = filteredOptions.find(
|
|
6133
|
+
(opt) => opt.label.toLowerCase() === inputValue.toLowerCase()
|
|
6134
|
+
);
|
|
6135
|
+
if (exactMatch) {
|
|
6136
|
+
handleSelect(exactMatch.value);
|
|
6137
|
+
} else {
|
|
6138
|
+
onChange?.(inputValue.trim());
|
|
6139
|
+
setOpen(false);
|
|
6140
|
+
}
|
|
6141
|
+
}
|
|
6142
|
+
}
|
|
6143
|
+
},
|
|
6144
|
+
[
|
|
6145
|
+
open,
|
|
6146
|
+
filteredOptions,
|
|
6147
|
+
handleSelect,
|
|
6148
|
+
allowCustomValue,
|
|
6149
|
+
inputValue,
|
|
6150
|
+
onChange,
|
|
6151
|
+
selectedOption,
|
|
6152
|
+
value
|
|
6153
|
+
]
|
|
6154
|
+
);
|
|
6155
|
+
const renderOption = (option) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6156
|
+
"button",
|
|
6157
|
+
{
|
|
6158
|
+
type: "button",
|
|
6159
|
+
disabled: option.disabled,
|
|
6160
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
6161
|
+
onClick: () => handleSelect(option.value),
|
|
6162
|
+
className: cn(
|
|
6163
|
+
"relative flex w-full cursor-pointer select-none items-start gap-2 rounded-sm px-2 py-1.5 text-sm outline-none",
|
|
6164
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
6165
|
+
"focus:bg-accent focus:text-accent-foreground",
|
|
6166
|
+
option.disabled && "pointer-events-none opacity-50",
|
|
6167
|
+
value === option.value && "bg-accent/50"
|
|
6168
|
+
),
|
|
6169
|
+
children: [
|
|
6170
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-4 w-4 items-center justify-center flex-shrink-0 mt-0.5", children: value === option.value && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }) }),
|
|
6171
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
6172
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate", children: option.label }),
|
|
6173
|
+
option.description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground truncate", children: option.description })
|
|
6174
|
+
] })
|
|
6175
|
+
]
|
|
6176
|
+
},
|
|
6177
|
+
option.value
|
|
6178
|
+
);
|
|
6179
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: setOpen, children: [
|
|
6180
|
+
/* @__PURE__ */ jsxRuntime.jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6181
|
+
"div",
|
|
6182
|
+
{
|
|
6183
|
+
ref: wrapperRef,
|
|
6184
|
+
className: cn(
|
|
6185
|
+
"flex h-9 w-full items-center gap-2 rounded-md border border-input bg-transparent px-3 text-sm shadow-sm ring-offset-background",
|
|
6186
|
+
"hover:border-input-hover",
|
|
6187
|
+
"focus-within:outline-none focus-within:ring-1 focus-within:ring-ring",
|
|
6188
|
+
disabled && "cursor-not-allowed opacity-50 hover:border-input",
|
|
6189
|
+
className
|
|
6190
|
+
),
|
|
6191
|
+
children: [
|
|
6192
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6193
|
+
"input",
|
|
6194
|
+
{
|
|
6195
|
+
ref: inputRef,
|
|
6196
|
+
type: "text",
|
|
6197
|
+
value: inputValue,
|
|
6198
|
+
onChange: handleInputChange,
|
|
6199
|
+
onFocus: handleFocus,
|
|
6200
|
+
onBlur: handleBlur,
|
|
6201
|
+
onKeyDown: handleKeyDown,
|
|
6202
|
+
placeholder,
|
|
6203
|
+
disabled,
|
|
6204
|
+
className: "flex-1 min-w-0 bg-transparent py-2 outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed",
|
|
6205
|
+
role: "combobox",
|
|
6206
|
+
"aria-expanded": open,
|
|
6207
|
+
"aria-haspopup": "listbox",
|
|
6208
|
+
"aria-autocomplete": "list"
|
|
6209
|
+
}
|
|
6210
|
+
),
|
|
6211
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-shrink-0", children: [
|
|
6212
|
+
clearable && value && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6213
|
+
"span",
|
|
6214
|
+
{
|
|
6215
|
+
role: "button",
|
|
6216
|
+
tabIndex: -1,
|
|
6217
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
6218
|
+
onClick: handleClear,
|
|
6219
|
+
className: "rounded-sm hover:bg-muted p-0.5",
|
|
6220
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
6221
|
+
}
|
|
6222
|
+
),
|
|
6223
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4 opacity-50" })
|
|
6224
|
+
] })
|
|
6225
|
+
]
|
|
6226
|
+
}
|
|
6227
|
+
) }),
|
|
6228
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6229
|
+
PopoverContent,
|
|
6230
|
+
{
|
|
6231
|
+
className: "p-0",
|
|
6232
|
+
style: { width: wrapperRef.current?.offsetWidth },
|
|
6233
|
+
align: "start",
|
|
6234
|
+
sideOffset: 4,
|
|
6235
|
+
onOpenAutoFocus: (e) => e.preventDefault(),
|
|
6236
|
+
onFocusOutside: (e) => {
|
|
6237
|
+
if (wrapperRef.current?.contains(e.target)) {
|
|
6238
|
+
e.preventDefault();
|
|
6239
|
+
}
|
|
6240
|
+
},
|
|
6241
|
+
onInteractOutside: (e) => {
|
|
6242
|
+
if (wrapperRef.current?.contains(e.target)) {
|
|
6243
|
+
e.preventDefault();
|
|
6244
|
+
}
|
|
6245
|
+
},
|
|
6246
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-[300px] overflow-y-auto p-1", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-6 text-center text-sm text-muted-foreground", children: emptyText }) : hasGroups ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
6247
|
+
groupedOptions.ungrouped.map(renderOption),
|
|
6248
|
+
Object.entries(groupedOptions.groups).map(([group, opts]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
6249
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1.5 text-xs font-semibold text-muted-foreground", children: group }),
|
|
6250
|
+
opts.map(renderOption)
|
|
6251
|
+
] }, group))
|
|
6252
|
+
] }) : filteredOptions.map(renderOption) })
|
|
6253
|
+
}
|
|
6254
|
+
)
|
|
6255
|
+
] });
|
|
6256
|
+
}
|
|
6257
|
+
Combobox.displayName = "Combobox";
|
|
5653
6258
|
var iconButtonVariants = classVarianceAuthority.cva(
|
|
5654
6259
|
// Base styles
|
|
5655
6260
|
[
|
|
@@ -7963,6 +8568,7 @@ exports.CellEditor = CellEditor;
|
|
|
7963
8568
|
exports.Checkbox = Checkbox;
|
|
7964
8569
|
exports.Chip = Chip;
|
|
7965
8570
|
exports.CodeRenderer = CodeRenderer;
|
|
8571
|
+
exports.Combobox = Combobox;
|
|
7966
8572
|
exports.ConfirmationModal = ConfirmationModal;
|
|
7967
8573
|
exports.ContextMenu = ContextMenu;
|
|
7968
8574
|
exports.CopyButton = CopyButton;
|
|
@@ -8005,6 +8611,7 @@ exports.MODERN_LIGHT_THEME = MODERN_LIGHT_THEME;
|
|
|
8005
8611
|
exports.MarkdownRenderer = MarkdownRenderer;
|
|
8006
8612
|
exports.Modal = Modal;
|
|
8007
8613
|
exports.ModalButton = ModalButton;
|
|
8614
|
+
exports.MultiSelect = MultiSelect;
|
|
8008
8615
|
exports.OPTILOGIC_DARK_THEME = OPTILOGIC_DARK_THEME;
|
|
8009
8616
|
exports.OPTILOGIC_LEGACY_THEME = OPTILOGIC_LEGACY_THEME;
|
|
8010
8617
|
exports.OptilogicLogo = OptilogicLogo;
|
|
@@ -8022,6 +8629,7 @@ exports.Select = Select;
|
|
|
8022
8629
|
exports.SelectContent = SelectContent;
|
|
8023
8630
|
exports.SelectGroup = SelectGroup;
|
|
8024
8631
|
exports.SelectItem = SelectItem;
|
|
8632
|
+
exports.SelectItemDescription = SelectItemDescription;
|
|
8025
8633
|
exports.SelectLabel = SelectLabel;
|
|
8026
8634
|
exports.SelectScrollDownButton = SelectScrollDownButton;
|
|
8027
8635
|
exports.SelectScrollUpButton = SelectScrollUpButton;
|
|
@@ -8089,6 +8697,8 @@ exports.labelVariants = labelVariants;
|
|
|
8089
8697
|
exports.loadingSpinnerVariants = loadingSpinnerVariants;
|
|
8090
8698
|
exports.mergeRenderers = mergeRenderers;
|
|
8091
8699
|
exports.resolveRenderer = resolveRenderer;
|
|
8700
|
+
exports.tabsListVariants = tabsListVariants;
|
|
8701
|
+
exports.tabsTriggerVariants = tabsTriggerVariants;
|
|
8092
8702
|
exports.themeToHsl = themeToHsl;
|
|
8093
8703
|
exports.useColumnResize = useColumnResize;
|
|
8094
8704
|
exports.useColumnResizeManager = useColumnResizeManager;
|