@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.js CHANGED
@@ -351,17 +351,26 @@ var SelectItem = React20.forwardRef(({ className, children, ...props }, ref) =>
351
351
  {
352
352
  ref,
353
353
  className: cn(
354
- "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",
354
+ "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",
355
355
  className
356
356
  ),
357
357
  ...props,
358
358
  children: [
359
- /* @__PURE__ */ jsx("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) }) }),
359
+ /* @__PURE__ */ jsx("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center mt-0.5", children: /* @__PURE__ */ jsx(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) }) }),
360
360
  /* @__PURE__ */ jsx(SelectPrimitive.ItemText, { children })
361
361
  ]
362
362
  }
363
363
  ));
364
364
  SelectItem.displayName = SelectPrimitive.Item.displayName;
365
+ var SelectItemDescription = React20.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
366
+ "span",
367
+ {
368
+ ref,
369
+ className: cn("text-xs text-muted-foreground", className),
370
+ ...props
371
+ }
372
+ ));
373
+ SelectItemDescription.displayName = "SelectItemDescription";
365
374
  var SelectSeparator = React20.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
366
375
  SelectPrimitive.Separator,
367
376
  {
@@ -372,41 +381,85 @@ var SelectSeparator = React20.forwardRef(({ className, ...props }, ref) => /* @_
372
381
  ));
373
382
  SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
374
383
  var Tabs = TabsPrimitive.Root;
375
- var TabsList = React20.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
384
+ var tabsListVariants = cva(
385
+ "inline-flex h-10 items-center justify-start bg-transparent",
386
+ {
387
+ variants: {
388
+ variant: {
389
+ default: "border-b border-border",
390
+ pill: "gap-1 rounded-lg bg-muted p-1",
391
+ unstyled: ""
392
+ }
393
+ },
394
+ defaultVariants: {
395
+ variant: "default"
396
+ }
397
+ }
398
+ );
399
+ var tabsTriggerVariants = cva(
400
+ [
401
+ "inline-flex items-center justify-center whitespace-nowrap",
402
+ "px-4 py-2.5 text-sm font-medium",
403
+ "transition-colors",
404
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
405
+ "disabled:pointer-events-none disabled:opacity-50"
406
+ ],
407
+ {
408
+ variants: {
409
+ variant: {
410
+ default: [
411
+ "-mb-px",
412
+ "border-transparent text-muted-foreground",
413
+ "hover:text-foreground hover:border-muted-foreground/50",
414
+ "data-[state=active]:border-foreground data-[state=active]:text-foreground"
415
+ ],
416
+ pill: [
417
+ "rounded-md",
418
+ "text-muted-foreground",
419
+ "hover:text-foreground",
420
+ "data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm"
421
+ ],
422
+ unstyled: [
423
+ "text-muted-foreground",
424
+ "hover:text-foreground",
425
+ "data-[state=active]:text-foreground"
426
+ ]
427
+ },
428
+ indicatorSize: {
429
+ sm: "border-b",
430
+ default: "border-b-2",
431
+ lg: "border-b-[3px]"
432
+ }
433
+ },
434
+ compoundVariants: [
435
+ { variant: "pill", indicatorSize: "sm", className: "border-b-0" },
436
+ { variant: "pill", indicatorSize: "default", className: "border-b-0" },
437
+ { variant: "pill", indicatorSize: "lg", className: "border-b-0" },
438
+ { variant: "unstyled", indicatorSize: "sm", className: "border-b-0" },
439
+ { variant: "unstyled", indicatorSize: "default", className: "border-b-0" },
440
+ { variant: "unstyled", indicatorSize: "lg", className: "border-b-0" }
441
+ ],
442
+ defaultVariants: {
443
+ variant: "default",
444
+ indicatorSize: "default"
445
+ }
446
+ }
447
+ );
448
+ var TabsList = React20.forwardRef(({ className, variant, ...props }, ref) => /* @__PURE__ */ jsx(
376
449
  TabsPrimitive.List,
377
450
  {
378
451
  ref,
379
- className: cn(
380
- "inline-flex h-10 items-center justify-start",
381
- "border-b border-border",
382
- "bg-transparent",
383
- className
384
- ),
452
+ className: cn(tabsListVariants({ variant }), className),
385
453
  ...props
386
454
  }
387
455
  ));
388
456
  TabsList.displayName = TabsPrimitive.List.displayName;
389
- var TabsTrigger = React20.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
457
+ var TabsTrigger = React20.forwardRef(({ className, variant, indicatorSize, ...props }, ref) => /* @__PURE__ */ jsx(
390
458
  TabsPrimitive.Trigger,
391
459
  {
392
460
  ref,
393
461
  className: cn(
394
- // Base styles
395
- "inline-flex items-center justify-center whitespace-nowrap",
396
- "px-4 py-2.5 text-sm font-medium",
397
- "transition-colors",
398
- // Border-bottom indicator style
399
- "border-b-2 -mb-px",
400
- // Default state
401
- "border-transparent text-muted-foreground",
402
- // Hover state
403
- "hover:text-foreground hover:border-muted-foreground/50",
404
- // Active/selected state
405
- "data-[state=active]:border-foreground data-[state=active]:text-foreground",
406
- // Focus styles
407
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
408
- // Disabled styles
409
- "disabled:pointer-events-none disabled:opacity-50",
462
+ tabsTriggerVariants({ variant, indicatorSize }),
410
463
  className
411
464
  ),
412
465
  ...props
@@ -419,7 +472,6 @@ var TabsContent = React20.forwardRef(({ className, ...props }, ref) => /* @__PUR
419
472
  ref,
420
473
  className: cn(
421
474
  "mt-2",
422
- // Focus styles
423
475
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
424
476
  className
425
477
  ),
@@ -5613,6 +5665,504 @@ function Autocomplete({
5613
5665
  )
5614
5666
  ] });
5615
5667
  }
5668
+ function MultiSelect({
5669
+ options,
5670
+ value,
5671
+ onChange,
5672
+ placeholder = "Select items...",
5673
+ searchPlaceholder = "Search...",
5674
+ emptyText = "No options found.",
5675
+ disabled = false,
5676
+ className,
5677
+ clearable = true,
5678
+ maxDisplayItems = 3,
5679
+ showSelectAll = false,
5680
+ selectAllLabel = "Select all"
5681
+ }) {
5682
+ const [open, setOpen] = React20.useState(false);
5683
+ const [search, setSearch] = React20.useState("");
5684
+ const inputRef = React20.useRef(null);
5685
+ const safeOptions = options ?? [];
5686
+ const safeValue = value ?? [];
5687
+ const selectedSet = React20.useMemo(() => new Set(safeValue), [safeValue]);
5688
+ const filteredOptions = React20.useMemo(() => {
5689
+ if (!search.trim()) return safeOptions;
5690
+ const searchLower = search.toLowerCase();
5691
+ return safeOptions.filter(
5692
+ (opt) => opt.label.toLowerCase().includes(searchLower) || opt.description?.toLowerCase().includes(searchLower)
5693
+ );
5694
+ }, [safeOptions, search]);
5695
+ const groupedOptions = React20.useMemo(() => {
5696
+ const groups = {};
5697
+ const ungrouped = [];
5698
+ filteredOptions.forEach((opt) => {
5699
+ if (opt.group) {
5700
+ if (!groups[opt.group]) groups[opt.group] = [];
5701
+ groups[opt.group].push(opt);
5702
+ } else {
5703
+ ungrouped.push(opt);
5704
+ }
5705
+ });
5706
+ return { groups, ungrouped };
5707
+ }, [filteredOptions]);
5708
+ const hasGroups = Object.keys(groupedOptions.groups).length > 0;
5709
+ const selectableFiltered = React20.useMemo(
5710
+ () => filteredOptions.filter((opt) => !opt.disabled),
5711
+ [filteredOptions]
5712
+ );
5713
+ const allFilteredSelected = React20.useMemo(
5714
+ () => selectableFiltered.length > 0 && selectableFiltered.every((opt) => selectedSet.has(opt.value)),
5715
+ [selectableFiltered, selectedSet]
5716
+ );
5717
+ const someFilteredSelected = React20.useMemo(
5718
+ () => !allFilteredSelected && selectableFiltered.some((opt) => selectedSet.has(opt.value)),
5719
+ [selectableFiltered, selectedSet, allFilteredSelected]
5720
+ );
5721
+ const handleToggle = React20.useCallback(
5722
+ (optionValue) => {
5723
+ const next = selectedSet.has(optionValue) ? safeValue.filter((v) => v !== optionValue) : [...safeValue, optionValue];
5724
+ onChange?.(next);
5725
+ },
5726
+ [onChange, safeValue, selectedSet]
5727
+ );
5728
+ const handleRemove = React20.useCallback(
5729
+ (optionValue, e) => {
5730
+ e.stopPropagation();
5731
+ onChange?.(safeValue.filter((v) => v !== optionValue));
5732
+ },
5733
+ [onChange, safeValue]
5734
+ );
5735
+ const handleClearAll = React20.useCallback(
5736
+ (e) => {
5737
+ e.stopPropagation();
5738
+ onChange?.([]);
5739
+ },
5740
+ [onChange]
5741
+ );
5742
+ const handleSelectAll = React20.useCallback(() => {
5743
+ if (allFilteredSelected) {
5744
+ const filteredValues = new Set(selectableFiltered.map((o) => o.value));
5745
+ onChange?.(safeValue.filter((v) => !filteredValues.has(v)));
5746
+ } else {
5747
+ const existing = new Set(safeValue);
5748
+ const next = [...safeValue];
5749
+ for (const opt of selectableFiltered) {
5750
+ if (!existing.has(opt.value)) {
5751
+ next.push(opt.value);
5752
+ }
5753
+ }
5754
+ onChange?.(next);
5755
+ }
5756
+ }, [allFilteredSelected, selectableFiltered, safeValue, onChange]);
5757
+ React20.useEffect(() => {
5758
+ if (open) {
5759
+ const timeout = setTimeout(() => inputRef.current?.focus(), 0);
5760
+ return () => clearTimeout(timeout);
5761
+ } else {
5762
+ setSearch("");
5763
+ }
5764
+ }, [open]);
5765
+ const handleKeyDown = React20.useCallback((e) => {
5766
+ if (e.key === "Escape") setOpen(false);
5767
+ }, []);
5768
+ const selectedLabels = React20.useMemo(
5769
+ () => safeValue.map((v) => safeOptions.find((o) => o.value === v)?.label ?? v).slice(0, maxDisplayItems),
5770
+ [safeValue, safeOptions, maxDisplayItems]
5771
+ );
5772
+ const overflow = safeValue.length - maxDisplayItems;
5773
+ const isSearching = search.trim().length > 0;
5774
+ const renderOption = (option) => /* @__PURE__ */ jsxs(
5775
+ "button",
5776
+ {
5777
+ type: "button",
5778
+ disabled: option.disabled,
5779
+ onClick: () => handleToggle(option.value),
5780
+ className: cn(
5781
+ "relative flex w-full cursor-pointer select-none items-start gap-2 rounded-sm px-2 py-1.5 text-sm outline-none",
5782
+ "hover:bg-accent hover:text-accent-foreground",
5783
+ option.disabled && "pointer-events-none opacity-50"
5784
+ ),
5785
+ children: [
5786
+ /* @__PURE__ */ jsx(
5787
+ Checkbox,
5788
+ {
5789
+ checked: selectedSet.has(option.value),
5790
+ tabIndex: -1,
5791
+ className: "mt-0.5 pointer-events-none"
5792
+ }
5793
+ ),
5794
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
5795
+ /* @__PURE__ */ jsx("div", { className: "truncate", children: option.label }),
5796
+ option.description && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground truncate", children: option.description })
5797
+ ] })
5798
+ ]
5799
+ },
5800
+ option.value
5801
+ );
5802
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
5803
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, disabled, children: /* @__PURE__ */ jsxs(
5804
+ "button",
5805
+ {
5806
+ type: "button",
5807
+ role: "combobox",
5808
+ "aria-expanded": open,
5809
+ "aria-haspopup": "listbox",
5810
+ disabled,
5811
+ className: cn(
5812
+ "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",
5813
+ "hover:border-input-hover",
5814
+ "focus:outline-none focus:ring-1 focus:ring-ring",
5815
+ "disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:border-input",
5816
+ className
5817
+ ),
5818
+ children: [
5819
+ /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-wrap gap-1 items-center min-w-0", children: safeValue.length === 0 ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: placeholder }) : /* @__PURE__ */ jsxs(Fragment, { children: [
5820
+ selectedLabels.map((label, i) => /* @__PURE__ */ jsxs(
5821
+ "span",
5822
+ {
5823
+ 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",
5824
+ children: [
5825
+ /* @__PURE__ */ jsx("span", { className: "truncate max-w-[100px]", children: label }),
5826
+ /* @__PURE__ */ jsx(
5827
+ "span",
5828
+ {
5829
+ role: "button",
5830
+ tabIndex: -1,
5831
+ onClick: (e) => handleRemove(safeValue[i], e),
5832
+ className: "rounded-sm hover:bg-foreground/10 p-0.5",
5833
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
5834
+ }
5835
+ )
5836
+ ]
5837
+ },
5838
+ safeValue[i]
5839
+ )),
5840
+ overflow > 0 && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
5841
+ "+",
5842
+ overflow
5843
+ ] })
5844
+ ] }) }),
5845
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 flex-shrink-0", children: [
5846
+ clearable && safeValue.length > 0 && /* @__PURE__ */ jsx(
5847
+ "span",
5848
+ {
5849
+ role: "button",
5850
+ tabIndex: -1,
5851
+ onClick: handleClearAll,
5852
+ className: "rounded-sm hover:bg-muted p-0.5",
5853
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5 text-muted-foreground" })
5854
+ }
5855
+ ),
5856
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4 opacity-50" })
5857
+ ] })
5858
+ ]
5859
+ }
5860
+ ) }),
5861
+ /* @__PURE__ */ jsxs(
5862
+ PopoverContent,
5863
+ {
5864
+ className: "w-[--radix-popover-trigger-width] p-0",
5865
+ align: "start",
5866
+ sideOffset: 4,
5867
+ onKeyDown: handleKeyDown,
5868
+ children: [
5869
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center border-b border-border px-3", children: [
5870
+ /* @__PURE__ */ jsx(
5871
+ "input",
5872
+ {
5873
+ ref: inputRef,
5874
+ type: "text",
5875
+ value: search,
5876
+ onChange: (e) => setSearch(e.target.value),
5877
+ placeholder: searchPlaceholder,
5878
+ className: "flex h-9 w-full bg-transparent py-2 text-sm outline-none placeholder:text-muted-foreground"
5879
+ }
5880
+ ),
5881
+ search && /* @__PURE__ */ jsx(
5882
+ "button",
5883
+ {
5884
+ type: "button",
5885
+ onClick: () => setSearch(""),
5886
+ className: "p-1 hover:bg-muted rounded-sm",
5887
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5 text-muted-foreground" })
5888
+ }
5889
+ )
5890
+ ] }),
5891
+ /* @__PURE__ */ jsxs("div", { className: "max-h-[300px] overflow-y-auto p-1", children: [
5892
+ showSelectAll && selectableFiltered.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
5893
+ /* @__PURE__ */ jsxs(
5894
+ "button",
5895
+ {
5896
+ type: "button",
5897
+ onClick: handleSelectAll,
5898
+ className: cn(
5899
+ "relative flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none",
5900
+ "hover:bg-accent hover:text-accent-foreground"
5901
+ ),
5902
+ children: [
5903
+ /* @__PURE__ */ jsx(
5904
+ Checkbox,
5905
+ {
5906
+ checked: allFilteredSelected ? true : someFilteredSelected ? "indeterminate" : false,
5907
+ tabIndex: -1,
5908
+ className: "pointer-events-none"
5909
+ }
5910
+ ),
5911
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: isSearching ? `${selectAllLabel} (filtered)` : selectAllLabel })
5912
+ ]
5913
+ }
5914
+ ),
5915
+ /* @__PURE__ */ jsx("div", { className: "-mx-1 my-1 h-px bg-border" })
5916
+ ] }),
5917
+ filteredOptions.length === 0 ? /* @__PURE__ */ jsx("div", { className: "py-6 text-center text-sm text-muted-foreground", children: emptyText }) : hasGroups ? /* @__PURE__ */ jsxs(Fragment, { children: [
5918
+ groupedOptions.ungrouped.map(renderOption),
5919
+ Object.entries(groupedOptions.groups).map(([group, opts]) => /* @__PURE__ */ jsxs("div", { children: [
5920
+ /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-xs font-semibold text-muted-foreground", children: group }),
5921
+ opts.map(renderOption)
5922
+ ] }, group))
5923
+ ] }) : filteredOptions.map(renderOption)
5924
+ ] })
5925
+ ]
5926
+ }
5927
+ )
5928
+ ] });
5929
+ }
5930
+ MultiSelect.displayName = "MultiSelect";
5931
+ function Combobox({
5932
+ options,
5933
+ value,
5934
+ onChange,
5935
+ onInputChange,
5936
+ placeholder = "Type or select...",
5937
+ emptyText = "No options found.",
5938
+ disabled = false,
5939
+ className,
5940
+ clearable = false,
5941
+ allowCustomValue = true
5942
+ }) {
5943
+ const [open, setOpen] = React20.useState(false);
5944
+ const [inputValue, setInputValue] = React20.useState("");
5945
+ const inputRef = React20.useRef(null);
5946
+ const wrapperRef = React20.useRef(null);
5947
+ const selectedOption = React20.useMemo(
5948
+ () => options.find((opt) => opt.value === value),
5949
+ [options, value]
5950
+ );
5951
+ React20.useEffect(() => {
5952
+ if (!open) {
5953
+ setInputValue(selectedOption?.label ?? value ?? "");
5954
+ }
5955
+ }, [value, selectedOption, open]);
5956
+ const filteredOptions = React20.useMemo(() => {
5957
+ if (!inputValue.trim()) return options;
5958
+ const searchLower = inputValue.toLowerCase();
5959
+ return options.filter(
5960
+ (opt) => opt.label.toLowerCase().includes(searchLower) || opt.description?.toLowerCase().includes(searchLower)
5961
+ );
5962
+ }, [options, inputValue]);
5963
+ const groupedOptions = React20.useMemo(() => {
5964
+ const groups = {};
5965
+ const ungrouped = [];
5966
+ filteredOptions.forEach((opt) => {
5967
+ if (opt.group) {
5968
+ if (!groups[opt.group]) groups[opt.group] = [];
5969
+ groups[opt.group].push(opt);
5970
+ } else {
5971
+ ungrouped.push(opt);
5972
+ }
5973
+ });
5974
+ return { groups, ungrouped };
5975
+ }, [filteredOptions]);
5976
+ const hasGroups = Object.keys(groupedOptions.groups).length > 0;
5977
+ const handleInputChange = React20.useCallback(
5978
+ (e) => {
5979
+ const newValue = e.target.value;
5980
+ setInputValue(newValue);
5981
+ onInputChange?.(newValue);
5982
+ if (!open) setOpen(true);
5983
+ },
5984
+ [onInputChange, open]
5985
+ );
5986
+ const handleSelect = React20.useCallback(
5987
+ (optionValue) => {
5988
+ const option = options.find((o) => o.value === optionValue);
5989
+ onChange?.(optionValue);
5990
+ setInputValue(option?.label ?? optionValue);
5991
+ setOpen(false);
5992
+ },
5993
+ [onChange, options]
5994
+ );
5995
+ const handleClear = React20.useCallback(
5996
+ (e) => {
5997
+ e.stopPropagation();
5998
+ e.preventDefault();
5999
+ onChange?.(void 0);
6000
+ setInputValue("");
6001
+ inputRef.current?.focus();
6002
+ },
6003
+ [onChange]
6004
+ );
6005
+ const handleFocus = React20.useCallback(() => {
6006
+ setOpen(true);
6007
+ inputRef.current?.select();
6008
+ }, []);
6009
+ const handleBlur = React20.useCallback(() => {
6010
+ setTimeout(() => {
6011
+ if (!wrapperRef.current?.contains(document.activeElement)) {
6012
+ setOpen(false);
6013
+ }
6014
+ if (allowCustomValue && inputValue.trim()) {
6015
+ const matchingOption = options.find(
6016
+ (opt) => opt.label.toLowerCase() === inputValue.toLowerCase()
6017
+ );
6018
+ if (matchingOption) {
6019
+ onChange?.(matchingOption.value);
6020
+ setInputValue(matchingOption.label);
6021
+ } else {
6022
+ onChange?.(inputValue.trim());
6023
+ }
6024
+ } else if (!allowCustomValue) {
6025
+ setInputValue(selectedOption?.label ?? "");
6026
+ }
6027
+ }, 200);
6028
+ }, [allowCustomValue, inputValue, options, onChange, selectedOption]);
6029
+ const handleKeyDown = React20.useCallback(
6030
+ (e) => {
6031
+ if (e.key === "Escape") {
6032
+ setOpen(false);
6033
+ setInputValue(selectedOption?.label ?? value ?? "");
6034
+ inputRef.current?.blur();
6035
+ } else if (e.key === "Enter" && open) {
6036
+ e.preventDefault();
6037
+ if (filteredOptions.length === 1) {
6038
+ handleSelect(filteredOptions[0].value);
6039
+ } else if (allowCustomValue && inputValue.trim()) {
6040
+ const exactMatch = filteredOptions.find(
6041
+ (opt) => opt.label.toLowerCase() === inputValue.toLowerCase()
6042
+ );
6043
+ if (exactMatch) {
6044
+ handleSelect(exactMatch.value);
6045
+ } else {
6046
+ onChange?.(inputValue.trim());
6047
+ setOpen(false);
6048
+ }
6049
+ }
6050
+ }
6051
+ },
6052
+ [
6053
+ open,
6054
+ filteredOptions,
6055
+ handleSelect,
6056
+ allowCustomValue,
6057
+ inputValue,
6058
+ onChange,
6059
+ selectedOption,
6060
+ value
6061
+ ]
6062
+ );
6063
+ const renderOption = (option) => /* @__PURE__ */ jsxs(
6064
+ "button",
6065
+ {
6066
+ type: "button",
6067
+ disabled: option.disabled,
6068
+ onMouseDown: (e) => e.preventDefault(),
6069
+ onClick: () => handleSelect(option.value),
6070
+ className: cn(
6071
+ "relative flex w-full cursor-pointer select-none items-start gap-2 rounded-sm px-2 py-1.5 text-sm outline-none",
6072
+ "hover:bg-accent hover:text-accent-foreground",
6073
+ "focus:bg-accent focus:text-accent-foreground",
6074
+ option.disabled && "pointer-events-none opacity-50",
6075
+ value === option.value && "bg-accent/50"
6076
+ ),
6077
+ children: [
6078
+ /* @__PURE__ */ jsx("span", { className: "flex h-4 w-4 items-center justify-center flex-shrink-0 mt-0.5", children: value === option.value && /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) }),
6079
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
6080
+ /* @__PURE__ */ jsx("div", { className: "truncate", children: option.label }),
6081
+ option.description && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground truncate", children: option.description })
6082
+ ] })
6083
+ ]
6084
+ },
6085
+ option.value
6086
+ );
6087
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
6088
+ /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs(
6089
+ "div",
6090
+ {
6091
+ ref: wrapperRef,
6092
+ className: cn(
6093
+ "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",
6094
+ "hover:border-input-hover",
6095
+ "focus-within:outline-none focus-within:ring-1 focus-within:ring-ring",
6096
+ disabled && "cursor-not-allowed opacity-50 hover:border-input",
6097
+ className
6098
+ ),
6099
+ children: [
6100
+ /* @__PURE__ */ jsx(
6101
+ "input",
6102
+ {
6103
+ ref: inputRef,
6104
+ type: "text",
6105
+ value: inputValue,
6106
+ onChange: handleInputChange,
6107
+ onFocus: handleFocus,
6108
+ onBlur: handleBlur,
6109
+ onKeyDown: handleKeyDown,
6110
+ placeholder,
6111
+ disabled,
6112
+ className: "flex-1 min-w-0 bg-transparent py-2 outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed",
6113
+ role: "combobox",
6114
+ "aria-expanded": open,
6115
+ "aria-haspopup": "listbox",
6116
+ "aria-autocomplete": "list"
6117
+ }
6118
+ ),
6119
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 flex-shrink-0", children: [
6120
+ clearable && value && /* @__PURE__ */ jsx(
6121
+ "span",
6122
+ {
6123
+ role: "button",
6124
+ tabIndex: -1,
6125
+ onMouseDown: (e) => e.preventDefault(),
6126
+ onClick: handleClear,
6127
+ className: "rounded-sm hover:bg-muted p-0.5",
6128
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5 text-muted-foreground" })
6129
+ }
6130
+ ),
6131
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4 opacity-50" })
6132
+ ] })
6133
+ ]
6134
+ }
6135
+ ) }),
6136
+ /* @__PURE__ */ jsx(
6137
+ PopoverContent,
6138
+ {
6139
+ className: "p-0",
6140
+ style: { width: wrapperRef.current?.offsetWidth },
6141
+ align: "start",
6142
+ sideOffset: 4,
6143
+ onOpenAutoFocus: (e) => e.preventDefault(),
6144
+ onFocusOutside: (e) => {
6145
+ if (wrapperRef.current?.contains(e.target)) {
6146
+ e.preventDefault();
6147
+ }
6148
+ },
6149
+ onInteractOutside: (e) => {
6150
+ if (wrapperRef.current?.contains(e.target)) {
6151
+ e.preventDefault();
6152
+ }
6153
+ },
6154
+ children: /* @__PURE__ */ jsx("div", { className: "max-h-[300px] overflow-y-auto p-1", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsx("div", { className: "py-6 text-center text-sm text-muted-foreground", children: emptyText }) : hasGroups ? /* @__PURE__ */ jsxs(Fragment, { children: [
6155
+ groupedOptions.ungrouped.map(renderOption),
6156
+ Object.entries(groupedOptions.groups).map(([group, opts]) => /* @__PURE__ */ jsxs("div", { children: [
6157
+ /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-xs font-semibold text-muted-foreground", children: group }),
6158
+ opts.map(renderOption)
6159
+ ] }, group))
6160
+ ] }) : filteredOptions.map(renderOption) })
6161
+ }
6162
+ )
6163
+ ] });
6164
+ }
6165
+ Combobox.displayName = "Combobox";
5616
6166
  var iconButtonVariants = cva(
5617
6167
  // Base styles
5618
6168
  [
@@ -7889,6 +8439,6 @@ var DataStarIcon = forwardRef(
7889
8439
  );
7890
8440
  DataStarIcon.displayName = "DataStarIcon";
7891
8441
 
7892
- export { ALL_THEMES, Accordion, AccordionContent, AccordionItem, AccordionTrigger, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, Autocomplete, Badge, Board, BoardContent, BoardHeader, Button, Calendar, Card, CardActions, CardContent, CardDescription, CardFooter, CardGrid, CardHeader, CardImage, CardList, CardTitle, CellEditor, Checkbox, Chip, CodeRenderer, ConfirmationModal, ContextMenu, CopyButton, CosmicFrogIcon, CsvRenderer, DARK_ELEGANT_THEME, DEFAULT_RENDERERS, DataGrid, DataStarIcon, DataTable, DatePicker, DatePickerInput, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, FileView, FilterPopover, HeaderCell, HtmlRenderer, IconButton, ImageRenderer, Input, Label, LoadingSpinner, MINIMALIST_LIGHT_THEME, MODERN_DARK_THEME, MODERN_LIGHT_THEME, MarkdownRenderer, Modal, ModalButton, OPTILOGIC_DARK_THEME, OPTILOGIC_LEGACY_THEME, OptilogicLogo, OptilogicLogoWithText, PRESET_THEMES, PlainTextRenderer, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, ResizablePanel, ResizeHandle, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SelectableCard, Separator, Skeleton, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, ThemePicker, Toaster, Tooltip, TooltipArrow, TooltipContent, TooltipPortal, TooltipProvider, TooltipRoot, TooltipTrigger, accordionContentVariants, accordionItemVariants, accordionTriggerVariants, applyFilterOperator, applyFilters, applySorting, applyTheme, areThemesEqual, badgeVariants, boardVariants, buttonVariants, cardActionsVariants, cardGridVariants, cardImageVariants, cardListVariants, cardVariants, cloneTheme, cn, detectContentType, exportTheme, getCellValue, getCurrentTheme, getDefaultTheme, getFileExtension, getPresetTheme, hexToHsl, iconButtonVariants, importTheme, isPresetTheme, isTextContentType, isUrlContentType, labelVariants, loadingSpinnerVariants, mergeRenderers, resolveRenderer, themeToHsl, useColumnResize, useColumnResizeManager, useConfirmation, useContentType, useContextMenu, useDataGridState, useKeyboardNavigation, validateTheme };
8442
+ export { ALL_THEMES, Accordion, AccordionContent, AccordionItem, AccordionTrigger, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, Autocomplete, Badge, Board, BoardContent, BoardHeader, Button, Calendar, Card, CardActions, CardContent, CardDescription, CardFooter, CardGrid, CardHeader, CardImage, CardList, CardTitle, CellEditor, Checkbox, Chip, CodeRenderer, Combobox, ConfirmationModal, ContextMenu, CopyButton, CosmicFrogIcon, CsvRenderer, DARK_ELEGANT_THEME, DEFAULT_RENDERERS, DataGrid, DataStarIcon, DataTable, DatePicker, DatePickerInput, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, FileView, FilterPopover, HeaderCell, HtmlRenderer, IconButton, ImageRenderer, Input, Label, LoadingSpinner, MINIMALIST_LIGHT_THEME, MODERN_DARK_THEME, MODERN_LIGHT_THEME, MarkdownRenderer, Modal, ModalButton, MultiSelect, OPTILOGIC_DARK_THEME, OPTILOGIC_LEGACY_THEME, OptilogicLogo, OptilogicLogoWithText, PRESET_THEMES, PlainTextRenderer, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, ResizablePanel, ResizeHandle, Select, SelectContent, SelectGroup, SelectItem, SelectItemDescription, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SelectableCard, Separator, Skeleton, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, ThemePicker, Toaster, Tooltip, TooltipArrow, TooltipContent, TooltipPortal, TooltipProvider, TooltipRoot, TooltipTrigger, accordionContentVariants, accordionItemVariants, accordionTriggerVariants, applyFilterOperator, applyFilters, applySorting, applyTheme, areThemesEqual, badgeVariants, boardVariants, buttonVariants, cardActionsVariants, cardGridVariants, cardImageVariants, cardListVariants, cardVariants, cloneTheme, cn, detectContentType, exportTheme, getCellValue, getCurrentTheme, getDefaultTheme, getFileExtension, getPresetTheme, hexToHsl, iconButtonVariants, importTheme, isPresetTheme, isTextContentType, isUrlContentType, labelVariants, loadingSpinnerVariants, mergeRenderers, resolveRenderer, tabsListVariants, tabsTriggerVariants, themeToHsl, useColumnResize, useColumnResizeManager, useConfirmation, useContentType, useContextMenu, useDataGridState, useKeyboardNavigation, validateTheme };
7893
8443
  //# sourceMappingURL=index.js.map
7894
8444
  //# sourceMappingURL=index.js.map