Skip to content

Commit

Permalink
Use configurable filterKey to filter nested sections
Browse files Browse the repository at this point in the history
  • Loading branch information
gijsroge committed May 2, 2024
1 parent 56a1037 commit 444c1f3
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 8 deletions.
37 changes: 35 additions & 2 deletions packages/@react-spectrum/listbox/stories/ListBox.stories.tsx
Expand Up @@ -11,7 +11,7 @@
*/

import {action} from '@storybook/addon-actions';
import {ActionGroup, AlertDialog, Avatar, Button, DialogContainer, Flex, Text} from '@adobe/react-spectrum';
import {ActionGroup, AlertDialog, Avatar, Button, DialogContainer, Flex, Text, useFilter} from '@adobe/react-spectrum';
import AlignCenter from '@spectrum-icons/workflow/AlignCenter';
import AlignLeft from '@spectrum-icons/workflow/AlignLeft';
import AlignRight from '@spectrum-icons/workflow/AlignRight';
Expand All @@ -26,7 +26,7 @@ import {Label} from '@react-spectrum/label';
import Paste from '@spectrum-icons/workflow/Paste';
import React, {useRef, useState} from 'react';
import {TranslateListBox} from './../chromatic/ListBoxLanguages.chromatic';
import {useAsyncList, useTreeData} from '@react-stately/data';
import {useAsyncList, useListData, useTreeData} from '@react-stately/data';

let iconMap = {
AlignCenter,
Expand Down Expand Up @@ -1043,3 +1043,36 @@ export const WithAvatars = {
</StoryDecorator>
)]
};


export const FilterableListBox = {
render: () => <SearchableListBox />,
decorators: null,
name: 'filterable listbox'
};

function SearchableListBox() {

const {contains} = useFilter({sensitivity: 'base'});

const list = useListData({
initialItems: withSection,
filterKey: 'children',
filter: (item, text) => {
return contains(item.name, text);
}
});

return (
<>
<input type="text" onChange={(e) => list.setFilterText(e.target.value)} />
<ListBox width="150px" items={list.items} aria-labelledby="labelSearchableListBox">
{(item) => (
<Section key={item.name} items={item.children} title={item.name}>
{(item) => <Item key={item.name}>{item.name}</Item>}
</Section>
)}
</ListBox >
</>
);
}
13 changes: 8 additions & 5 deletions packages/@react-stately/data/src/useListData.ts
Expand Up @@ -23,7 +23,9 @@ export interface ListOptions<T> {
/** A function that returns a unique key for an item object. */
getKey?: (item: T) => Key,
/** A function that returns whether a item matches the current filter text. */
filter?: (item: T, filterText: string) => boolean
filter?: (item: T, filterText: string) => boolean,
/** The key of the item that contains the nested items it should filter. */
filterKey?: string
}

export interface ListData<T> {
Expand Down Expand Up @@ -142,6 +144,7 @@ export function useListData<T>(options: ListOptions<T>): ListData<T> {
initialSelectedKeys,
getKey = (item: any) => item.id || item.key,
filter,
filterKey,
initialFilterText = ''
} = options;

Expand All @@ -152,13 +155,13 @@ export function useListData<T>(options: ListOptions<T>): ListData<T> {
filterText: initialFilterText
});

const filterItems = (items: T[], filterText: string): T[] => {
const filterItems = (items: T[], filterText: string, key: string): T[] => {
return items.reduce((acc: T[], item: any) => {
if (item.children) {
if (item[key]) {
if (filter(item, filterText)) {
acc.push(item);
} else {
const children = filterItems(item.children, filterText);
const children = filterItems(item[key], filterText, key);
if (children.length > 0) {
acc.push({...item, children});
}
Expand All @@ -171,7 +174,7 @@ export function useListData<T>(options: ListOptions<T>): ListData<T> {
};

let filteredItems = useMemo(
() => (filter ? filterItems(state.items, state.filterText) : state.items),
() => (filter ? filterItems(state.items, state.filterText, filterKey) : state.items),
[state.items, state.filterText, filter]
);

Expand Down
2 changes: 1 addition & 1 deletion packages/@react-stately/data/test/useListData.test.js
Expand Up @@ -821,7 +821,7 @@ describe('useListData', function () {
});

it('should support filtering items across sections', function () {
let {result} = renderHook(() => useListData({initialItems: grouped, getKey, filter, initialFilterText: 'Child 1'}));
let {result} = renderHook(() => useListData({initialItems: grouped, getKey, filter, initialFilterText: 'Child 1', filterKey: 'children'}));

expect(result.current.items).toEqual([
{name: 'One', children: [{name: 'Child 1'}]},
Expand Down

0 comments on commit 444c1f3

Please sign in to comment.