Skip to content

Commit 86c10b2

Browse files
committed
fix(multi-select): improve empty state and footer actions
1 parent 4e12880 commit 86c10b2

File tree

1 file changed

+106
-74
lines changed

1 file changed

+106
-74
lines changed

src/components/ui/multi-select.tsx

Lines changed: 106 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,6 @@ function MultiSelectOptionItem({
378378
return (
379379
<div className="flex items-center">
380380
<CommandItem
381-
value={option.value}
382381
onSelect={isReadOnly ? undefined : () => toggleOption(option.value)}
383382
role="option"
384383
aria-selected={isSelected}
@@ -737,6 +736,28 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
737736
);
738737
}, [options, searchValue, searchable, isGroupedOptions]);
739738

739+
const hasFilteredResults = React.useMemo(() => {
740+
if (isGroupedOptions(filteredOptions)) {
741+
return filteredOptions.some((group) => group.options.length > 0);
742+
}
743+
return filteredOptions.length > 0;
744+
}, [filteredOptions, isGroupedOptions]);
745+
746+
const filteredOptionValues = React.useMemo(() => {
747+
if (isGroupedOptions(filteredOptions)) {
748+
return filteredOptions.flatMap((group) =>
749+
group.options.map((option) => option.value),
750+
);
751+
}
752+
return filteredOptions.map((option) => option.value);
753+
}, [filteredOptions, isGroupedOptions]);
754+
755+
const hasSelectedInFiltered = React.useMemo(
756+
() =>
757+
filteredOptionValues.some((value) => selectedValues.includes(value)),
758+
[filteredOptionValues, selectedValues],
759+
);
760+
740761
const handleInputKeyDown = (
741762
event: React.KeyboardEvent<HTMLInputElement>,
742763
) => {
@@ -1191,7 +1212,7 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
11911212
align="start"
11921213
onEscapeKeyDown={() => setIsPopoverOpen(false)}
11931214
>
1194-
<Command>
1215+
<Command shouldFilter={false}>
11951216
{searchable && (
11961217
<CommandInput
11971218
placeholder="Wyszukaj opcję..."
@@ -1214,85 +1235,96 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
12141235
"overscroll-behavior-y-contain",
12151236
)}
12161237
>
1217-
<CommandEmpty>{emptyIndicator || "Brak wyników."}</CommandEmpty>{" "}
1218-
{!hideSelectAll && !searchValue && (
1219-
<CommandGroup>
1220-
<CommandItem
1221-
key="all"
1222-
onSelect={toggleAll}
1223-
role="option"
1224-
aria-selected={
1225-
selectedValues.length ===
1226-
getAllOptions().filter((opt) => !opt.disabled).length
1227-
}
1228-
aria-label={`Select all ${
1229-
getAllOptions().length
1230-
} options`}
1231-
className="cursor-pointer"
1232-
>
1233-
<div
1234-
className={cn(
1235-
"border-primary mr-2 flex h-4 w-4 items-center justify-center rounded-sm border",
1236-
selectedValues.length ===
1238+
{hasFilteredResults ? (
1239+
<>
1240+
{!hideSelectAll && !searchValue && (
1241+
<CommandGroup>
1242+
<CommandItem
1243+
key="all"
1244+
onSelect={toggleAll}
1245+
role="option"
1246+
aria-selected={
1247+
selectedValues.length ===
12371248
getAllOptions().filter((opt) => !opt.disabled)
12381249
.length
1239-
? "bg-primary"
1240-
: "opacity-50 [&_svg]:invisible",
1241-
)}
1242-
aria-hidden="true"
1243-
>
1244-
<CheckIcon className="text-primary-foreground h-4 w-4" />
1245-
</div>
1246-
<span>
1247-
(Wybierz wszystkie
1248-
{getAllOptions().length > 20
1249-
? ` – ${declineNumeric(
1250-
getAllOptions().length,
1251-
"option",
1252-
{ singularCase: GrammaticalCase.Accusative },
1253-
)}`
1254-
: ""}
1255-
)
1256-
</span>
1257-
</CommandItem>
1258-
</CommandGroup>
1259-
)}
1260-
{isGroupedOptions(filteredOptions) ? (
1261-
filteredOptions.map((group) => (
1262-
<CommandGroup key={group.heading} heading={group.heading}>
1263-
{group.options.map((option) => (
1264-
<MultiSelectOptionItem
1265-
key={option.value}
1266-
option={option}
1267-
isReadOnly={isReadOnly}
1268-
selectedValues={selectedValues}
1269-
toggleOption={toggleOption}
1270-
setOptionSelected={setOptionSelected}
1271-
onEditItem={onEditItem}
1272-
/>
1273-
))}
1274-
</CommandGroup>
1275-
))
1250+
}
1251+
aria-label={`Select all ${
1252+
getAllOptions().length
1253+
} options`}
1254+
className="cursor-pointer"
1255+
>
1256+
<div
1257+
className={cn(
1258+
"border-primary mr-2 flex h-4 w-4 items-center justify-center rounded-sm border",
1259+
selectedValues.length ===
1260+
getAllOptions().filter((opt) => !opt.disabled)
1261+
.length
1262+
? "bg-primary"
1263+
: "opacity-50 [&_svg]:invisible",
1264+
)}
1265+
aria-hidden="true"
1266+
>
1267+
<CheckIcon className="text-primary-foreground h-4 w-4" />
1268+
</div>
1269+
<span>
1270+
(Wybierz wszystkie
1271+
{getAllOptions().length > 20
1272+
? ` – ${declineNumeric(
1273+
getAllOptions().length,
1274+
"option",
1275+
{ singularCase: GrammaticalCase.Accusative },
1276+
)}`
1277+
: ""}
1278+
)
1279+
</span>
1280+
</CommandItem>
1281+
</CommandGroup>
1282+
)}
1283+
{isGroupedOptions(filteredOptions) ? (
1284+
filteredOptions.map((group) => (
1285+
<CommandGroup
1286+
key={group.heading}
1287+
heading={group.heading}
1288+
>
1289+
{group.options.map((option) => (
1290+
<MultiSelectOptionItem
1291+
key={option.value}
1292+
option={option}
1293+
isReadOnly={isReadOnly}
1294+
selectedValues={selectedValues}
1295+
toggleOption={toggleOption}
1296+
setOptionSelected={setOptionSelected}
1297+
onEditItem={onEditItem}
1298+
/>
1299+
))}
1300+
</CommandGroup>
1301+
))
1302+
) : (
1303+
<CommandGroup>
1304+
{filteredOptions.map((option) => (
1305+
<MultiSelectOptionItem
1306+
key={option.value}
1307+
option={option}
1308+
isReadOnly={isReadOnly}
1309+
selectedValues={selectedValues}
1310+
toggleOption={toggleOption}
1311+
setOptionSelected={setOptionSelected}
1312+
onEditItem={onEditItem}
1313+
/>
1314+
))}
1315+
</CommandGroup>
1316+
)}
1317+
</>
12761318
) : (
1277-
<CommandGroup>
1278-
{filteredOptions.map((option) => (
1279-
<MultiSelectOptionItem
1280-
key={option.value}
1281-
option={option}
1282-
isReadOnly={isReadOnly}
1283-
selectedValues={selectedValues}
1284-
toggleOption={toggleOption}
1285-
setOptionSelected={setOptionSelected}
1286-
onEditItem={onEditItem}
1287-
/>
1288-
))}
1289-
</CommandGroup>
1319+
<div className="text-muted-foreground px-3 py-6 text-sm">
1320+
{emptyIndicator || "Brak wyników."}
1321+
</div>
12901322
)}
12911323
</CommandList>
1292-
<CommandSeparator />
1324+
<Separator className="bg-border" />
12931325
<CommandGroup>
12941326
<div className="flex items-center justify-between">
1295-
{selectedValues.length > 0 && (
1327+
{hasSelectedInFiltered && (
12961328
<>
12971329
<CommandItem
12981330
onSelect={handleClear}

0 commit comments

Comments
 (0)