Skip to content

Commit 4365f11

Browse files
committed
fix(FilterListBox): filtering issue
1 parent 701bd95 commit 4365f11

File tree

3 files changed

+160
-161
lines changed

3 files changed

+160
-161
lines changed

.changeset/red-mugs-live.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cube-dev/ui-kit": patch
3+
---
4+
5+
Fix FilterListBox filtering bug.

src/components/fields/FilterListBox/FilterListBox.tsx

Lines changed: 39 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import {
2727
import { mergeProps, modAttrs, useCombinedRefs } from '../../../utils/react';
2828
import { useFocus } from '../../../utils/react/interactions';
2929
import { StyledHeader } from '../../actions/Menu/styled';
30-
import { ItemBase } from '../../content/ItemBase';
3130
import { useFieldProps, useFormProps, wrapWithField } from '../../form';
3231
import { CubeItemProps } from '../../Item';
3332
import { CubeListBoxProps, ListBox } from '../ListBox/ListBox';
@@ -759,19 +758,6 @@ export const FilterListBox = forwardRef(function FilterListBox<
759758
],
760759
);
761760

762-
const hasResults = useMemo(() => {
763-
if (!listStateRef.current?.collection) return !!enhancedChildren;
764-
765-
// Collection is already filtered by React Stately, just check if it has items
766-
for (const node of listStateRef.current.collection) {
767-
if (node.type === 'item') return true;
768-
if (node.childNodes && [...node.childNodes].length > 0) return true;
769-
}
770-
return false;
771-
}, [listStateRef.current?.collection, enhancedChildren, searchValue]);
772-
773-
const showEmptyMessage = !hasResults && searchValue.trim();
774-
775761
// Handler must be defined before we render ListBox so we can pass it.
776762
const handleSelectionChange = (selection: any) => {
777763
if (allowsCustomValue) {
@@ -876,55 +862,45 @@ export const FilterListBox = forwardRef(function FilterListBox<
876862
<div role="presentation" />
877863
)}
878864
{searchInput}
879-
{showEmptyMessage ? (
880-
<ItemBase
881-
preset="t4"
882-
color="#dark-03"
883-
size={size}
884-
padding="(.5x - 1bw)"
885-
>
886-
{emptyLabel !== undefined ? emptyLabel : 'No results found'}
887-
</ItemBase>
888-
) : (
889-
<ListBox
890-
ref={listBoxRef}
891-
aria-label={innerAriaLabel}
892-
selectedKey={selectedKey}
893-
defaultSelectedKey={defaultSelectedKey}
894-
selectedKeys={selectedKeys}
895-
defaultSelectedKeys={defaultSelectedKeys}
896-
selectionMode={selectionMode}
897-
isDisabled={isDisabled}
898-
listRef={listRef}
899-
stateRef={listStateRef}
900-
listStyles={listStyles}
901-
shouldFocusWrap={shouldFocusWrap}
902-
optionStyles={optionStyles}
903-
sectionStyles={sectionStyles}
904-
headingStyles={headingStyles}
905-
validationState={validationState}
906-
disallowEmptySelection={props.disallowEmptySelection}
907-
disabledKeys={props.disabledKeys}
908-
focusOnHover={focusOnHover}
909-
shouldUseVirtualFocus={true}
910-
showSelectAll={showSelectAll}
911-
selectAllLabel={selectAllLabel}
912-
footer={footer}
913-
footerStyles={footerStyles}
914-
mods={mods}
915-
size="medium"
916-
styles={listBoxStyles}
917-
isCheckable={isCheckable}
918-
items={items as any}
919-
allValueProps={allValueProps}
920-
filter={filterFn}
921-
onSelectionChange={handleSelectionChange}
922-
onEscape={onEscape}
923-
onOptionClick={handleOptionClick}
924-
>
925-
{enhancedChildren as any}
926-
</ListBox>
927-
)}
865+
<ListBox
866+
ref={listBoxRef}
867+
aria-label={innerAriaLabel}
868+
selectedKey={selectedKey}
869+
defaultSelectedKey={defaultSelectedKey}
870+
selectedKeys={selectedKeys}
871+
defaultSelectedKeys={defaultSelectedKeys}
872+
selectionMode={selectionMode}
873+
isDisabled={isDisabled}
874+
listRef={listRef}
875+
stateRef={listStateRef}
876+
listStyles={listStyles}
877+
shouldFocusWrap={shouldFocusWrap}
878+
optionStyles={optionStyles}
879+
sectionStyles={sectionStyles}
880+
headingStyles={headingStyles}
881+
validationState={validationState}
882+
disallowEmptySelection={props.disallowEmptySelection}
883+
disabledKeys={props.disabledKeys}
884+
focusOnHover={focusOnHover}
885+
shouldUseVirtualFocus={true}
886+
showSelectAll={showSelectAll}
887+
selectAllLabel={selectAllLabel}
888+
footer={footer}
889+
footerStyles={footerStyles}
890+
mods={mods}
891+
size="medium"
892+
styles={listBoxStyles}
893+
isCheckable={isCheckable}
894+
items={items as any}
895+
allValueProps={allValueProps}
896+
filter={filterFn}
897+
emptyLabel={emptyLabel !== undefined ? emptyLabel : 'No results found'}
898+
onSelectionChange={handleSelectionChange}
899+
onEscape={onEscape}
900+
onOptionClick={handleOptionClick}
901+
>
902+
{enhancedChildren as any}
903+
</ListBox>
928904
</FilterListBoxWrapperElement>
929905
);
930906

