lecom-ui 5.4.32 → 5.4.34

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.
@@ -56,14 +56,19 @@ const MultiSelect = React.forwardRef(
56
56
  allowTypoDistance = 0,
57
57
  groupedOptions,
58
58
  classNameContent,
59
- size = "medium"
59
+ size = "medium",
60
+ disabled = false,
61
+ readOnly = false
60
62
  }, ref) => {
61
63
  const { t } = useTranslation();
62
64
  const [selectedValues, setSelectedValues] = React.useState(value);
63
65
  const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
64
66
  const [query, setQuery] = React.useState("");
65
- const [dynamicMaxCount, setDynamicMaxCount] = React.useState(selectedValues.length || 1);
67
+ const [dynamicMaxCount, setDynamicMaxCount] = React.useState(
68
+ selectedValues.length || 1
69
+ );
66
70
  const buttonRef = React.useRef(null);
71
+ const tagsContainerRef = React.useRef(null);
67
72
  React.useEffect(() => {
68
73
  const shallowEqual = (a, b) => {
69
74
  if (a === b) return true;
@@ -80,7 +85,7 @@ const MultiSelect = React.forwardRef(
80
85
  }
81
86
  if (selectedValues.length === 0) return;
82
87
  const calculateMaxCount = () => {
83
- if (!buttonRef.current) {
88
+ if (!buttonRef.current || !tagsContainerRef.current) {
84
89
  setTimeout(() => calculateMaxCount(), 10);
85
90
  return;
86
91
  }
@@ -89,57 +94,59 @@ const MultiSelect = React.forwardRef(
89
94
  setTimeout(() => calculateMaxCount(), 10);
90
95
  return;
91
96
  }
92
- const CONTROLS_WIDTH = 80;
93
- const COUNTER_TAG_WIDTH = 41;
94
97
  const TAG_GAP = 4;
95
- const CONTAINER_PADDING = 8;
96
- const AVG_CHAR_WIDTH = 8;
97
- const TAG_PADDING = 24;
98
- const availableWidth = buttonWidth - CONTAINER_PADDING * 2 - CONTROLS_WIDTH;
98
+ const BUTTON_PADDING = 4;
99
+ const TAGS_CONTAINER_PADDING = 8;
100
+ const COUNTER_TAG_MAX_WIDTH = 65;
101
+ const ICONS_WIDTH = !disabled && !readOnly ? 48 : 20;
102
+ const availableWidth = buttonWidth - ICONS_WIDTH - BUTTON_PADDING * 2 - TAGS_CONTAINER_PADDING * 2 - TAG_GAP;
99
103
  if (availableWidth <= 0) {
100
104
  setDynamicMaxCount(1);
101
105
  return;
102
106
  }
103
- const getTreeLabel = (val) => {
104
- if (!treeOptions) return void 0;
105
- const stack = [...treeOptions];
106
- while (stack.length) {
107
- const n = stack.pop();
108
- if (n.value === val) return n.label;
109
- if (n.children) stack.push(...n.children);
107
+ const tagElements = tagsContainerRef.current.querySelectorAll("[data-tag-item]");
108
+ if (tagElements.length === 0) {
109
+ setDynamicMaxCount(selectedValues.length);
110
+ return;
111
+ }
112
+ const tagWidths = [];
113
+ tagElements.forEach((element) => {
114
+ if (!element.hasAttribute("data-counter-tag")) {
115
+ tagWidths.push(element.offsetWidth);
110
116
  }
111
- return void 0;
112
- };
113
- const estimatedWidths = [];
114
- for (let i = 0; i < selectedValues.length; i++) {
115
- const label = getTreeLabel(selectedValues[i]) || options.find((o) => o.value === selectedValues[i])?.label || selectedValues[i];
116
- const estimatedWidth = Math.min(
117
- label.length * AVG_CHAR_WIDTH + TAG_PADDING,
118
- 250
119
- );
120
- estimatedWidths.push(estimatedWidth);
117
+ });
118
+ if (tagWidths.length === 0) {
119
+ setTimeout(() => calculateMaxCount(), 10);
120
+ return;
121
121
  }
122
- let totalWidth = 0;
123
- for (let i = 0; i < estimatedWidths.length; i++) {
124
- totalWidth += estimatedWidths[i] + (i > 0 ? TAG_GAP : 0);
122
+ let totalWidthWithoutCounter = 0;
123
+ let countWithoutCounter = 0;
124
+ for (let i = 0; i < tagWidths.length; i++) {
125
+ const widthNeeded = tagWidths[i] + (i > 0 ? TAG_GAP : 0);
126
+ if (totalWidthWithoutCounter + widthNeeded <= availableWidth) {
127
+ totalWidthWithoutCounter += widthNeeded;
128
+ countWithoutCounter++;
129
+ } else {
130
+ break;
131
+ }
125
132
  }
126
- if (totalWidth <= availableWidth) {
133
+ if (countWithoutCounter >= selectedValues.length) {
127
134
  setDynamicMaxCount(selectedValues.length);
128
135
  return;
129
136
  }
130
- const availableWithCounter = availableWidth - COUNTER_TAG_WIDTH - TAG_GAP;
131
- let count = 0;
132
- totalWidth = 0;
133
- for (let i = 0; i < estimatedWidths.length; i++) {
134
- const tagWidth = estimatedWidths[i] + (i > 0 ? TAG_GAP : 0);
135
- if (totalWidth + tagWidth <= availableWithCounter) {
136
- totalWidth += tagWidth;
137
- count++;
137
+ const availableWidthWithCounter = availableWidth - COUNTER_TAG_MAX_WIDTH - TAG_GAP;
138
+ let totalWidthWithCounter = 0;
139
+ let countWithCounter = 0;
140
+ for (let i = 0; i < tagWidths.length; i++) {
141
+ const widthNeeded = tagWidths[i] + (i > 0 ? TAG_GAP : 0);
142
+ if (totalWidthWithCounter + widthNeeded <= availableWidthWithCounter) {
143
+ totalWidthWithCounter += widthNeeded;
144
+ countWithCounter++;
138
145
  } else {
139
146
  break;
140
147
  }
141
148
  }
142
- setDynamicMaxCount(Math.max(1, count));
149
+ setDynamicMaxCount(Math.max(1, countWithCounter));
143
150
  };
144
151
  const rafId = requestAnimationFrame(() => {
145
152
  calculateMaxCount();
@@ -154,7 +161,7 @@ const MultiSelect = React.forwardRef(
154
161
  cancelAnimationFrame(rafId);
155
162
  resizeObserver.disconnect();
156
163
  };
157
- }, [maxCount, selectedValues, treeOptions, options]);
164
+ }, [maxCount, selectedValues, treeOptions, options, disabled, readOnly]);
158
165
  const handleInputKeyDown = (event) => {
159
166
  if (event.key === "Enter") {
160
167
  setIsPopoverOpen(true);
@@ -166,11 +173,13 @@ const MultiSelect = React.forwardRef(
166
173
  }
167
174
  };
168
175
  const toggleOption = (option) => {
176
+ if (readOnly) return;
169
177
  const newSelectedValues = selectedValues.includes(option) ? selectedValues.filter((value2) => value2 !== option) : [...selectedValues, option];
170
178
  setSelectedValues(newSelectedValues);
171
179
  onValueChange(newSelectedValues);
172
180
  };
173
181
  const handleClear = () => {
182
+ if (readOnly) return;
174
183
  setSelectedValues([]);
175
184
  onValueChange([]);
176
185
  };
@@ -178,6 +187,7 @@ const MultiSelect = React.forwardRef(
178
187
  setIsPopoverOpen((prev) => !prev);
179
188
  };
180
189
  const toggleAll = () => {
190
+ if (readOnly) return;
181
191
  if (treeOptions && treeOptions.length) {
182
192
  const gather = (acc, nodes) => {
183
193
  for (const n of nodes) {
@@ -340,7 +350,10 @@ const MultiSelect = React.forwardRef(
340
350
  if (!node.children || node.children.length === 0) {
341
351
  return [node.value];
342
352
  }
343
- return [node.value, ...node.children.flatMap((c) => collectAllValues(c))];
353
+ return [
354
+ node.value,
355
+ ...node.children.flatMap((c) => collectAllValues(c))
356
+ ];
344
357
  }
345
358
  };
346
359
  const isNodeFullySelected = (node) => {
@@ -371,6 +384,7 @@ const MultiSelect = React.forwardRef(
371
384
  }
372
385
  };
373
386
  const toggleTreeNode = (node) => {
387
+ if (readOnly) return;
374
388
  if (treeSelectionStrategy === "all") {
375
389
  const isSelected = selectedValues.includes(node.value);
376
390
  const next = isSelected ? selectedValues.filter((v) => v !== node.value) : [...selectedValues, node.value];
@@ -454,7 +468,8 @@ const MultiSelect = React.forwardRef(
454
468
  {
455
469
  className: cn(
456
470
  "flex items-center gap-2 px-2 py-1 cursor-pointer rounded-sm text-grey-800 hover:bg-grey-100",
457
- (fully || isSelectedLeaf) && "bg-blue-50"
471
+ (fully || isSelectedLeaf) && "bg-blue-50",
472
+ readOnly && "cursor-default hover:bg-transparent"
458
473
  ),
459
474
  style: { paddingLeft: depth * 14 + 8 }
460
475
  },
@@ -467,7 +482,9 @@ const MultiSelect = React.forwardRef(
467
482
  ),
468
483
  onClick: (e) => {
469
484
  e.stopPropagation();
470
- toggleTreeNode(n);
485
+ if (!readOnly) {
486
+ toggleTreeNode(n);
487
+ }
471
488
  }
472
489
  },
473
490
  fully && /* @__PURE__ */ React.createElement(Check, { className: "!h-3 !w-3 !text-white", strokeWidth: 3 }),
@@ -480,7 +497,9 @@ const MultiSelect = React.forwardRef(
480
497
  className: "flex-1 truncate cursor-pointer overflow-hidden",
481
498
  onClick: (e) => {
482
499
  e.stopPropagation();
483
- toggleTreeNode(n);
500
+ if (!readOnly) {
501
+ toggleTreeNode(n);
502
+ }
484
503
  },
485
504
  title: n.label
486
505
  },
@@ -514,43 +533,65 @@ const MultiSelect = React.forwardRef(
514
533
  transition-all duration-300 outline-none
515
534
  [&_svg]:pointer-events-auto`,
516
535
  maxCount === void 0 && TRIGGER_HEIGHT_CLASSES[size],
536
+ disabled && "opacity-50 cursor-not-allowed pointer-events-none",
537
+ readOnly && !disabled && "opacity-50 hover:border-grey-400",
517
538
  className
518
539
  ),
519
- "aria-expanded": isPopoverOpen
540
+ "aria-expanded": isPopoverOpen,
541
+ disabled
520
542
  },
521
- selectedValues.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "flex justify-between items-center w-full" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-1" }, selectedValues.slice(0, dynamicMaxCount).map((value2) => {
522
- const selectedOption = options.find((option) => option.value === value2);
543
+ selectedValues.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "flex justify-between items-center w-full px-2" }, /* @__PURE__ */ React.createElement("div", { ref: tagsContainerRef, className: "flex items-center gap-1 flex-nowrap overflow-hidden" }, selectedValues.slice(0, dynamicMaxCount).map((value2) => {
544
+ const selectedOption = options.find(
545
+ (option) => option.value === value2
546
+ );
523
547
  const label = findTreeLabel(value2) || selectedOption?.label || value2;
524
548
  const prefix = selectedOption?.prefix;
525
- return /* @__PURE__ */ React.createElement(Tag, { key: value2, color: "blue", className: "focus:ring-0 flex items-center gap-2 max-w-[15.625rem]" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 truncate min-w-0" }, prefix && /* @__PURE__ */ React.createElement(
526
- Typography,
527
- {
528
- className: "flex items-center flex-shrink-0 [&_svg]:text-blue-600",
529
- textColor: "text-blue-600"
530
- },
531
- prefix
532
- ), /* @__PURE__ */ React.createElement(
533
- Typography,
549
+ return /* @__PURE__ */ React.createElement(
550
+ Tag,
534
551
  {
535
- variant: getFontVariant(size),
536
- textColor: "text-blue-600",
537
- className: "truncate"
552
+ key: value2,
553
+ color: "blue",
554
+ className: "focus:ring-0 flex items-center gap-2 max-w-[15.625rem]",
555
+ "data-tag-item": true
538
556
  },
539
- label
540
- )), /* @__PURE__ */ React.createElement(
541
- X,
542
- {
543
- className: "h-4 w-4 cursor-pointer flex-shrink-0",
544
- onClick: (event) => {
545
- event.stopPropagation();
546
- toggleOption(value2);
557
+ /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 truncate min-w-0" }, prefix && /* @__PURE__ */ React.createElement(
558
+ Typography,
559
+ {
560
+ className: "flex items-center flex-shrink-0 [&_svg]:text-blue-600",
561
+ textColor: "text-blue-600"
562
+ },
563
+ prefix
564
+ ), /* @__PURE__ */ React.createElement(
565
+ Typography,
566
+ {
567
+ variant: getFontVariant(size),
568
+ textColor: "text-blue-600",
569
+ className: "truncate"
570
+ },
571
+ label
572
+ )),
573
+ !disabled && !readOnly && /* @__PURE__ */ React.createElement(
574
+ X,
575
+ {
576
+ className: "h-5 w-5 cursor-pointer flex-shrink-0",
577
+ onClick: (event) => {
578
+ event.stopPropagation();
579
+ toggleOption(value2);
580
+ }
547
581
  }
548
- }
549
- ));
550
- }), selectedValues.length > dynamicMaxCount && /* @__PURE__ */ React.createElement(Tag, { color: "blue", className: "focus:ring-0" }, /* @__PURE__ */ React.createElement(Typography, { variant: getFontVariant(size), className: "text-blue-600" }, `+ ${selectedValues.length - dynamicMaxCount}`))), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement(
582
+ )
583
+ );
584
+ }), selectedValues.length > dynamicMaxCount && /* @__PURE__ */ React.createElement(Tag, { color: "blue", className: "focus:ring-0", "data-tag-item": true, "data-counter-tag": true }, /* @__PURE__ */ React.createElement(
585
+ Typography,
586
+ {
587
+ variant: getFontVariant(size),
588
+ className: "text-blue-600"
589
+ },
590
+ `+ ${selectedValues.length - dynamicMaxCount}`
591
+ ))), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, !disabled && !readOnly && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
551
592
  X,
552
593
  {
553
- className: "h-4 w-4 cursor-pointer text-grey-800",
594
+ className: "h-5 w-5 cursor-pointer text-grey-800",
554
595
  onClick: (event) => {
555
596
  event.stopPropagation();
556
597
  handleClear();
@@ -560,13 +601,13 @@ const MultiSelect = React.forwardRef(
560
601
  Separator,
561
602
  {
562
603
  orientation: "vertical",
563
- className: "flex min-h-4 mx-1 h-full"
604
+ className: "flex min-h-5 mx-1 h-full"
564
605
  }
565
- ), isPopoverOpen ? /* @__PURE__ */ React.createElement(ChevronUp, { className: "h-4 w-4 cursor-pointer text-grey-800" }) : /* @__PURE__ */ React.createElement(ChevronDown, { className: "h-4 w-4 cursor-pointer text-grey-800" }))) : /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between w-full mx-auto" }, /* @__PURE__ */ React.createElement(
606
+ )), isPopoverOpen ? /* @__PURE__ */ React.createElement(ChevronUp, { className: "h-5 w-5 cursor-pointer text-grey-800" }) : /* @__PURE__ */ React.createElement(ChevronDown, { className: "h-5 w-5 cursor-pointer text-grey-800" }))) : /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between w-full mx-auto" }, /* @__PURE__ */ React.createElement(
566
607
  Typography,
567
608
  {
568
609
  variant: getFontVariant(size),
569
- className: "text-grey-800 mx-3"
610
+ className: "text-grey-500 mx-3"
570
611
  },
571
612
  placeholder
572
613
  ), isPopoverOpen ? /* @__PURE__ */ React.createElement(ChevronUp, { className: "h-4 cursor-pointer text-grey-800 mx-2" }) : /* @__PURE__ */ React.createElement(ChevronDown, { className: "h-4 cursor-pointer text-grey-800 mx-2" }))
@@ -583,114 +624,165 @@ const MultiSelect = React.forwardRef(
583
624
  align: "start",
584
625
  onEscapeKeyDown: () => setIsPopoverOpen(false)
585
626
  },
586
- !isTree && /* @__PURE__ */ React.createElement(Command, { shouldFilter: false, className: SEARCH_INPUT_CLASSES[size] }, /* @__PURE__ */ React.createElement(
587
- CommandInput,
588
- {
589
- placeholder: treeSearchPlaceholder || t("multiSelect.search"),
590
- onKeyDown: handleInputKeyDown,
591
- value: query,
592
- onValueChange: (v) => setQuery(v)
593
- }
594
- ), /* @__PURE__ */ React.createElement(CommandList, null, /* @__PURE__ */ React.createElement(CommandGroup, null, (effectiveOptions.length === 0 || query && filteredOptions.length === 0) && /* @__PURE__ */ React.createElement(CommandEmpty, { className: cn("p-4 text-center text-grey-800", getFontVariant(size)) }, t("multiSelect.empty")), !query && effectiveOptions.length > 0 && /* @__PURE__ */ React.createElement(
595
- CommandItem,
627
+ !isTree && /* @__PURE__ */ React.createElement(
628
+ Command,
596
629
  {
597
- key: "all",
598
- onSelect: toggleAll,
599
- className: "cursor-pointer"
630
+ shouldFilter: false,
631
+ className: SEARCH_INPUT_CLASSES[size]
600
632
  },
601
633
  /* @__PURE__ */ React.createElement(
602
- "div",
634
+ CommandInput,
603
635
  {
604
- className: cn(
605
- "flex h-4 w-4 items-center justify-center rounded-sm border border-grey-800",
606
- selectedValues.length > 0 ? "bg-blue-600 border-blue-600 text-primary-foreground" : "opacity-50 [&_svg]:invisible"
607
- )
608
- },
609
- selectedValues.length === effectiveOptions.length ? /* @__PURE__ */ React.createElement(Check, { className: "!h-3 !w-3 !text-white", strokeWidth: 3 }) : /* @__PURE__ */ React.createElement(Minus, { className: "!h-3 !w-3 !text-white", strokeWidth: 3 })
636
+ placeholder: treeSearchPlaceholder || t("multiSelect.search"),
637
+ onKeyDown: handleInputKeyDown,
638
+ value: query,
639
+ onValueChange: (v) => setQuery(v),
640
+ disabled: readOnly
641
+ }
610
642
  ),
611
- /* @__PURE__ */ React.createElement(
612
- Typography,
643
+ /* @__PURE__ */ React.createElement(CommandList, null, /* @__PURE__ */ React.createElement(CommandGroup, null, (effectiveOptions.length === 0 || query && filteredOptions.length === 0) && /* @__PURE__ */ React.createElement(
644
+ CommandEmpty,
613
645
  {
614
- variant: getFontVariant(size),
615
- className: "text-grey-800"
616
- },
617
- selectAllLabel ?? t("multiSelect.selectAll")
618
- )
619
- ), isGrouped ? Object.entries(groupedOptions).map(
620
- ([categoryName, categoryOptions]) => {
621
- const filtered = categoryOptions.filter(
622
- (opt) => !query || effectiveOptions.some(
623
- (eff) => eff.value === opt.value && filteredOptions.some(
624
- (filt) => filt.value === opt.value
625
- )
646
+ className: cn(
647
+ "p-4 text-center text-grey-800",
648
+ getFontVariant(size)
626
649
  )
627
- );
628
- if (!filtered.length) return null;
629
- return /* @__PURE__ */ React.createElement("div", { key: categoryName }, /* @__PURE__ */ React.createElement("div", { className: "px-2 py-2 text-xs font-medium text-muted-foreground" }, categoryName), filtered.map((option) => {
630
- const isSelected = selectedValues.includes(
631
- option.value
632
- );
633
- return /* @__PURE__ */ React.createElement(
634
- CommandItem,
635
- {
636
- key: option.value,
637
- onSelect: () => toggleOption(option.value),
638
- className: "cursor-pointer"
639
- },
640
- /* @__PURE__ */ React.createElement(
641
- "div",
642
- {
643
- className: cn(
644
- "flex h-4 w-4 items-center justify-center rounded-sm border-2 border-grey-400 flex-shrink-0",
645
- isSelected ? "bg-blue-600 border-blue-600 text-primary-foreground" : "opacity-50 [&_svg]:invisible"
646
- )
647
- },
648
- /* @__PURE__ */ React.createElement(Check, { className: "!h-3 !w-3 !text-white", strokeWidth: 3 })
649
- ),
650
- /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 truncate min-w-0" }, option.prefix && /* @__PURE__ */ React.createElement(Typography, { className: "flex items-center flex-shrink-0" }, option.prefix), /* @__PURE__ */ React.createElement(
651
- Typography,
652
- {
653
- variant: getFontVariant(size),
654
- className: "text-grey-800 truncate overflow-hidden",
655
- title: option.label
656
- },
657
- highlight(option.label, query)
658
- ))
659
- );
660
- }));
661
- }
662
- ) : filteredOptions.map((option) => {
663
- const isSelected = selectedValues.includes(
664
- option.value
665
- );
666
- return /* @__PURE__ */ React.createElement(
650
+ },
651
+ t("multiSelect.empty")
652
+ ), !query && effectiveOptions.length > 0 && /* @__PURE__ */ React.createElement(
667
653
  CommandItem,
668
654
  {
669
- key: option.value,
670
- onSelect: () => toggleOption(option.value),
671
- className: "cursor-pointer"
655
+ key: "all",
656
+ onSelect: readOnly ? void 0 : toggleAll,
657
+ className: cn(
658
+ "cursor-pointer",
659
+ readOnly && "cursor-default pointer-events-none hover:bg-transparent"
660
+ )
672
661
  },
673
662
  /* @__PURE__ */ React.createElement(
674
663
  "div",
675
664
  {
676
665
  className: cn(
677
- "flex h-4 w-4 items-center justify-center rounded-sm border border-grey-800 flex-shrink-0",
678
- isSelected ? "bg-blue-600 border-blue-600 text-primary-foreground" : "opacity-50 [&_svg]:invisible"
666
+ "flex h-4 w-4 items-center justify-center rounded-sm border border-grey-800",
667
+ selectedValues.length > 0 ? "bg-blue-600 border-blue-600 text-primary-foreground" : "opacity-50 [&_svg]:invisible"
679
668
  )
680
669
  },
681
- /* @__PURE__ */ React.createElement(Check, { className: "!h-3 !w-3 !text-white", strokeWidth: 3 })
670
+ selectedValues.length === effectiveOptions.length ? /* @__PURE__ */ React.createElement(
671
+ Check,
672
+ {
673
+ className: "!h-3 !w-3 !text-white",
674
+ strokeWidth: 3
675
+ }
676
+ ) : /* @__PURE__ */ React.createElement(
677
+ Minus,
678
+ {
679
+ className: "!h-3 !w-3 !text-white",
680
+ strokeWidth: 3
681
+ }
682
+ )
682
683
  ),
683
- /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 truncate min-w-0" }, option.prefix && /* @__PURE__ */ React.createElement(Typography, { className: "flex items-center flex-shrink-0" }, option.prefix), /* @__PURE__ */ React.createElement(
684
+ /* @__PURE__ */ React.createElement(
684
685
  Typography,
685
686
  {
686
687
  variant: getFontVariant(size),
687
- className: "text-grey-800 truncate overflow-hidden",
688
- title: option.label
688
+ className: "text-grey-800"
689
689
  },
690
- highlight(option.label, query)
691
- ))
692
- );
693
- })))),
690
+ selectAllLabel ?? t("multiSelect.selectAll")
691
+ )
692
+ ), isGrouped ? Object.entries(groupedOptions).map(
693
+ ([categoryName, categoryOptions]) => {
694
+ const filtered = categoryOptions.filter(
695
+ (opt) => !query || effectiveOptions.some(
696
+ (eff) => eff.value === opt.value && filteredOptions.some(
697
+ (filt) => filt.value === opt.value
698
+ )
699
+ )
700
+ );
701
+ if (!filtered.length) return null;
702
+ return /* @__PURE__ */ React.createElement("div", { key: categoryName }, /* @__PURE__ */ React.createElement("div", { className: "px-2 py-2 text-xs font-medium text-muted-foreground" }, categoryName), filtered.map((option) => {
703
+ const isSelected = selectedValues.includes(
704
+ option.value
705
+ );
706
+ return /* @__PURE__ */ React.createElement(
707
+ CommandItem,
708
+ {
709
+ key: option.value,
710
+ onSelect: readOnly ? void 0 : () => toggleOption(option.value),
711
+ className: cn(
712
+ "cursor-pointer",
713
+ readOnly && "cursor-default pointer-events-none hover:bg-transparent"
714
+ )
715
+ },
716
+ /* @__PURE__ */ React.createElement(
717
+ "div",
718
+ {
719
+ className: cn(
720
+ "flex h-4 w-4 items-center justify-center rounded-sm border-2 border-grey-400 flex-shrink-0",
721
+ isSelected ? "bg-blue-600 border-blue-600 text-primary-foreground" : "opacity-50 [&_svg]:invisible"
722
+ )
723
+ },
724
+ /* @__PURE__ */ React.createElement(
725
+ Check,
726
+ {
727
+ className: "!h-3 !w-3 !text-white",
728
+ strokeWidth: 3
729
+ }
730
+ )
731
+ ),
732
+ /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 min-w-0" }, option.prefix && /* @__PURE__ */ React.createElement(Typography, { className: "flex items-center flex-shrink-0" }, option.prefix), /* @__PURE__ */ React.createElement(
733
+ Typography,
734
+ {
735
+ variant: getFontVariant(size),
736
+ className: "text-grey-800 break-words",
737
+ title: option.label
738
+ },
739
+ highlight(option.label, query)
740
+ ))
741
+ );
742
+ }));
743
+ }
744
+ ) : filteredOptions.map((option) => {
745
+ const isSelected = selectedValues.includes(
746
+ option.value
747
+ );
748
+ return /* @__PURE__ */ React.createElement(
749
+ CommandItem,
750
+ {
751
+ key: option.value,
752
+ onSelect: readOnly ? void 0 : () => toggleOption(option.value),
753
+ className: cn(
754
+ "cursor-pointer",
755
+ readOnly && "cursor-default pointer-events-none hover:bg-transparent"
756
+ )
757
+ },
758
+ /* @__PURE__ */ React.createElement(
759
+ "div",
760
+ {
761
+ className: cn(
762
+ "flex h-4 w-4 items-center justify-center rounded-sm border border-grey-800 flex-shrink-0",
763
+ isSelected ? "bg-blue-600 border-blue-600 text-primary-foreground" : "opacity-50 [&_svg]:invisible"
764
+ )
765
+ },
766
+ /* @__PURE__ */ React.createElement(
767
+ Check,
768
+ {
769
+ className: "!h-3 !w-3 !text-white",
770
+ strokeWidth: 3
771
+ }
772
+ )
773
+ ),
774
+ /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 min-w-0" }, option.prefix && /* @__PURE__ */ React.createElement(Typography, { className: "flex items-center flex-shrink-0" }, option.prefix), /* @__PURE__ */ React.createElement(
775
+ Typography,
776
+ {
777
+ variant: getFontVariant(size),
778
+ className: "text-grey-800 break-words",
779
+ title: option.label
780
+ },
781
+ highlight(option.label, query)
782
+ ))
783
+ );
784
+ })))
785
+ ),
694
786
  isTree && /* @__PURE__ */ React.createElement("div", { className: "min-w-[260px] max-h-80 overflow-hidden flex flex-col" }, treeSearch && /* @__PURE__ */ React.createElement("div", { className: "px-1 pt-2 pb-0 border-b border-grey-300 flex-shrink-0" }, /* @__PURE__ */ React.createElement("div", { className: "relative" }, /* @__PURE__ */ React.createElement(
695
787
  "svg",
696
788
  {
@@ -715,6 +807,7 @@ const MultiSelect = React.forwardRef(
715
807
  value: treeQuery,
716
808
  onChange: (e) => setTreeQuery(e.target.value),
717
809
  placeholder: treeSearchPlaceholder || t("multiSelect.search"),
810
+ disabled: readOnly,
718
811
  className: cn(
719
812
  "w-full pl-9 pr-3 border-0 focus:outline-none focus:ring-0 bg-transparent placeholder:text-muted-foreground",
720
813
  getFontVariant(size),
@@ -724,8 +817,11 @@ const MultiSelect = React.forwardRef(
724
817
  ))), /* @__PURE__ */ React.createElement("div", { className: "px-2 pb-1 overflow-y-auto overflow-x-hidden [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-grey-300 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb:hover]:bg-grey-400" }, !treeQuery && treeOptions && treeOptions.length > 0 && /* @__PURE__ */ React.createElement(
725
818
  "div",
726
819
  {
727
- className: "flex items-center gap-2 px-2 py-1 cursor-pointer rounded-sm hover:bg-accent",
728
- onClick: toggleAll
820
+ className: cn(
821
+ "flex items-center gap-2 px-2 py-1 cursor-pointer rounded-sm hover:bg-accent",
822
+ readOnly && "cursor-default hover:bg-transparent pointer-events-none"
823
+ ),
824
+ onClick: readOnly ? void 0 : toggleAll
729
825
  },
730
826
  /* @__PURE__ */ React.createElement(
731
827
  "div",
@@ -745,7 +841,19 @@ const MultiSelect = React.forwardRef(
745
841
  acc += treeSelectionStrategy === "all" ? 1 + countAll(n.children) : countAll(n.children);
746
842
  }
747
843
  return acc;
748
- }(treeOptions) ? /* @__PURE__ */ React.createElement(Check, { className: "!h-3 !w-3 !text-white", strokeWidth: 3 }) : /* @__PURE__ */ React.createElement(Minus, { className: "!h-3 !w-3 !text-white", strokeWidth: 3 })
844
+ }(treeOptions) ? /* @__PURE__ */ React.createElement(
845
+ Check,
846
+ {
847
+ className: "!h-3 !w-3 !text-white",
848
+ strokeWidth: 3
849
+ }
850
+ ) : /* @__PURE__ */ React.createElement(
851
+ Minus,
852
+ {
853
+ className: "!h-3 !w-3 !text-white",
854
+ strokeWidth: 3
855
+ }
856
+ )
749
857
  ),
750
858
  /* @__PURE__ */ React.createElement(
751
859
  Typography,
@@ -755,7 +863,16 @@ const MultiSelect = React.forwardRef(
755
863
  },
756
864
  selectAllLabel ?? t("multiSelect.selectAll")
757
865
  )
758
- ), !treeOptions || treeOptions.length === 0 || treeQuery && displayedTree.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: cn("p-4 text-center text-grey-800", getFontVariant(size)) }, t("multiSelect.empty")) : renderTree(displayedTree)))
866
+ ), !treeOptions || treeOptions.length === 0 || treeQuery && displayedTree.length === 0 ? /* @__PURE__ */ React.createElement(
867
+ "div",
868
+ {
869
+ className: cn(
870
+ "p-4 text-center text-grey-800",
871
+ getFontVariant(size)
872
+ )
873
+ },
874
+ t("multiSelect.empty")
875
+ ) : renderTree(displayedTree)))
759
876
  )
760
877
  );
761
878
  }