lecom-ui 5.3.79 → 5.3.81
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/README.md +1 -1
- package/dist/components/Combobox/Combobox.js +5 -5
- package/dist/components/MultiSelect/MultiSelect.js +176 -45
- package/dist/components/TagInput/TagInput.js +111 -76
- package/dist/index.d.ts +13 -10
- package/dist/plugin/extend.js +78 -78
- package/dist/plugin/fontFaces.js +172 -172
- package/dist/plugin/general.js +12 -12
- package/dist/plugin/pluginDev.cjs +5 -5
- package/dist/plugin/pluginNext.cjs +5 -5
- package/dist/plugin/pluginNextTurbo.cjs +5 -5
- package/dist/plugin/pluginVite.cjs +5 -5
- package/dist/plugin/template.js +31 -31
- package/dist/plugin/typographies.js +152 -152
- package/dist/plugin/varsTheme.js +79 -79
- package/dist/style.min.css +1 -1
- package/package.json +1 -1
- package/dist/components/Collapse/Collapse.js +0 -94
package/README.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
lecom-ui
|
|
1
|
+
lecom-ui
|
|
@@ -69,7 +69,7 @@ const comboboxItemVariants = cva(
|
|
|
69
69
|
false: ""
|
|
70
70
|
},
|
|
71
71
|
selected: {
|
|
72
|
-
true: "bg-grey-100
|
|
72
|
+
true: "bg-grey-100 text-grey-900",
|
|
73
73
|
false: ""
|
|
74
74
|
}
|
|
75
75
|
},
|
|
@@ -241,7 +241,7 @@ const ComboboxTriggerButton = React.forwardRef(
|
|
|
241
241
|
disabled: disabled ?? false,
|
|
242
242
|
...props
|
|
243
243
|
},
|
|
244
|
-
/* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 truncate
|
|
244
|
+
/* @__PURE__ */ React.createElement("div", { className: cn("flex items-center gap-2 truncate min-w-[calc(100%-2.25rem)]", selectedPrefix && "min-w-[calc(100%-4.75rem)]") }, selectedPrefix && /* @__PURE__ */ React.createElement(Typography, { className: "flex items-center shrink-0" }, selectedPrefix), /* @__PURE__ */ React.createElement(
|
|
245
245
|
Typography,
|
|
246
246
|
{
|
|
247
247
|
className: cn("truncate", colorClass),
|
|
@@ -249,13 +249,13 @@ const ComboboxTriggerButton = React.forwardRef(
|
|
|
249
249
|
},
|
|
250
250
|
selectedLabel
|
|
251
251
|
)),
|
|
252
|
-
/* @__PURE__ */ React.createElement(
|
|
252
|
+
/* @__PURE__ */ React.createElement("div", { className: "w-5" }, /* @__PURE__ */ React.createElement(
|
|
253
253
|
ChevronIcon,
|
|
254
254
|
{
|
|
255
|
-
className: cn("
|
|
255
|
+
className: cn("shrink-0", colorClass),
|
|
256
256
|
size: ICON_SIZE
|
|
257
257
|
}
|
|
258
|
-
)
|
|
258
|
+
))
|
|
259
259
|
);
|
|
260
260
|
}
|
|
261
261
|
);
|
|
@@ -10,13 +10,36 @@ import { X, ChevronUp, ChevronDown, Check, Minus } from 'lucide-react';
|
|
|
10
10
|
import { initializeI18n } from '../../i18n/index.js';
|
|
11
11
|
|
|
12
12
|
initializeI18n();
|
|
13
|
+
const TRIGGER_HEIGHT_CLASSES = {
|
|
14
|
+
small: "!h-8",
|
|
15
|
+
medium: "!h-10",
|
|
16
|
+
large: "!h-12"
|
|
17
|
+
};
|
|
18
|
+
const SEARCH_INPUT_HEIGHT_CLASSES = {
|
|
19
|
+
small: "h-7",
|
|
20
|
+
medium: "h-8",
|
|
21
|
+
large: "h-9"
|
|
22
|
+
};
|
|
23
|
+
const SEARCH_INPUT_CLASSES = {
|
|
24
|
+
small: "[&_[cmdk-input]]:h-7 [&_[cmdk-input]]:body-small-400 [&_svg]:text-grey-800 [&_svg]:opacity-100 [&_svg]:h-4 [&_svg]:w-4",
|
|
25
|
+
medium: "[&_[cmdk-input]]:h-8 [&_[cmdk-input]]:body-medium-400 [&_svg]:text-grey-800 [&_svg]:opacity-100 [&_svg]:h-4 [&_svg]:w-4",
|
|
26
|
+
large: "[&_[cmdk-input]]:h-9 [&_[cmdk-input]]:body-large-400 [&_svg]:text-grey-800 [&_svg]:opacity-100 [&_svg]:h-4 [&_svg]:w-4"
|
|
27
|
+
};
|
|
28
|
+
const getFontVariant = (size) => {
|
|
29
|
+
const sizeMap = {
|
|
30
|
+
small: "body-small-400",
|
|
31
|
+
medium: "body-medium-400",
|
|
32
|
+
large: "body-large-400"
|
|
33
|
+
};
|
|
34
|
+
return sizeMap[size ?? "medium"];
|
|
35
|
+
};
|
|
13
36
|
const MultiSelect = React.forwardRef(
|
|
14
37
|
({
|
|
15
38
|
options = [],
|
|
16
39
|
onValueChange,
|
|
17
40
|
value = [],
|
|
18
41
|
placeholder = "Select options",
|
|
19
|
-
maxCount
|
|
42
|
+
maxCount,
|
|
20
43
|
modalPopover = false,
|
|
21
44
|
className,
|
|
22
45
|
side = "bottom",
|
|
@@ -32,12 +55,15 @@ const MultiSelect = React.forwardRef(
|
|
|
32
55
|
highlightMatches = true,
|
|
33
56
|
allowTypoDistance = 0,
|
|
34
57
|
groupedOptions,
|
|
35
|
-
classNameContent
|
|
58
|
+
classNameContent,
|
|
59
|
+
size = "medium"
|
|
36
60
|
}, ref) => {
|
|
37
61
|
const { t } = useTranslation();
|
|
38
62
|
const [selectedValues, setSelectedValues] = React.useState(value);
|
|
39
63
|
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
|
|
40
64
|
const [query, setQuery] = React.useState("");
|
|
65
|
+
const [dynamicMaxCount, setDynamicMaxCount] = React.useState(selectedValues.length || 1);
|
|
66
|
+
const buttonRef = React.useRef(null);
|
|
41
67
|
React.useEffect(() => {
|
|
42
68
|
const shallowEqual = (a, b) => {
|
|
43
69
|
if (a === b) return true;
|
|
@@ -47,6 +73,88 @@ const MultiSelect = React.forwardRef(
|
|
|
47
73
|
};
|
|
48
74
|
setSelectedValues((prev) => shallowEqual(prev, value) ? prev : value);
|
|
49
75
|
}, [value]);
|
|
76
|
+
React.useEffect(() => {
|
|
77
|
+
if (maxCount !== void 0) {
|
|
78
|
+
setDynamicMaxCount(maxCount);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (selectedValues.length === 0) return;
|
|
82
|
+
const calculateMaxCount = () => {
|
|
83
|
+
if (!buttonRef.current) {
|
|
84
|
+
setTimeout(() => calculateMaxCount(), 10);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const buttonWidth = buttonRef.current.offsetWidth;
|
|
88
|
+
if (buttonWidth === 0) {
|
|
89
|
+
setTimeout(() => calculateMaxCount(), 10);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const CONTROLS_WIDTH = 80;
|
|
93
|
+
const COUNTER_TAG_WIDTH = 41;
|
|
94
|
+
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;
|
|
99
|
+
if (availableWidth <= 0) {
|
|
100
|
+
setDynamicMaxCount(1);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
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);
|
|
110
|
+
}
|
|
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);
|
|
121
|
+
}
|
|
122
|
+
let totalWidth = 0;
|
|
123
|
+
for (let i = 0; i < estimatedWidths.length; i++) {
|
|
124
|
+
totalWidth += estimatedWidths[i] + (i > 0 ? TAG_GAP : 0);
|
|
125
|
+
}
|
|
126
|
+
if (totalWidth <= availableWidth) {
|
|
127
|
+
setDynamicMaxCount(selectedValues.length);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
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++;
|
|
138
|
+
} else {
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
setDynamicMaxCount(Math.max(1, count));
|
|
143
|
+
};
|
|
144
|
+
const rafId = requestAnimationFrame(() => {
|
|
145
|
+
calculateMaxCount();
|
|
146
|
+
});
|
|
147
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
148
|
+
calculateMaxCount();
|
|
149
|
+
});
|
|
150
|
+
if (buttonRef.current) {
|
|
151
|
+
resizeObserver.observe(buttonRef.current);
|
|
152
|
+
}
|
|
153
|
+
return () => {
|
|
154
|
+
cancelAnimationFrame(rafId);
|
|
155
|
+
resizeObserver.disconnect();
|
|
156
|
+
};
|
|
157
|
+
}, [maxCount, selectedValues, treeOptions, options]);
|
|
50
158
|
const handleInputKeyDown = (event) => {
|
|
51
159
|
if (event.key === "Enter") {
|
|
52
160
|
setIsPopoverOpen(true);
|
|
@@ -69,11 +177,6 @@ const MultiSelect = React.forwardRef(
|
|
|
69
177
|
const handleTogglePopover = () => {
|
|
70
178
|
setIsPopoverOpen((prev) => !prev);
|
|
71
179
|
};
|
|
72
|
-
const clearExtraOptions = () => {
|
|
73
|
-
const newSelectedValues = selectedValues.slice(0, maxCount);
|
|
74
|
-
setSelectedValues(newSelectedValues);
|
|
75
|
-
onValueChange(newSelectedValues);
|
|
76
|
-
};
|
|
77
180
|
const toggleAll = () => {
|
|
78
181
|
if (treeOptions && treeOptions.length) {
|
|
79
182
|
const gather = (acc, nodes) => {
|
|
@@ -320,7 +423,8 @@ const MultiSelect = React.forwardRef(
|
|
|
320
423
|
"div",
|
|
321
424
|
{
|
|
322
425
|
className: cn(
|
|
323
|
-
"flex items-center gap-2 px-2 py-1 cursor-pointer rounded-sm text-grey-800 hover:bg-grey-100
|
|
426
|
+
"flex items-center gap-2 px-2 py-1 cursor-pointer rounded-sm text-grey-800 hover:bg-grey-100",
|
|
427
|
+
getFontVariant(size),
|
|
324
428
|
(fully || isSelectedLeaf) && "bg-blue-50"
|
|
325
429
|
),
|
|
326
430
|
style: { paddingLeft: depth * 14 + 8 }
|
|
@@ -337,13 +441,13 @@ const MultiSelect = React.forwardRef(
|
|
|
337
441
|
toggleTreeNode(n);
|
|
338
442
|
}
|
|
339
443
|
},
|
|
340
|
-
fully && /* @__PURE__ */ React.createElement(Check, { className: "h-3 w-3" }),
|
|
444
|
+
fully && /* @__PURE__ */ React.createElement(Check, { className: "!h-3 !w-3 !text-white", strokeWidth: 3 }),
|
|
341
445
|
partial && !fully && /* @__PURE__ */ React.createElement(Minus, { className: "h-3 w-3 text-blue-600" })
|
|
342
446
|
),
|
|
343
447
|
/* @__PURE__ */ React.createElement(
|
|
344
448
|
"span",
|
|
345
449
|
{
|
|
346
|
-
className: "
|
|
450
|
+
className: "flex-1 truncate cursor-pointer overflow-hidden",
|
|
347
451
|
onClick: (e) => {
|
|
348
452
|
e.stopPropagation();
|
|
349
453
|
toggleTreeNode(n);
|
|
@@ -364,25 +468,34 @@ const MultiSelect = React.forwardRef(
|
|
|
364
468
|
/* @__PURE__ */ React.createElement(PopoverTrigger, { asChild: true }, /* @__PURE__ */ React.createElement(
|
|
365
469
|
"button",
|
|
366
470
|
{
|
|
367
|
-
ref
|
|
471
|
+
ref: (node) => {
|
|
472
|
+
buttonRef.current = node;
|
|
473
|
+
if (typeof ref === "function") {
|
|
474
|
+
ref(node);
|
|
475
|
+
} else if (ref) {
|
|
476
|
+
ref.current = node;
|
|
477
|
+
}
|
|
478
|
+
},
|
|
368
479
|
onClick: handleTogglePopover,
|
|
369
480
|
className: cn(
|
|
370
|
-
`flex w-full p-1 rounded-
|
|
481
|
+
`flex w-full p-1 rounded-sm h-auto items-center justify-between bg-background
|
|
371
482
|
border border-grey-400
|
|
372
483
|
hover:border-grey-500 focus:border-grey-400 focus:ring-grey-600 focus:ring-opacity-15 focus:ring-4
|
|
373
484
|
transition-all duration-300 outline-none
|
|
374
485
|
[&_svg]:pointer-events-auto`,
|
|
486
|
+
TRIGGER_HEIGHT_CLASSES[size],
|
|
375
487
|
className
|
|
376
488
|
),
|
|
377
489
|
"aria-expanded": isPopoverOpen
|
|
378
490
|
},
|
|
379
|
-
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,
|
|
491
|
+
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) => {
|
|
380
492
|
const label = findTreeLabel(value2) || options.find((o) => o.value === value2)?.label || value2;
|
|
381
|
-
return /* @__PURE__ */ React.createElement(Tag, { key: value2, color: "blue", className: "focus:ring-0" }, /* @__PURE__ */ React.createElement(
|
|
493
|
+
return /* @__PURE__ */ React.createElement(Tag, { key: value2, color: "blue", className: "focus:ring-0 max-w-[15.625rem]" }, /* @__PURE__ */ React.createElement(
|
|
382
494
|
Typography,
|
|
383
495
|
{
|
|
384
|
-
variant:
|
|
385
|
-
textColor: "text-blue-600"
|
|
496
|
+
variant: getFontVariant(size),
|
|
497
|
+
textColor: "text-blue-600",
|
|
498
|
+
className: "truncate"
|
|
386
499
|
},
|
|
387
500
|
label
|
|
388
501
|
), /* @__PURE__ */ React.createElement(
|
|
@@ -395,19 +508,10 @@ const MultiSelect = React.forwardRef(
|
|
|
395
508
|
}
|
|
396
509
|
}
|
|
397
510
|
));
|
|
398
|
-
}), selectedValues.length >
|
|
511
|
+
}), 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(
|
|
399
512
|
X,
|
|
400
513
|
{
|
|
401
|
-
className: "h-4 w-4 cursor-pointer
|
|
402
|
-
onClick: (event) => {
|
|
403
|
-
event.stopPropagation();
|
|
404
|
-
clearExtraOptions();
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
))), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement(
|
|
408
|
-
X,
|
|
409
|
-
{
|
|
410
|
-
className: "h-4 mx-2 cursor-pointer text-grey-800",
|
|
514
|
+
className: "h-4 w-4 cursor-pointer text-grey-800",
|
|
411
515
|
onClick: (event) => {
|
|
412
516
|
event.stopPropagation();
|
|
413
517
|
handleClear();
|
|
@@ -417,9 +521,16 @@ const MultiSelect = React.forwardRef(
|
|
|
417
521
|
Separator,
|
|
418
522
|
{
|
|
419
523
|
orientation: "vertical",
|
|
420
|
-
className: "flex min-h-4 h-full"
|
|
524
|
+
className: "flex min-h-4 mx-1 h-full"
|
|
421
525
|
}
|
|
422
|
-
), isPopoverOpen ? /* @__PURE__ */ React.createElement(ChevronUp, { className: "h-4
|
|
526
|
+
), 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(
|
|
527
|
+
Typography,
|
|
528
|
+
{
|
|
529
|
+
variant: getFontVariant(size),
|
|
530
|
+
className: "text-grey-800 mx-3"
|
|
531
|
+
},
|
|
532
|
+
placeholder
|
|
533
|
+
), 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" }))
|
|
423
534
|
)),
|
|
424
535
|
/* @__PURE__ */ React.createElement(
|
|
425
536
|
PopoverContent,
|
|
@@ -433,7 +544,7 @@ const MultiSelect = React.forwardRef(
|
|
|
433
544
|
align: "start",
|
|
434
545
|
onEscapeKeyDown: () => setIsPopoverOpen(false)
|
|
435
546
|
},
|
|
436
|
-
!isTree && /* @__PURE__ */ React.createElement(Command, { shouldFilter: false }, /* @__PURE__ */ React.createElement(
|
|
547
|
+
!isTree && /* @__PURE__ */ React.createElement(Command, { shouldFilter: false, className: SEARCH_INPUT_CLASSES[size] }, /* @__PURE__ */ React.createElement(
|
|
437
548
|
CommandInput,
|
|
438
549
|
{
|
|
439
550
|
placeholder: treeSearchPlaceholder || t("multiSelect.search"),
|
|
@@ -441,7 +552,7 @@ const MultiSelect = React.forwardRef(
|
|
|
441
552
|
value: query,
|
|
442
553
|
onValueChange: (v) => setQuery(v)
|
|
443
554
|
}
|
|
444
|
-
), /* @__PURE__ */ React.createElement(CommandList, null, (!filteredOptions || filteredOptions.length === 0) && /* @__PURE__ */ React.createElement(CommandEmpty, { className: "p-4
|
|
555
|
+
), /* @__PURE__ */ React.createElement(CommandList, null, (!filteredOptions || filteredOptions.length === 0) && /* @__PURE__ */ React.createElement(CommandEmpty, { className: cn("p-4 text-center text-grey-800", getFontVariant(size)) }, t("multiSelect.empty")), /* @__PURE__ */ React.createElement(CommandGroup, null, !query && /* @__PURE__ */ React.createElement(
|
|
445
556
|
CommandItem,
|
|
446
557
|
{
|
|
447
558
|
key: "all",
|
|
@@ -452,13 +563,20 @@ const MultiSelect = React.forwardRef(
|
|
|
452
563
|
"div",
|
|
453
564
|
{
|
|
454
565
|
className: cn(
|
|
455
|
-
"flex h-4 w-4 items-center justify-center rounded-sm border
|
|
566
|
+
"flex h-4 w-4 items-center justify-center rounded-sm border border-grey-800",
|
|
456
567
|
selectedValues.length > 0 ? "bg-blue-600 border-blue-600 text-primary-foreground" : "opacity-50 [&_svg]:invisible"
|
|
457
568
|
)
|
|
458
569
|
},
|
|
459
|
-
selectedValues.length === effectiveOptions.length ? /* @__PURE__ */ React.createElement(Check, { className: "h-
|
|
570
|
+
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 })
|
|
460
571
|
),
|
|
461
|
-
/* @__PURE__ */ React.createElement(
|
|
572
|
+
/* @__PURE__ */ React.createElement(
|
|
573
|
+
Typography,
|
|
574
|
+
{
|
|
575
|
+
variant: getFontVariant(size),
|
|
576
|
+
className: "text-grey-800"
|
|
577
|
+
},
|
|
578
|
+
selectAllLabel ?? t("multiSelect.selectAll")
|
|
579
|
+
)
|
|
462
580
|
), isGrouped ? Object.entries(groupedOptions).map(
|
|
463
581
|
([categoryName, categoryOptions]) => {
|
|
464
582
|
const filtered = categoryOptions.filter(
|
|
@@ -488,11 +606,12 @@ const MultiSelect = React.forwardRef(
|
|
|
488
606
|
isSelected ? "bg-blue-600 border-blue-600 text-primary-foreground" : "opacity-50 [&_svg]:invisible"
|
|
489
607
|
)
|
|
490
608
|
},
|
|
491
|
-
/* @__PURE__ */ React.createElement(Check, { className: "h-
|
|
609
|
+
/* @__PURE__ */ React.createElement(Check, { className: "!h-3 !w-3 !text-white", strokeWidth: 3 })
|
|
492
610
|
),
|
|
493
611
|
/* @__PURE__ */ React.createElement(
|
|
494
|
-
|
|
612
|
+
Typography,
|
|
495
613
|
{
|
|
614
|
+
variant: getFontVariant(size),
|
|
496
615
|
className: "text-grey-800 truncate overflow-hidden",
|
|
497
616
|
title: option.label
|
|
498
617
|
},
|
|
@@ -516,15 +635,16 @@ const MultiSelect = React.forwardRef(
|
|
|
516
635
|
"div",
|
|
517
636
|
{
|
|
518
637
|
className: cn(
|
|
519
|
-
"flex h-4 w-4 items-center justify-center rounded-sm border
|
|
638
|
+
"flex h-4 w-4 items-center justify-center rounded-sm border border-grey-800 flex-shrink-0",
|
|
520
639
|
isSelected ? "bg-blue-600 border-blue-600 text-primary-foreground" : "opacity-50 [&_svg]:invisible"
|
|
521
640
|
)
|
|
522
641
|
},
|
|
523
|
-
/* @__PURE__ */ React.createElement(Check, { className: "h-
|
|
642
|
+
/* @__PURE__ */ React.createElement(Check, { className: "!h-3 !w-3 !text-white", strokeWidth: 3 })
|
|
524
643
|
),
|
|
525
644
|
/* @__PURE__ */ React.createElement(
|
|
526
|
-
|
|
645
|
+
Typography,
|
|
527
646
|
{
|
|
647
|
+
variant: getFontVariant(size),
|
|
528
648
|
className: "text-grey-800 truncate overflow-hidden",
|
|
529
649
|
title: option.label
|
|
530
650
|
},
|
|
@@ -545,7 +665,7 @@ const MultiSelect = React.forwardRef(
|
|
|
545
665
|
{
|
|
546
666
|
strokeLinecap: "round",
|
|
547
667
|
strokeLinejoin: "round",
|
|
548
|
-
strokeWidth:
|
|
668
|
+
strokeWidth: 3,
|
|
549
669
|
d: "M21 21l-4.35-4.35m0 0A7.5 7.5 0 1 0 5.25 5.25a7.5 7.5 0 0 0 11.4 11.4z"
|
|
550
670
|
}
|
|
551
671
|
)
|
|
@@ -556,19 +676,23 @@ const MultiSelect = React.forwardRef(
|
|
|
556
676
|
value: treeQuery,
|
|
557
677
|
onChange: (e) => setTreeQuery(e.target.value),
|
|
558
678
|
placeholder: treeSearchPlaceholder || t("multiSelect.search"),
|
|
559
|
-
className:
|
|
679
|
+
className: cn(
|
|
680
|
+
"w-full pl-9 pr-3 border-0 focus:outline-none focus:ring-0 bg-transparent placeholder:text-muted-foreground",
|
|
681
|
+
getFontVariant(size),
|
|
682
|
+
SEARCH_INPUT_HEIGHT_CLASSES[size]
|
|
683
|
+
)
|
|
560
684
|
}
|
|
561
685
|
))), /* @__PURE__ */ React.createElement("div", { className: "px-2 pb-1 overflow-y-auto overflow-x-hidden" }, !treeQuery && /* @__PURE__ */ React.createElement(
|
|
562
686
|
"div",
|
|
563
687
|
{
|
|
564
|
-
className: "flex items-center gap-2 px-2 py-1 cursor-pointer rounded-sm hover:bg-accent
|
|
688
|
+
className: "flex items-center gap-2 px-2 py-1 cursor-pointer rounded-sm hover:bg-accent",
|
|
565
689
|
onClick: toggleAll
|
|
566
690
|
},
|
|
567
691
|
/* @__PURE__ */ React.createElement(
|
|
568
692
|
"div",
|
|
569
693
|
{
|
|
570
694
|
className: cn(
|
|
571
|
-
"flex h-4 w-4 items-center justify-center rounded-sm border
|
|
695
|
+
"flex h-4 w-4 items-center justify-center rounded-sm border border-grey-800",
|
|
572
696
|
selectedValues.length > 0 ? "bg-blue-600 border-blue-600 text-primary-foreground" : "opacity-50 [&_svg]:invisible"
|
|
573
697
|
)
|
|
574
698
|
},
|
|
@@ -582,9 +706,16 @@ const MultiSelect = React.forwardRef(
|
|
|
582
706
|
acc += treeSelectionStrategy === "all" ? 1 + countAll(n.children) : countAll(n.children);
|
|
583
707
|
}
|
|
584
708
|
return acc;
|
|
585
|
-
}(treeOptions) ? /* @__PURE__ */ React.createElement(Check, { className: "h-
|
|
709
|
+
}(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 })
|
|
586
710
|
),
|
|
587
|
-
/* @__PURE__ */ React.createElement(
|
|
711
|
+
/* @__PURE__ */ React.createElement(
|
|
712
|
+
Typography,
|
|
713
|
+
{
|
|
714
|
+
variant: getFontVariant(size),
|
|
715
|
+
className: "text-grey-800"
|
|
716
|
+
},
|
|
717
|
+
selectAllLabel ?? t("multiSelect.selectAll")
|
|
718
|
+
)
|
|
588
719
|
), treeOptions && renderTree(displayedTree)))
|
|
589
720
|
)
|
|
590
721
|
);
|
|
@@ -13,10 +13,10 @@ function TagItem({ item, onRemove, readOnly }) {
|
|
|
13
13
|
},
|
|
14
14
|
[item.value, onRemove]
|
|
15
15
|
);
|
|
16
|
-
return /* @__PURE__ */ React.createElement(Tag, { "data-tag": "true", color: "blue" }, /* @__PURE__ */ React.createElement(Typography, { variant: "body-small-400", className: "text-blue-600" }, item.label), !readOnly && /* @__PURE__ */ React.createElement(
|
|
16
|
+
return /* @__PURE__ */ React.createElement(Tag, { "data-tag": "true", color: "blue", className: "max-w-[15.625rem] h-6 !px-2 !py-0.5" }, /* @__PURE__ */ React.createElement(Typography, { variant: "body-small-400", className: "text-blue-600 truncate p-0" }, item.label), !readOnly && /* @__PURE__ */ React.createElement(
|
|
17
17
|
X,
|
|
18
18
|
{
|
|
19
|
-
className: "w-4 h-4 cursor-pointer",
|
|
19
|
+
className: "w-4 h-4 cursor-pointer flex-shrink-0",
|
|
20
20
|
onClick: handleRemove,
|
|
21
21
|
"aria-label": `Remover ${item.label}`,
|
|
22
22
|
tabIndex: 0
|
|
@@ -26,95 +26,130 @@ function TagItem({ item, onRemove, readOnly }) {
|
|
|
26
26
|
function HiddenCountTag({ count }) {
|
|
27
27
|
return /* @__PURE__ */ React.createElement(Tag, { color: "blue", "data-tag": "true" }, /* @__PURE__ */ React.createElement(Typography, { variant: "body-small-400", className: "text-blue-600" }, "+", count));
|
|
28
28
|
}
|
|
29
|
-
function
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
function calculateEstimatedWidths(value) {
|
|
30
|
+
const AVG_CHAR_WIDTH = 8;
|
|
31
|
+
const TAG_PADDING = 24;
|
|
32
|
+
return value.map((item) => {
|
|
33
|
+
const textLength = item.label.length;
|
|
34
|
+
const calculatedWidth = textLength * AVG_CHAR_WIDTH + TAG_PADDING;
|
|
35
|
+
return Math.min(calculatedWidth, 250);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function simulateTagLayout(estimatedWidths, availableWidth, maxLines) {
|
|
39
|
+
const COUNTER_TAG_WIDTH = 60;
|
|
40
|
+
const TAG_GAP = 4;
|
|
41
|
+
let currentLine = 0;
|
|
42
|
+
let currentLineWidth = 0;
|
|
43
|
+
let count = 0;
|
|
44
|
+
for (let i = 0; i < estimatedWidths.length; i++) {
|
|
45
|
+
const tagWidth = estimatedWidths[i] + (i > 0 && currentLineWidth > 0 ? TAG_GAP : 0);
|
|
46
|
+
if (currentLineWidth + tagWidth > availableWidth) {
|
|
47
|
+
currentLine++;
|
|
48
|
+
currentLineWidth = 0;
|
|
49
|
+
if (currentLine >= maxLines) {
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const isLastLine = currentLine === maxLines - 1;
|
|
54
|
+
const needsCounter = isLastLine && i < estimatedWidths.length - 1;
|
|
55
|
+
const widthNeeded = tagWidth + (needsCounter ? COUNTER_TAG_WIDTH + TAG_GAP : 0);
|
|
56
|
+
if (currentLineWidth + widthNeeded <= availableWidth) {
|
|
57
|
+
currentLineWidth += tagWidth;
|
|
58
|
+
count++;
|
|
59
|
+
} else {
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return Math.max(1, count);
|
|
64
|
+
}
|
|
65
|
+
function useDynamicMaxCount(ref, value, maxDisplayCount) {
|
|
66
|
+
const [dynamicMaxCount, setDynamicMaxCount] = React.useState(
|
|
67
|
+
value.length || 1
|
|
37
68
|
);
|
|
38
|
-
const [
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
69
|
+
const [maxLines, setMaxLines] = React.useState(null);
|
|
70
|
+
const hasCalculatedMaxLines = React.useRef(false);
|
|
71
|
+
React.useEffect(() => {
|
|
72
|
+
if (hasCalculatedMaxLines.current || !ref.current || value.length === 0) return;
|
|
73
|
+
const calculateMaxLines = () => {
|
|
74
|
+
if (!ref.current) {
|
|
75
|
+
setTimeout(calculateMaxLines, 10);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const containerHeight = ref.current.offsetHeight;
|
|
79
|
+
if (containerHeight === 0) {
|
|
80
|
+
setTimeout(calculateMaxLines, 10);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const TAG_HEIGHT = 24;
|
|
84
|
+
const CONTAINER_PADDING_VERTICAL = 4;
|
|
85
|
+
const availableHeight = containerHeight - CONTAINER_PADDING_VERTICAL * 2;
|
|
86
|
+
const calculatedMaxLines = Math.max(1, Math.floor(availableHeight / TAG_HEIGHT));
|
|
87
|
+
setMaxLines(calculatedMaxLines);
|
|
88
|
+
hasCalculatedMaxLines.current = true;
|
|
89
|
+
};
|
|
90
|
+
requestAnimationFrame(calculateMaxLines);
|
|
91
|
+
}, [ref, value.length]);
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
if (maxDisplayCount !== void 0) {
|
|
94
|
+
setDynamicMaxCount(maxDisplayCount);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (value.length === 0 || maxLines === null) return;
|
|
98
|
+
const calculateMaxCount = () => {
|
|
99
|
+
if (!ref.current) {
|
|
100
|
+
setTimeout(() => calculateMaxCount(), 10);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const containerWidth = ref.current.offsetWidth;
|
|
104
|
+
if (containerWidth === 0) {
|
|
105
|
+
setTimeout(() => calculateMaxCount(), 10);
|
|
106
|
+
return;
|
|
47
107
|
}
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
108
|
+
const CONTAINER_PADDING = 12;
|
|
109
|
+
const TAG_GAP = 4;
|
|
110
|
+
const availableWidth = containerWidth - CONTAINER_PADDING * 2;
|
|
111
|
+
if (availableWidth <= 0) {
|
|
112
|
+
setDynamicMaxCount(1);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const estimatedWidths = calculateEstimatedWidths(value);
|
|
116
|
+
const totalWidth = estimatedWidths.reduce(
|
|
117
|
+
(sum, width, i) => sum + width + (i > 0 ? TAG_GAP : 0),
|
|
118
|
+
0
|
|
51
119
|
);
|
|
52
|
-
if (
|
|
53
|
-
|
|
120
|
+
if (totalWidth <= availableWidth) {
|
|
121
|
+
setDynamicMaxCount(value.length);
|
|
122
|
+
return;
|
|
54
123
|
}
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
for (const child of children) {
|
|
60
|
-
if (child.offsetTop === firstLineTop) firstLineCount++;
|
|
61
|
-
else if (child.offsetTop === secondLineTop) secondLineCount++;
|
|
124
|
+
const totalAvailableWidth = availableWidth * maxLines;
|
|
125
|
+
if (totalWidth <= totalAvailableWidth) {
|
|
126
|
+
setDynamicMaxCount(value.length);
|
|
127
|
+
return;
|
|
62
128
|
}
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
visibleCount,
|
|
66
|
-
hiddenCount: Math.max(totalTags - visibleCount, 0)
|
|
67
|
-
};
|
|
68
|
-
},
|
|
69
|
-
[getInitialEstimate]
|
|
70
|
-
);
|
|
71
|
-
const calculateMaxVisible = React.useCallback(() => {
|
|
72
|
-
const container = ref.current;
|
|
73
|
-
if (!container) return;
|
|
74
|
-
const children = Array.from(container.children).filter(
|
|
75
|
-
(el) => el.dataset.tag === "true" && !el.textContent?.startsWith("+")
|
|
76
|
-
);
|
|
77
|
-
const { visibleCount, hiddenCount: newHiddenCount } = calculateVisibleTags(
|
|
78
|
-
children,
|
|
79
|
-
value.length
|
|
80
|
-
);
|
|
81
|
-
setMaxVisibleCount(visibleCount);
|
|
82
|
-
setHiddenCount(newHiddenCount);
|
|
83
|
-
}, [ref, calculateVisibleTags, value.length]);
|
|
84
|
-
React.useLayoutEffect(() => {
|
|
85
|
-
const calculateWhenVisible = () => {
|
|
86
|
-
requestAnimationFrame(() => requestAnimationFrame(calculateMaxVisible));
|
|
129
|
+
const count = simulateTagLayout(estimatedWidths, availableWidth, maxLines);
|
|
130
|
+
setDynamicMaxCount(count);
|
|
87
131
|
};
|
|
88
|
-
const
|
|
89
|
-
|
|
132
|
+
const rafId = requestAnimationFrame(() => {
|
|
133
|
+
calculateMaxCount();
|
|
134
|
+
});
|
|
135
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
136
|
+
calculateMaxCount();
|
|
90
137
|
});
|
|
91
|
-
const resizeObserver = new ResizeObserver(calculateMaxVisible);
|
|
92
138
|
if (ref.current) {
|
|
93
|
-
observer.observe(ref.current);
|
|
94
139
|
resizeObserver.observe(ref.current);
|
|
95
|
-
if (ref.current.offsetParent !== null) {
|
|
96
|
-
calculateWhenVisible();
|
|
97
|
-
}
|
|
98
140
|
}
|
|
99
141
|
return () => {
|
|
100
|
-
|
|
142
|
+
cancelAnimationFrame(rafId);
|
|
101
143
|
resizeObserver.disconnect();
|
|
102
144
|
};
|
|
103
|
-
}, [value,
|
|
104
|
-
|
|
105
|
-
const timeout = setTimeout(calculateMaxVisible, 150);
|
|
106
|
-
return () => clearTimeout(timeout);
|
|
107
|
-
}, [value.length, calculateMaxVisible]);
|
|
108
|
-
React.useLayoutEffect(() => {
|
|
109
|
-
calculateMaxVisible();
|
|
110
|
-
}, [value.length, calculateMaxVisible]);
|
|
111
|
-
return { maxVisibleCount, hiddenCount };
|
|
145
|
+
}, [maxDisplayCount, value, ref, maxLines]);
|
|
146
|
+
return dynamicMaxCount;
|
|
112
147
|
}
|
|
113
148
|
function TagInput(props) {
|
|
114
149
|
const {
|
|
115
150
|
value,
|
|
116
151
|
onRemove,
|
|
117
|
-
maxDisplayCount
|
|
152
|
+
maxDisplayCount,
|
|
118
153
|
placeholder = "Nenhum item selecionado",
|
|
119
154
|
disabled = false,
|
|
120
155
|
readOnly = false,
|
|
@@ -124,14 +159,14 @@ function TagInput(props) {
|
|
|
124
159
|
...restProps
|
|
125
160
|
} = props;
|
|
126
161
|
const ref = React.useRef(null);
|
|
127
|
-
|
|
128
|
-
const visibleCount = Math.min(
|
|
162
|
+
const dynamicMaxCount = useDynamicMaxCount(ref, value, maxDisplayCount);
|
|
163
|
+
const visibleCount = Math.min(dynamicMaxCount, value.length);
|
|
129
164
|
const displayTags = value.slice(0, visibleCount);
|
|
130
165
|
const actualHiddenCount = Math.max(value.length - visibleCount, 0);
|
|
131
166
|
const textareaRadius = radius === "full" ? "large" : radius;
|
|
132
167
|
const containerClassNames = cn(
|
|
133
168
|
textareaVariants({ variant, radius: textareaRadius }),
|
|
134
|
-
"flex flex-wrap items-start min-h-10 gap-1 py-
|
|
169
|
+
"flex flex-wrap items-start min-h-10 gap-1 py-1 px-3",
|
|
135
170
|
disabled && "opacity-50 pointer-events-none",
|
|
136
171
|
className
|
|
137
172
|
);
|