@optilogic/core 1.2.2 → 1.3.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.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-center 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",
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 TabsList = React20__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
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
- // Base styles
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
  ),
@@ -5650,6 +5702,504 @@ function Autocomplete({
5650
5702
  )
5651
5703
  ] });
5652
5704
  }
5705
+ function MultiSelect({
5706
+ options,
5707
+ value,
5708
+ onChange,
5709
+ placeholder = "Select items...",
5710
+ searchPlaceholder = "Search...",
5711
+ emptyText = "No options found.",
5712
+ disabled = false,
5713
+ className,
5714
+ clearable = true,
5715
+ maxDisplayItems = 3,
5716
+ showSelectAll = false,
5717
+ selectAllLabel = "Select all"
5718
+ }) {
5719
+ const [open, setOpen] = React20__namespace.useState(false);
5720
+ const [search, setSearch] = React20__namespace.useState("");
5721
+ const inputRef = React20__namespace.useRef(null);
5722
+ const safeOptions = options ?? [];
5723
+ const safeValue = value ?? [];
5724
+ const selectedSet = React20__namespace.useMemo(() => new Set(safeValue), [safeValue]);
5725
+ const filteredOptions = React20__namespace.useMemo(() => {
5726
+ if (!search.trim()) return safeOptions;
5727
+ const searchLower = search.toLowerCase();
5728
+ return safeOptions.filter(
5729
+ (opt) => opt.label.toLowerCase().includes(searchLower) || opt.description?.toLowerCase().includes(searchLower)
5730
+ );
5731
+ }, [safeOptions, search]);
5732
+ const groupedOptions = React20__namespace.useMemo(() => {
5733
+ const groups = {};
5734
+ const ungrouped = [];
5735
+ filteredOptions.forEach((opt) => {
5736
+ if (opt.group) {
5737
+ if (!groups[opt.group]) groups[opt.group] = [];
5738
+ groups[opt.group].push(opt);
5739
+ } else {
5740
+ ungrouped.push(opt);
5741
+ }
5742
+ });
5743
+ return { groups, ungrouped };
5744
+ }, [filteredOptions]);
5745
+ const hasGroups = Object.keys(groupedOptions.groups).length > 0;
5746
+ const selectableFiltered = React20__namespace.useMemo(
5747
+ () => filteredOptions.filter((opt) => !opt.disabled),
5748
+ [filteredOptions]
5749
+ );
5750
+ const allFilteredSelected = React20__namespace.useMemo(
5751
+ () => selectableFiltered.length > 0 && selectableFiltered.every((opt) => selectedSet.has(opt.value)),
5752
+ [selectableFiltered, selectedSet]
5753
+ );
5754
+ const someFilteredSelected = React20__namespace.useMemo(
5755
+ () => !allFilteredSelected && selectableFiltered.some((opt) => selectedSet.has(opt.value)),
5756
+ [selectableFiltered, selectedSet, allFilteredSelected]
5757
+ );
5758
+ const handleToggle = React20__namespace.useCallback(
5759
+ (optionValue) => {
5760
+ const next = selectedSet.has(optionValue) ? safeValue.filter((v) => v !== optionValue) : [...safeValue, optionValue];
5761
+ onChange?.(next);
5762
+ },
5763
+ [onChange, safeValue, selectedSet]
5764
+ );
5765
+ const handleRemove = React20__namespace.useCallback(
5766
+ (optionValue, e) => {
5767
+ e.stopPropagation();
5768
+ onChange?.(safeValue.filter((v) => v !== optionValue));
5769
+ },
5770
+ [onChange, safeValue]
5771
+ );
5772
+ const handleClearAll = React20__namespace.useCallback(
5773
+ (e) => {
5774
+ e.stopPropagation();
5775
+ onChange?.([]);
5776
+ },
5777
+ [onChange]
5778
+ );
5779
+ const handleSelectAll = React20__namespace.useCallback(() => {
5780
+ if (allFilteredSelected) {
5781
+ const filteredValues = new Set(selectableFiltered.map((o) => o.value));
5782
+ onChange?.(safeValue.filter((v) => !filteredValues.has(v)));
5783
+ } else {
5784
+ const existing = new Set(safeValue);
5785
+ const next = [...safeValue];
5786
+ for (const opt of selectableFiltered) {
5787
+ if (!existing.has(opt.value)) {
5788
+ next.push(opt.value);
5789
+ }
5790
+ }
5791
+ onChange?.(next);
5792
+ }
5793
+ }, [allFilteredSelected, selectableFiltered, safeValue, onChange]);
5794
+ React20__namespace.useEffect(() => {
5795
+ if (open) {
5796
+ const timeout = setTimeout(() => inputRef.current?.focus(), 0);
5797
+ return () => clearTimeout(timeout);
5798
+ } else {
5799
+ setSearch("");
5800
+ }
5801
+ }, [open]);
5802
+ const handleKeyDown = React20__namespace.useCallback((e) => {
5803
+ if (e.key === "Escape") setOpen(false);
5804
+ }, []);
5805
+ const selectedLabels = React20__namespace.useMemo(
5806
+ () => safeValue.map((v) => safeOptions.find((o) => o.value === v)?.label ?? v).slice(0, maxDisplayItems),
5807
+ [safeValue, safeOptions, maxDisplayItems]
5808
+ );
5809
+ const overflow = safeValue.length - maxDisplayItems;
5810
+ const isSearching = search.trim().length > 0;
5811
+ const renderOption = (option) => /* @__PURE__ */ jsxRuntime.jsxs(
5812
+ "button",
5813
+ {
5814
+ type: "button",
5815
+ disabled: option.disabled,
5816
+ onClick: () => handleToggle(option.value),
5817
+ className: cn(
5818
+ "relative flex w-full cursor-pointer select-none items-start gap-2 rounded-sm px-2 py-1.5 text-sm outline-none",
5819
+ "hover:bg-accent hover:text-accent-foreground",
5820
+ option.disabled && "pointer-events-none opacity-50"
5821
+ ),
5822
+ children: [
5823
+ /* @__PURE__ */ jsxRuntime.jsx(
5824
+ Checkbox,
5825
+ {
5826
+ checked: selectedSet.has(option.value),
5827
+ tabIndex: -1,
5828
+ className: "mt-0.5 pointer-events-none"
5829
+ }
5830
+ ),
5831
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
5832
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate", children: option.label }),
5833
+ option.description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground truncate", children: option.description })
5834
+ ] })
5835
+ ]
5836
+ },
5837
+ option.value
5838
+ );
5839
+ return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: setOpen, children: [
5840
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, disabled, children: /* @__PURE__ */ jsxRuntime.jsxs(
5841
+ "button",
5842
+ {
5843
+ type: "button",
5844
+ role: "combobox",
5845
+ "aria-expanded": open,
5846
+ "aria-haspopup": "listbox",
5847
+ disabled,
5848
+ className: cn(
5849
+ "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",
5850
+ "hover:border-input-hover",
5851
+ "focus:outline-none focus:ring-1 focus:ring-ring",
5852
+ "disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:border-input",
5853
+ className
5854
+ ),
5855
+ children: [
5856
+ /* @__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: [
5857
+ selectedLabels.map((label, i) => /* @__PURE__ */ jsxRuntime.jsxs(
5858
+ "span",
5859
+ {
5860
+ 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",
5861
+ children: [
5862
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate max-w-[100px]", children: label }),
5863
+ /* @__PURE__ */ jsxRuntime.jsx(
5864
+ "span",
5865
+ {
5866
+ role: "button",
5867
+ tabIndex: -1,
5868
+ onClick: (e) => handleRemove(safeValue[i], e),
5869
+ className: "rounded-sm hover:bg-foreground/10 p-0.5",
5870
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3 w-3" })
5871
+ }
5872
+ )
5873
+ ]
5874
+ },
5875
+ safeValue[i]
5876
+ )),
5877
+ overflow > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
5878
+ "+",
5879
+ overflow
5880
+ ] })
5881
+ ] }) }),
5882
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-shrink-0", children: [
5883
+ clearable && safeValue.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
5884
+ "span",
5885
+ {
5886
+ role: "button",
5887
+ tabIndex: -1,
5888
+ onClick: handleClearAll,
5889
+ className: "rounded-sm hover:bg-muted p-0.5",
5890
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3.5 w-3.5 text-muted-foreground" })
5891
+ }
5892
+ ),
5893
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4 opacity-50" })
5894
+ ] })
5895
+ ]
5896
+ }
5897
+ ) }),
5898
+ /* @__PURE__ */ jsxRuntime.jsxs(
5899
+ PopoverContent,
5900
+ {
5901
+ className: "w-[--radix-popover-trigger-width] p-0",
5902
+ align: "start",
5903
+ sideOffset: 4,
5904
+ onKeyDown: handleKeyDown,
5905
+ children: [
5906
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center border-b border-border px-3", children: [
5907
+ /* @__PURE__ */ jsxRuntime.jsx(
5908
+ "input",
5909
+ {
5910
+ ref: inputRef,
5911
+ type: "text",
5912
+ value: search,
5913
+ onChange: (e) => setSearch(e.target.value),
5914
+ placeholder: searchPlaceholder,
5915
+ className: "flex h-9 w-full bg-transparent py-2 text-sm outline-none placeholder:text-muted-foreground"
5916
+ }
5917
+ ),
5918
+ search && /* @__PURE__ */ jsxRuntime.jsx(
5919
+ "button",
5920
+ {
5921
+ type: "button",
5922
+ onClick: () => setSearch(""),
5923
+ className: "p-1 hover:bg-muted rounded-sm",
5924
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3.5 w-3.5 text-muted-foreground" })
5925
+ }
5926
+ )
5927
+ ] }),
5928
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-h-[300px] overflow-y-auto p-1", children: [
5929
+ showSelectAll && selectableFiltered.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5930
+ /* @__PURE__ */ jsxRuntime.jsxs(
5931
+ "button",
5932
+ {
5933
+ type: "button",
5934
+ onClick: handleSelectAll,
5935
+ className: cn(
5936
+ "relative flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none",
5937
+ "hover:bg-accent hover:text-accent-foreground"
5938
+ ),
5939
+ children: [
5940
+ /* @__PURE__ */ jsxRuntime.jsx(
5941
+ Checkbox,
5942
+ {
5943
+ checked: allFilteredSelected ? true : someFilteredSelected ? "indeterminate" : false,
5944
+ tabIndex: -1,
5945
+ className: "pointer-events-none"
5946
+ }
5947
+ ),
5948
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: isSearching ? `${selectAllLabel} (filtered)` : selectAllLabel })
5949
+ ]
5950
+ }
5951
+ ),
5952
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "-mx-1 my-1 h-px bg-border" })
5953
+ ] }),
5954
+ 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: [
5955
+ groupedOptions.ungrouped.map(renderOption),
5956
+ Object.entries(groupedOptions.groups).map(([group, opts]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
5957
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1.5 text-xs font-semibold text-muted-foreground", children: group }),
5958
+ opts.map(renderOption)
5959
+ ] }, group))
5960
+ ] }) : filteredOptions.map(renderOption)
5961
+ ] })
5962
+ ]
5963
+ }
5964
+ )
5965
+ ] });
5966
+ }
5967
+ MultiSelect.displayName = "MultiSelect";
5968
+ function Combobox({
5969
+ options,
5970
+ value,
5971
+ onChange,
5972
+ onInputChange,
5973
+ placeholder = "Type or select...",
5974
+ emptyText = "No options found.",
5975
+ disabled = false,
5976
+ className,
5977
+ clearable = false,
5978
+ allowCustomValue = true
5979
+ }) {
5980
+ const [open, setOpen] = React20__namespace.useState(false);
5981
+ const [inputValue, setInputValue] = React20__namespace.useState("");
5982
+ const inputRef = React20__namespace.useRef(null);
5983
+ const wrapperRef = React20__namespace.useRef(null);
5984
+ const selectedOption = React20__namespace.useMemo(
5985
+ () => options.find((opt) => opt.value === value),
5986
+ [options, value]
5987
+ );
5988
+ React20__namespace.useEffect(() => {
5989
+ if (!open) {
5990
+ setInputValue(selectedOption?.label ?? value ?? "");
5991
+ }
5992
+ }, [value, selectedOption, open]);
5993
+ const filteredOptions = React20__namespace.useMemo(() => {
5994
+ if (!inputValue.trim()) return options;
5995
+ const searchLower = inputValue.toLowerCase();
5996
+ return options.filter(
5997
+ (opt) => opt.label.toLowerCase().includes(searchLower) || opt.description?.toLowerCase().includes(searchLower)
5998
+ );
5999
+ }, [options, inputValue]);
6000
+ const groupedOptions = React20__namespace.useMemo(() => {
6001
+ const groups = {};
6002
+ const ungrouped = [];
6003
+ filteredOptions.forEach((opt) => {
6004
+ if (opt.group) {
6005
+ if (!groups[opt.group]) groups[opt.group] = [];
6006
+ groups[opt.group].push(opt);
6007
+ } else {
6008
+ ungrouped.push(opt);
6009
+ }
6010
+ });
6011
+ return { groups, ungrouped };
6012
+ }, [filteredOptions]);
6013
+ const hasGroups = Object.keys(groupedOptions.groups).length > 0;
6014
+ const handleInputChange = React20__namespace.useCallback(
6015
+ (e) => {
6016
+ const newValue = e.target.value;
6017
+ setInputValue(newValue);
6018
+ onInputChange?.(newValue);
6019
+ if (!open) setOpen(true);
6020
+ },
6021
+ [onInputChange, open]
6022
+ );
6023
+ const handleSelect = React20__namespace.useCallback(
6024
+ (optionValue) => {
6025
+ const option = options.find((o) => o.value === optionValue);
6026
+ onChange?.(optionValue);
6027
+ setInputValue(option?.label ?? optionValue);
6028
+ setOpen(false);
6029
+ },
6030
+ [onChange, options]
6031
+ );
6032
+ const handleClear = React20__namespace.useCallback(
6033
+ (e) => {
6034
+ e.stopPropagation();
6035
+ e.preventDefault();
6036
+ onChange?.(void 0);
6037
+ setInputValue("");
6038
+ inputRef.current?.focus();
6039
+ },
6040
+ [onChange]
6041
+ );
6042
+ const handleFocus = React20__namespace.useCallback(() => {
6043
+ setOpen(true);
6044
+ inputRef.current?.select();
6045
+ }, []);
6046
+ const handleBlur = React20__namespace.useCallback(() => {
6047
+ setTimeout(() => {
6048
+ if (!wrapperRef.current?.contains(document.activeElement)) {
6049
+ setOpen(false);
6050
+ }
6051
+ if (allowCustomValue && inputValue.trim()) {
6052
+ const matchingOption = options.find(
6053
+ (opt) => opt.label.toLowerCase() === inputValue.toLowerCase()
6054
+ );
6055
+ if (matchingOption) {
6056
+ onChange?.(matchingOption.value);
6057
+ setInputValue(matchingOption.label);
6058
+ } else {
6059
+ onChange?.(inputValue.trim());
6060
+ }
6061
+ } else if (!allowCustomValue) {
6062
+ setInputValue(selectedOption?.label ?? "");
6063
+ }
6064
+ }, 200);
6065
+ }, [allowCustomValue, inputValue, options, onChange, selectedOption]);
6066
+ const handleKeyDown = React20__namespace.useCallback(
6067
+ (e) => {
6068
+ if (e.key === "Escape") {
6069
+ setOpen(false);
6070
+ setInputValue(selectedOption?.label ?? value ?? "");
6071
+ inputRef.current?.blur();
6072
+ } else if (e.key === "Enter" && open) {
6073
+ e.preventDefault();
6074
+ if (filteredOptions.length === 1) {
6075
+ handleSelect(filteredOptions[0].value);
6076
+ } else if (allowCustomValue && inputValue.trim()) {
6077
+ const exactMatch = filteredOptions.find(
6078
+ (opt) => opt.label.toLowerCase() === inputValue.toLowerCase()
6079
+ );
6080
+ if (exactMatch) {
6081
+ handleSelect(exactMatch.value);
6082
+ } else {
6083
+ onChange?.(inputValue.trim());
6084
+ setOpen(false);
6085
+ }
6086
+ }
6087
+ }
6088
+ },
6089
+ [
6090
+ open,
6091
+ filteredOptions,
6092
+ handleSelect,
6093
+ allowCustomValue,
6094
+ inputValue,
6095
+ onChange,
6096
+ selectedOption,
6097
+ value
6098
+ ]
6099
+ );
6100
+ const renderOption = (option) => /* @__PURE__ */ jsxRuntime.jsxs(
6101
+ "button",
6102
+ {
6103
+ type: "button",
6104
+ disabled: option.disabled,
6105
+ onMouseDown: (e) => e.preventDefault(),
6106
+ onClick: () => handleSelect(option.value),
6107
+ className: cn(
6108
+ "relative flex w-full cursor-pointer select-none items-start gap-2 rounded-sm px-2 py-1.5 text-sm outline-none",
6109
+ "hover:bg-accent hover:text-accent-foreground",
6110
+ "focus:bg-accent focus:text-accent-foreground",
6111
+ option.disabled && "pointer-events-none opacity-50",
6112
+ value === option.value && "bg-accent/50"
6113
+ ),
6114
+ children: [
6115
+ /* @__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" }) }),
6116
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
6117
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate", children: option.label }),
6118
+ option.description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground truncate", children: option.description })
6119
+ ] })
6120
+ ]
6121
+ },
6122
+ option.value
6123
+ );
6124
+ return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: setOpen, children: [
6125
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
6126
+ "div",
6127
+ {
6128
+ ref: wrapperRef,
6129
+ className: cn(
6130
+ "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",
6131
+ "hover:border-input-hover",
6132
+ "focus-within:outline-none focus-within:ring-1 focus-within:ring-ring",
6133
+ disabled && "cursor-not-allowed opacity-50 hover:border-input",
6134
+ className
6135
+ ),
6136
+ children: [
6137
+ /* @__PURE__ */ jsxRuntime.jsx(
6138
+ "input",
6139
+ {
6140
+ ref: inputRef,
6141
+ type: "text",
6142
+ value: inputValue,
6143
+ onChange: handleInputChange,
6144
+ onFocus: handleFocus,
6145
+ onBlur: handleBlur,
6146
+ onKeyDown: handleKeyDown,
6147
+ placeholder,
6148
+ disabled,
6149
+ className: "flex-1 min-w-0 bg-transparent py-2 outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed",
6150
+ role: "combobox",
6151
+ "aria-expanded": open,
6152
+ "aria-haspopup": "listbox",
6153
+ "aria-autocomplete": "list"
6154
+ }
6155
+ ),
6156
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-shrink-0", children: [
6157
+ clearable && value && /* @__PURE__ */ jsxRuntime.jsx(
6158
+ "span",
6159
+ {
6160
+ role: "button",
6161
+ tabIndex: -1,
6162
+ onMouseDown: (e) => e.preventDefault(),
6163
+ onClick: handleClear,
6164
+ className: "rounded-sm hover:bg-muted p-0.5",
6165
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3.5 w-3.5 text-muted-foreground" })
6166
+ }
6167
+ ),
6168
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4 opacity-50" })
6169
+ ] })
6170
+ ]
6171
+ }
6172
+ ) }),
6173
+ /* @__PURE__ */ jsxRuntime.jsx(
6174
+ PopoverContent,
6175
+ {
6176
+ className: "p-0",
6177
+ style: { width: wrapperRef.current?.offsetWidth },
6178
+ align: "start",
6179
+ sideOffset: 4,
6180
+ onOpenAutoFocus: (e) => e.preventDefault(),
6181
+ onFocusOutside: (e) => {
6182
+ if (wrapperRef.current?.contains(e.target)) {
6183
+ e.preventDefault();
6184
+ }
6185
+ },
6186
+ onInteractOutside: (e) => {
6187
+ if (wrapperRef.current?.contains(e.target)) {
6188
+ e.preventDefault();
6189
+ }
6190
+ },
6191
+ 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: [
6192
+ groupedOptions.ungrouped.map(renderOption),
6193
+ Object.entries(groupedOptions.groups).map(([group, opts]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
6194
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1.5 text-xs font-semibold text-muted-foreground", children: group }),
6195
+ opts.map(renderOption)
6196
+ ] }, group))
6197
+ ] }) : filteredOptions.map(renderOption) })
6198
+ }
6199
+ )
6200
+ ] });
6201
+ }
6202
+ Combobox.displayName = "Combobox";
5653
6203
  var iconButtonVariants = classVarianceAuthority.cva(
5654
6204
  // Base styles
5655
6205
  [
@@ -7963,6 +8513,7 @@ exports.CellEditor = CellEditor;
7963
8513
  exports.Checkbox = Checkbox;
7964
8514
  exports.Chip = Chip;
7965
8515
  exports.CodeRenderer = CodeRenderer;
8516
+ exports.Combobox = Combobox;
7966
8517
  exports.ConfirmationModal = ConfirmationModal;
7967
8518
  exports.ContextMenu = ContextMenu;
7968
8519
  exports.CopyButton = CopyButton;
@@ -8005,6 +8556,7 @@ exports.MODERN_LIGHT_THEME = MODERN_LIGHT_THEME;
8005
8556
  exports.MarkdownRenderer = MarkdownRenderer;
8006
8557
  exports.Modal = Modal;
8007
8558
  exports.ModalButton = ModalButton;
8559
+ exports.MultiSelect = MultiSelect;
8008
8560
  exports.OPTILOGIC_DARK_THEME = OPTILOGIC_DARK_THEME;
8009
8561
  exports.OPTILOGIC_LEGACY_THEME = OPTILOGIC_LEGACY_THEME;
8010
8562
  exports.OptilogicLogo = OptilogicLogo;
@@ -8022,6 +8574,7 @@ exports.Select = Select;
8022
8574
  exports.SelectContent = SelectContent;
8023
8575
  exports.SelectGroup = SelectGroup;
8024
8576
  exports.SelectItem = SelectItem;
8577
+ exports.SelectItemDescription = SelectItemDescription;
8025
8578
  exports.SelectLabel = SelectLabel;
8026
8579
  exports.SelectScrollDownButton = SelectScrollDownButton;
8027
8580
  exports.SelectScrollUpButton = SelectScrollUpButton;
@@ -8089,6 +8642,8 @@ exports.labelVariants = labelVariants;
8089
8642
  exports.loadingSpinnerVariants = loadingSpinnerVariants;
8090
8643
  exports.mergeRenderers = mergeRenderers;
8091
8644
  exports.resolveRenderer = resolveRenderer;
8645
+ exports.tabsListVariants = tabsListVariants;
8646
+ exports.tabsTriggerVariants = tabsTriggerVariants;
8092
8647
  exports.themeToHsl = themeToHsl;
8093
8648
  exports.useColumnResize = useColumnResize;
8094
8649
  exports.useColumnResizeManager = useColumnResizeManager;