ListBox
A control to display a list of items where one or more can be selected.
| Install | yarn add @diallink-corp/convergo-react-listbox |
|---|---|
| Version | 4.1.2 |
| Usage | import {ListBox} from '@diallink-corp/convergo-react-listbox' |
Static Collection
The ListBox component can either render static or dynamic collections of data. For simple ListBox components that need to render a list of pre-defined items a static collection should be used. Static collections cannot be changed over time.
<ListBox aria-label='What is your favorite fruit?'>
<Item key='apples'>Apples</Item>
<Item key='bananas'>Bananas</Item>
<Item key='grapes'>Grapes</Item>
<Item key='oranges'>Oranges</Item>
</ListBox>
<ListBox aria-label='What is your favorite fruit?'>
<Item key='apples'>Apples</Item>
<Item key='bananas'>Bananas</Item>
<Item key='grapes'>Grapes</Item>
<Item key='oranges'>Oranges</Item>
</ListBox>
<ListBox aria-label="What is your favorite fruit?">
<Item key="apples">
Apples
</Item>
<Item key="bananas">
Bananas
</Item>
<Item key="grapes">
Grapes
</Item>
<Item key="oranges">
Oranges
</Item>
</ListBox>
Dynamic Collection
For data that is non-static, such as data fetched from a backend, the ListBox component supports dynamic collections. Dynamic collections support adding, updating and removing of the data that is being rendered.
function Example() {
const [fruits, setFruits] = useState([
{id: 1, name: 'Apples'},
{id: 2, name: 'Bananas'},
{id: 3, name: 'Grapes'},
{id: 4, name: 'Oranges'}
]);
return (
<ListBox aria-label='What is your favorite fruit?' items={fruits}>
{(item) => <Item key={item.id}>{item.name}</Item>}
</ListBox>
);
}
function Example() {
const [fruits, setFruits] = useState([
{ id: 1, name: 'Apples' },
{ id: 2, name: 'Bananas' },
{ id: 3, name: 'Grapes' },
{ id: 4, name: 'Oranges' }
]);
return (
<ListBox
aria-label="What is your favorite fruit?"
items={fruits}
>
{(item) => <Item key={item.id}>{item.name}</Item>}
</ListBox>
);
}
function Example() {
const [
fruits,
setFruits
] = useState([
{
id: 1,
name: 'Apples'
},
{
id: 2,
name: 'Bananas'
},
{
id: 3,
name: 'Grapes'
},
{
id: 4,
name: 'Oranges'
}
]);
return (
<ListBox
aria-label="What is your favorite fruit?"
items={fruits}
>
{(item) => (
<Item
key={item.id}
>
{item.name}
</Item>
)}
</ListBox>
);
}
Asynchronous Fetching
The ListBox component supports asynchronous fetching of data via the onLoadMoreItems prop. The ListBox
component uses its built-in infinite scrolling to achieve this. This means that when the user reaches
the bottom of the list, the component will automatically load more data. If you provide an isLoading
prop, the ListBox component will show a Spinner component, to indicate to the user, that more data is
being fetched.
function Example() {
const list = useAsyncListState({
async loadItems({ cursor, signal }) {
const response = await fetch(
cursor || 'https://pokeapi.co/api/v2/pokemon',
{ signal }
);
const { results, next } = await response.json();
return { items: results, cursor: next };
}
});
return (
<ListBox
aria-label="What is your favorite fruit?"
items={list.items}
isLoading={list.isLoading}
onLoadMoreItems={list.loadMoreItems}
style={{ maxHeight: '300px' }}
>
{(item) => <Item key={item.name}>{item.name}</Item>}
</ListBox>
);
}
function Example() {
const list = useAsyncListState({
async loadItems({ cursor, signal }) {
const response = await fetch(
cursor || 'https://pokeapi.co/api/v2/pokemon',
{ signal }
);
const { results, next } = await response.json();
return { items: results, cursor: next };
}
});
return (
<ListBox
aria-label="What is your favorite fruit?"
items={list.items}
isLoading={list.isLoading}
onLoadMoreItems={list.loadMoreItems}
style={{ maxHeight: '300px' }}
>
{(item) => <Item key={item.name}>{item.name}</Item>}
</ListBox>
);
}
function Example() {
const list =
useAsyncListState({
async loadItems(
{
cursor,
signal
}
) {
const response =
await fetch(
cursor ||
'https://pokeapi.co/api/v2/pokemon',
{ signal }
);
const {
results,
next
} =
await response
.json();
return {
items: results,
cursor: next
};
}
});
return (
<ListBox
aria-label="What is your favorite fruit?"
items={list.items}
isLoading={list
.isLoading}
onLoadMoreItems={list
.loadMoreItems}
style={{
maxHeight:
'300px'
}}
>
{(item) => (
<Item
key={item.name}
>
{item.name}
</Item>
)}
</ListBox>
);
}
Uncontrolled Selection
By default, the ListBox component handles its selection uncontrolled. In the uncontrolled variant you can
pass in a defaultSelectedKeys to select one or more values by default. The key that is being referenced here is the
value that is passed to the Item component as a key prop, so in this case item.name.
function Example() {
const fruits = [
{ id: 1, name: 'Apples' },
{ id: 2, name: 'Bananas' },
{ id: 3, name: 'Grapes' },
{ id: 4, name: 'Oranges' }
];
return (
<ListBox
aria-label="Fruits"
items={fruits}
defaultSelectedKeys={['Grapes']}
selectionMode="single"
>
{(item) => <Item key={item.name}>{item.name}</Item>}
</ListBox>
);
}
function Example() {
const fruits = [
{ id: 1, name: 'Apples' },
{ id: 2, name: 'Bananas' },
{ id: 3, name: 'Grapes' },
{ id: 4, name: 'Oranges' }
];
return (
<ListBox
aria-label="Fruits"
items={fruits}
defaultSelectedKeys={['Grapes']}
selectionMode="single"
>
{(item) => <Item key={item.name}>{item.name}</Item>}
</ListBox>
);
}
function Example() {
const fruits = [
{
id: 1,
name: 'Apples'
},
{
id: 2,
name: 'Bananas'
},
{
id: 3,
name: 'Grapes'
},
{
id: 4,
name: 'Oranges'
}
];
return (
<ListBox
aria-label="Fruits"
items={fruits}
defaultSelectedKeys={[
'Grapes'
]}
selectionMode="single"
>
{(item) => (
<Item
key={item.name}
>
{item.name}
</Item>
)}
</ListBox>
);
}
Controlled Selection
The use the controlled selection variant, you can use the selectedKey and onSelectionChange props together.
function Example() {
const fruits = [
{id: 1, name: 'Apples'},
{id: 2, name: 'Bananas'},
{id: 3, name: 'Grapes'},
{id: 4, name: 'Oranges'}
];
const [selectedFruit, setSelectedFruit] = useState('Bananas');
return (
<ListBox
aria-label='What is your favorite fruit?'
items={fruits}
selectionMode='multi'
selectedKeys={[selectedFruit]}
onSelectionChange={(value) => setSelectedFruit(value)}
>
{(item) => <Item key={item.name}><div>{item.name}</div></Item>}
</ListBox>
);
}
function Example() {
const fruits = [
{ id: 1, name: 'Apples' },
{ id: 2, name: 'Bananas' },
{ id: 3, name: 'Grapes' },
{ id: 4, name: 'Oranges' }
];
const [selectedFruit, setSelectedFruit] = useState(
'Bananas'
);
return (
<ListBox
aria-label="What is your favorite fruit?"
items={fruits}
selectionMode="multi"
selectedKeys={[selectedFruit]}
onSelectionChange={(value) => setSelectedFruit(value)}
>
{(item) => (
<Item key={item.name}>
<div>{item.name}</div>
</Item>
)}
</ListBox>
);
}
function Example() {
const fruits = [
{
id: 1,
name: 'Apples'
},
{
id: 2,
name: 'Bananas'
},
{
id: 3,
name: 'Grapes'
},
{
id: 4,
name: 'Oranges'
}
];
const [
selectedFruit,
setSelectedFruit
] = useState(
'Bananas'
);
return (
<ListBox
aria-label="What is your favorite fruit?"
items={fruits}
selectionMode="multi"
selectedKeys={[
selectedFruit
]}
onSelectionChange={(
value
) =>
setSelectedFruit(
value
)}
>
{(item) => (
<Item
key={item.name}
>
<div>
{item.name}
</div>
</Item>
)}
</ListBox>
);
}
Density
The ListBox supports various densities to control the size of the items.
Compact
<ListBox density='compact'>
<Item key='apples'>Apples</Item>
<Item key='bananas'>Bananas</Item>
<Item key='grapes'>Grapes</Item>
<Item key='oranges'>Oranges</Item>
</ListBox>
<ListBox density='compact'>
<Item key='apples'>Apples</Item>
<Item key='bananas'>Bananas</Item>
<Item key='grapes'>Grapes</Item>
<Item key='oranges'>Oranges</Item>
</ListBox>
<ListBox density="compact">
<Item key="apples">
Apples
</Item>
<Item key="bananas">
Bananas
</Item>
<Item key="grapes">
Grapes
</Item>
<Item key="oranges">
Oranges
</Item>
</ListBox>
Regular
<ListBox density='regular'>
<Item key='apples'>Apples</Item>
<Item key='bananas'>Bananas</Item>
<Item key='grapes'>Grapes</Item>
<Item key='oranges'>Oranges</Item>
</ListBox>
<ListBox density='regular'>
<Item key='apples'>Apples</Item>
<Item key='bananas'>Bananas</Item>
<Item key='grapes'>Grapes</Item>
<Item key='oranges'>Oranges</Item>
</ListBox>
<ListBox density="regular">
<Item key="apples">
Apples
</Item>
<Item key="bananas">
Bananas
</Item>
<Item key="grapes">
Grapes
</Item>
<Item key="oranges">
Oranges
</Item>
</ListBox>
Spacious
<ListBox density='spacious'>
<Item key='apples'>Apples</Item>
<Item key='bananas'>Bananas</Item>
<Item key='grapes'>Grapes</Item>
<Item key='oranges'>Oranges</Item>
</ListBox>
<ListBox density='spacious'>
<Item key='apples'>Apples</Item>
<Item key='bananas'>Bananas</Item>
<Item key='grapes'>Grapes</Item>
<Item key='oranges'>Oranges</Item>
</ListBox>
<ListBox density="spacious">
<Item key="apples">
Apples
</Item>
<Item key="bananas">
Bananas
</Item>
<Item key="grapes">
Grapes
</Item>
<Item key="oranges">
Oranges
</Item>
</ListBox>
Empty State
To show the user that the list box is empty when there is no data available you can use the renderEmptyState prop.
<ListBox renderEmptyState={() => <Text>No data was found</Text>}>
{[]}
</ListBox>
<ListBox
renderEmptyState={() => <Text>No data was found</Text>}
>
{[]}
</ListBox>
<ListBox
renderEmptyState={() => (
<Text>
No data was found
</Text>
)}
>
{[]}
</ListBox>
Slots
The ListBox comes with pre-styled slots for components like avatars, texts, descriptions and keyboard shortcuts.
function Example() {
const list = useAsyncListState({
async loadItems(state) {
let { cursor, signal, filterValue } = state;
if (cursor) {
cursor = cursor.replace(/^http:\/\//i, 'https://');
}
const response = await fetch(
cursor || `https://swapi.py4e.com/api/people/?search=`,
{ signal }
);
const { results, next } = await response.json();
return { ...state, items: results, cursor: next };
}
});
return (
<ListBox
aria-label="What is your favorite star wars character?"
items={list.items}
isLoading={list.isLoading}
onLoadMoreItems={list.loadMoreItems}
selectionMode="single"
style={{ maxHeight: '300px', maxWidth: '250px' }}
>
{(item) => (
<Item key={item.name}>
<Avatar src="https://cdn-icons-png.flaticon.com/512/147/147144.png?w=360" />
<Text>{item.name}</Text>
<Description>Height: {item.height} Weight: {item.mass}</Description>
</Item>
)}
</ListBox>
);
}
function Example() {
const list = useAsyncListState({
async loadItems(state) {
let { cursor, signal, filterValue } = state;
if (cursor) {
cursor = cursor.replace(/^http:\/\//i, 'https://');
}
const response = await fetch(
cursor ||
`https://swapi.py4e.com/api/people/?search=`,
{ signal }
);
const { results, next } = await response.json();
return { ...state, items: results, cursor: next };
}
});
return (
<ListBox
aria-label="What is your favorite star wars character?"
items={list.items}
isLoading={list.isLoading}
onLoadMoreItems={list.loadMoreItems}
selectionMode="single"
style={{ maxHeight: '300px', maxWidth: '250px' }}
>
{(item) => (
<Item key={item.name}>
<Avatar src="https://cdn-icons-png.flaticon.com/512/147/147144.png?w=360" />
<Text>{item.name}</Text>
<Description>
Height: {item.height} Weight: {item.mass}
</Description>
</Item>
)}
</ListBox>
);
}
function Example() {
const list =
useAsyncListState({
async loadItems(
state
) {
let {
cursor,
signal,
filterValue
} = state;
if (cursor) {
cursor = cursor
.replace(
/^http:\/\//i,
'https://'
);
}
const response =
await fetch(
cursor ||
`https://swapi.py4e.com/api/people/?search=`,
{ signal }
);
const {
results,
next
} =
await response
.json();
return {
...state,
items: results,
cursor: next
};
}
});
return (
<ListBox
aria-label="What is your favorite star wars character?"
items={list.items}
isLoading={list
.isLoading}
onLoadMoreItems={list
.loadMoreItems}
selectionMode="single"
style={{
maxHeight:
'300px',
maxWidth: '250px'
}}
>
{(item) => (
<Item
key={item.name}
>
<Avatar src="https://cdn-icons-png.flaticon.com/512/147/147144.png?w=360" />
<Text>
{item.name}
</Text>
<Description>
Height:{' '}
{item.height}
{' '}
Weight:{' '}
{item.mass}
</Description>
</Item>
)}
</ListBox>
);
}
Accessibility
In order to support internationalization, provide a localized string to the aria-label prop.