src/components/fields/ListBox/ListBox.tsx

Lines changed: 116 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,12 @@ export interface CubeListBoxProps<T>
303303
* Useful for implementing search/filter functionality.
304304
*/
305305
filter?: (nodes: Iterable<any>) => Iterable<any>;
306+
307+
/**
308+
* Label to display when the list is empty (no items available).
309+
* Defaults to "No items".
310+
*/
311+
emptyLabel?: ReactNode;
306312
}
307313

308314
const PROP_STYLES = [...BASE_STYLES, ...OUTER_STYLES, ...COLOR_STYLES];
@@ -508,6 +514,7 @@ export const ListBox = forwardRef(function ListBox<T extends object>(
508514
selectAllLabel,
509515
allValueProps,
510516
filter,
517+
emptyLabel = 'No items',
511518
form,
512519
...otherProps
513520
} = props;
@@ -886,110 +893,121 @@ export const ListBox = forwardRef(function ListBox<T extends object>(
886893
)}
887894
{/* Scroll container wrapper */}
888895
<ListBoxScrollElement ref={scrollRef} mods={mods} {...focusProps}>
889-
<ListElement
890-
qa={qa || 'ListBox'}
891-
{...mergedListBoxProps}
892-
ref={listRef}
893-
styles={listStyles}
894-
aria-disabled={isDisabled || undefined}
895-
mods={{ sections: hasSections }}
896-
style={
897-
shouldVirtualize
898-
? {
899-
position: 'relative',
900-
height: `${rowVirtualizer.getTotalSize() + 3}px`,
901-
}
902-
: undefined
903-
}
904-
>
905-
{shouldVirtualize
906-
? rowVirtualizer.getVirtualItems().map((virtualItem) => {
907-
const item = itemsArray[virtualItem.index];
908-
909-
return (
910-
<Option
911-
key={virtualItem.key}
912-
size={size}
913-
item={item}
914-
state={listState}
915-
styles={optionStyles}
916-
isParentDisabled={isDisabled}
917-
validationState={validationState}
918-
focusOnHover={focusOnHover}
919-
isCheckable={isCheckable}
920-
// We don't need to measure the element here, because the height is already set by the virtualizer
921-
// This is a workaround to avoid glitches when selecting/deselecting items
922-
virtualRef={rowVirtualizer.measureElement as any}
923-
virtualStyle={{
924-
position: 'absolute',
925-
top: 0,
926-
left: 0,
927-
right: 0,
928-
transform: `translateY(${virtualItem.start}px)`,
929-
}}
930-
virtualIndex={virtualItem.index}
931-
lastFocusSourceRef={lastFocusSourceRef}
932-
onClick={onOptionClick}
933-
/>
934-
);
935-
})
936-
: (() => {
937-
const renderedItems: ReactNode[] = [];
938-
let isFirstSection = true;
939-
940-
for (const item of listState.collection) {
941-
if (item.type === 'section') {
942-
if (!isFirstSection) {
896+
{listState.collection.size === 0 ? (
897+
<ItemBase
898+
preset="t4"
899+
color="#dark-03"
900+
size={size}
901+
padding="(.5x - 1bw)"
902+
>
903+
{emptyLabel}
904+
</ItemBase>
905+
) : (
906+
<ListElement
907+
qa={qa || 'ListBox'}
908+
{...mergedListBoxProps}
909+
ref={listRef}
910+
styles={listStyles}
911+
aria-disabled={isDisabled || undefined}
912+
mods={{ sections: hasSections }}
913+
style={
914+
shouldVirtualize
915+
? {
916+
position: 'relative',
917+
height: `${rowVirtualizer.getTotalSize() + 3}px`,
918+
}
919+
: undefined
920+
}
921+
>
922+
{shouldVirtualize
923+
? rowVirtualizer.getVirtualItems().map((virtualItem) => {
924+
const item = itemsArray[virtualItem.index];
925+
926+
return (
927+
<Option
928+
key={virtualItem.key}
929+
size={size}
930+
item={item}
931+
state={listState}
932+
styles={optionStyles}
933+
isParentDisabled={isDisabled}
934+
validationState={validationState}
935+
focusOnHover={focusOnHover}
936+
isCheckable={isCheckable}
937+
// We don't need to measure the element here, because the height is already set by the virtualizer
938+
// This is a workaround to avoid glitches when selecting/deselecting items
939+
virtualRef={rowVirtualizer.measureElement as any}
940+
virtualStyle={{
941+
position: 'absolute',
942+
top: 0,
943+
left: 0,
944+
right: 0,
945+
transform: `translateY(${virtualItem.start}px)`,
946+
}}
947+
virtualIndex={virtualItem.index}
948+
lastFocusSourceRef={lastFocusSourceRef}
949+
onClick={onOptionClick}
950+
/>
951+
);
952+
})
953+
: (() => {
954+
const renderedItems: ReactNode[] = [];
955+
let isFirstSection = true;
956+
957+
for (const item of listState.collection) {
958+
if (item.type === 'section') {
959+
if (!isFirstSection) {
960+
renderedItems.push(
961+
<StyledDivider
962+
key={`divider-${String(item.key)}`}
963+
role="separator"
964+
aria-orientation="horizontal"
965+
/>,
966+
);
967+
}
968+
943969
renderedItems.push(
944-
<StyledDivider
945-
key={`divider-${String(item.key)}`}
946-
role="separator"
947-
aria-orientation="horizontal"
970+
<ListBoxSection
971+
key={item.key}
972+
item={item}
973+
state={listState}
974+
optionStyles={optionStyles}
975+
headingStyles={headingStyles}
976+
sectionStyles={sectionStyles}
977+
isParentDisabled={isDisabled}
978+
validationState={validationState}
979+
focusOnHover={focusOnHover}
980+
isCheckable={isCheckable}
981+
size={size}
982+
lastFocusSourceRef={lastFocusSourceRef}
983+
onClick={onOptionClick}
948984
/>,
949985
);
950-
}
951986

952-
renderedItems.push(
953-
<ListBoxSection
954-
key={item.key}
955-
item={item}
956-
state={listState}
957-
optionStyles={optionStyles}
958-
headingStyles={headingStyles}
959-
sectionStyles={sectionStyles}
960-
isParentDisabled={isDisabled}
961-
validationState={validationState}
962-
focusOnHover={focusOnHover}
963-
isCheckable={isCheckable}
964-
size={size}
965-
lastFocusSourceRef={lastFocusSourceRef}
966-
onClick={onOptionClick}
967-
/>,
968-
);
969-
970-
isFirstSection = false;
971-
} else {
972-
renderedItems.push(
973-
<Option
974-
key={item.key}
975-
size={size}
976-
item={item}
977-
state={listState}
978-
styles={optionStyles}
979-
isParentDisabled={isDisabled}
980-
validationState={validationState}
981-
focusOnHover={focusOnHover}
982-
isCheckable={isCheckable}
983-
lastFocusSourceRef={lastFocusSourceRef}
984-
onClick={onOptionClick}
985-
/>,
986-
);
987+
isFirstSection = false;
988+
} else {
989+
renderedItems.push(
990+
<Option
991+
key={item.key}
992+
size={size}
993+
item={item}
994+
state={listState}
995+
styles={optionStyles}
996+
isParentDisabled={isDisabled}
997+
validationState={validationState}
998+
focusOnHover={focusOnHover}
999+
isCheckable={isCheckable}
1000+
lastFocusSourceRef={lastFocusSourceRef}
1001+
onClick={onOptionClick}
1002+
/>,
1003+
);
1004+
}
9871005
}
988-
}
9891006

990-
return renderedItems;
991-
})()}
992-
</ListElement>
1007+
return renderedItems;
1008+
})()}
1009+
</ListElement>
1010+
)}
9931011
</ListBoxScrollElement>
9941012
{footer ? (
9951013
<StyledFooter styles={footerStyles} data-size={size}>

0 commit comments

Comments
 (0)