Collection API
Examples
Basic Example
The ListBox on its own isn’t very useful. It registers each item with the model. The
ListBox.Item only uses the useListItemRegister hook which handles registration of static items
to the model. The ListBox uses useListRenderItems which handles rendering static items as well
as dynamic items.
- First
- Second
import React from 'react';
import {ListBox} from '@workday/canvas-kit-react/collection';
export default () => {
return (
<ListBox>
<ListBox.Item>First</ListBox.Item>
<ListBox.Item>Second</ListBox.Item>
</ListBox>
);
};
Identifying Items
A list item takes an optional data-id property that will be used to identify an item. Without a
data-id, the identifier will be the item’s index when first registered. The basic example has a
data-id attribute that is a string representation of the index. Providing a data-id will
override to a value of your choosing. This identifier will be used by other hooks to identify the
item for selection, maintaining a cursor, or anything else.
- First
- Second
import React from 'react';
import {ListBox} from '@workday/canvas-kit-react/collection';
export default () => {
return (
<ListBox>
<ListBox.Item data-id="first">First</ListBox.Item>
<ListBox.Item data-id="second">Second</ListBox.Item>
</ListBox>
);
};
Dynamic Items
The ListBox also handles a dynamic collection of items. Instead of providing each ListBox.Item
statically, provide a render function instead. The function is called with an item value that is
the same as what’s provided to the model.
Registering Items
Dynamic items can be passed in as an array of strings or objects via the items prop or the model
config.
Array of Strings
Use strings when your data is a simple list of options. Each string value is used as the unique identifer.
import React from 'react';
import {ListBox} from '@workday/canvas-kit-react/collection';
const items = ['Pizza', 'Chocolate', 'Cheeseburgers'];
export default () => (
<ListBox items={items}>{item => <ListBox.Item>{item}</ListBox.Item>}</ListBox>
);
Array of Objects
Use objects when your data contains additional information. Each object must have an id to ensure
it’s registered and searchable. Additional information such as icons or if the item is disabled can
be included in the object and will be accessible in the render function.
import React from 'react';
import {ListBox} from '@workday/canvas-kit-react/collection';
const items = [
{id: 'Atlanta (United States)'},
{id: 'Amsterdam (Europe)'},
{id: 'Austin (United States)'},
{id: 'Beaverton (United States)', disabled: true},
{id: 'Belfast (Europe)'},
{id: 'Berlin (Europe)'},
{id: 'Boston (United States)'},
{id: 'Boulder (United States)'},
{id: 'Chicago (United States)'},
];
export default () => (
<ListBox items={items}>
{item => <ListBox.Item aria-disabled={item.disabled}>{item.id}</ListBox.Item>}
</ListBox>
);
**Note: The
idshould match your text value for each item. This ensures proper selection of items in components likeSelectandAutocomplete. If youridis different from your text value, you’ll have to add a translation layer. **
Virtualization
By default, providing items dynamically will enable virtualization. This example adds a maxHeight
to ensure overflow. Virtualization uses absolute positioning of each item, which could cause
problems for popup menus. If your item count is low, pass shouldVirtualize={false} to disable
virtualization.
import React from 'react';
import {ListBox} from '@workday/canvas-kit-react/collection';
interface Item {
id: string;
text: string;
}
const items: Item[] = Array(1000)
.fill(true)
.map((_, index) => ({id: String(index + 1), text: `Item - ${index + 1}`}));
export default () => {
return (
<ListBox items={items} maxHeight={300}>
{(item: Item) => <ListBox.Item data-id={item.id}>{item.text}</ListBox.Item>}
</ListBox>
);
};
DataLoader
The collection system comes with a data loader to help with dynamic collections. You must provide
total, pageSize, and an asynchronous load function. The loader will call the load function
when the user navigates the collection and needs more data. This example shows how to hook up a
simple data loader and mocks a load function to simulate an asynchronous response.
The data loader also takes a model argument to know which model to create. The loader configures the model to handle asynchronous keyboard navigation and will return the configured model to you to pass along to the collection component.
Scroll or focus and use keys to navigate
Events:
import React from 'react';
import {
ListBox,
useListLoader,
useListModel,
useListItemSelect,
useListItemRovingFocus,
LoadReturn,
} from '@workday/canvas-kit-react/collection';
import {Box, Flex} from '@workday/canvas-kit-react/layout';
import {composeHooks} from '@workday/canvas-kit-react/common';
function pickRandom<T>(arr: T[]): T {
return arr[Math.floor(Math.random() * arr.length)];
}
const useListItem = composeHooks(useListItemSelect, useListItemRovingFocus);
const colors = ['Blue', 'Red', 'Purple', 'Green', 'Pink'];
const fruits = ['Apple', 'Orange', 'Banana', 'Grape', 'Lemon', 'Lime'];
const options = Array(1000)
.fill('')
.map((_, index) => {
return `${pickRandom(colors)} ${pickRandom(fruits)} ${index + 1}`;
});
export default () => {
const [messages, setMessages] = React.useState<string[]>([]);
const {model, loader} = useListLoader(
{
getId: (item: string) => item,
getTextValue: (item: string) => item,
shouldVirtualize: true,
total: 1000,
pageSize: 20,
async load({pageNumber, pageSize}) {
setMessages(messages => messages.concat(`Page ${pageNumber} loading`));
// Return a promise, but use setTimeout to mock a delayed server response
return new Promise<LoadReturn<string>>(resolve => {
setTimeout(() => {
const start = (pageNumber - 1) * pageSize;
const end = start + pageSize;
const total = options.length;
const items = options.slice(start, end);
setMessages(messages => messages.concat(`Page ${pageNumber} loaded`));
resolve({
items,
total,
});
}, 500);
});
},
},
useListModel
);
return (
<Flex gap="xl">
<Flex flexDirection="column" gap="zero">
<p>Scroll or focus and use keys to navigate</p>
<ListBox model={model} maxHeight={400} width={300}>
{item => (
<ListBox.Item
as="button"
role="listitem"
elemPropsHook={useListItem}
height={20}
background="transparent"
border="none"
>
{item}
</ListBox.Item>
)}
</ListBox>
</Flex>
<Flex flexDirection="column" gap="zero">
<p>Events:</p>
<Box as="ul" maxHeight={400} overflowY="auto">
{messages.map(message => (
<li key={message}>{message}</li>
))}
</Box>
</Flex>
</Flex>
);
};
Roving Tabindex
The list system also includes a cursor that extends the list. A cursor is mostly used for focusing
items. The
roving tabindex
is a well-supported way to accomplish accessibility requirements for focusing items within a list.
This example shows how to use useListRovingFocus. This example uses the ListBox component, but
the default ListBox.Item is very basic. We have two options, we can either pass additional
functionality via elemPropsHook or by creating a new item using our elemProps hook primitives.
Both will be demonstrated. Creating a custom item is recommended if you create a custom component
and export it. Using elemPropsHook with ListBox.Item is recommended only for one-off instances.
You can either use the tab key for focus on an item or click on an item and then use the up/down
keys to navigation the list. By default, the list is set to wrap navigation using the
wrappingNavigationManager. Only a single item in the list is a focus stop that “roves” as the
up/down arrows are pressed.
Note: This example doesn’t meet accessibility requirements. The list will have to have some type of context. Like “navigation list” or “menu list”.
- First
- Second
- Third
import React from 'react';
import {
useListItemRegister,
useListItemRovingFocus,
useListModel,
ListBox,
ListItemProps,
} from '@workday/canvas-kit-react/collection';
import {composeHooks, createSubcomponent} from '@workday/canvas-kit-react/common';
// create our own hook using `useListItemRegister` and `useListItemRovingFocus`. Note the
// `useListItemRegister` must be the last hook when using `composeHooks`
const useRovingFocusItem = composeHooks(useListItemRovingFocus, useListItemRegister);
// create our own item. We use `modelHook` to define which model should be used and `elemPropsHook`
// to determine which elemProps hook should be used. `elemProps` will be populated with props to
// pass to the element
const RovingFocusItem = createSubcomponent('li')({
displayName: 'RovingFocusItem',
modelHook: useListModel,
elemPropsHook: useRovingFocusItem,
})<ListItemProps>((elemProps, Element) => {
return <Element {...elemProps} />;
});
export default () => {
return (
<ListBox>
{/* We can use `ListBox.Item` and add `useListItemRovingFocus`. Useful for one-off */}
<ListBox.Item data-id="first" elemPropsHook={useListItemRovingFocus}>
First
</ListBox.Item>
{/* Use a custom item. Useful for reusing components */}
<RovingFocusItem data-id="second">Second</RovingFocusItem>
<RovingFocusItem data-id="third">Third</RovingFocusItem>
</ListBox>
);
};
Selection
Lists support selection. useSelectionItem is applied to an item which adds an onClick that adds
the item to the state.selectedIds. The default selection manager is a single select. This example
uses ListBox and creates a custom SelectableItem elemProps hook and component.
Cursor ID:
Selected ID: first
import React from 'react';
import {
useListItemRegister,
useListItemRovingFocus,
useListItemSelect,
useListModel,
ListItemProps,
ListBox,
getCursor,
} from '@workday/canvas-kit-react/collection';
import {
composeHooks,
createElemPropsHook,
createSubcomponent,
} from '@workday/canvas-kit-react/common';
// Create a custom hook for our item
const useItem = composeHooks(
createElemPropsHook(useListModel)((model, ref, elemProps: ListItemProps) => {
return {
role: 'listitem',
style: {
background: model.state.selectedIds.includes(elemProps['data-id']) ? 'gray' : 'white',
},
};
}),
useListItemSelect,
useListItemRovingFocus,
useListItemRegister
);
// Create a custom item
const SelectableItem = createSubcomponent('button')({
displayName: 'SelectableItem',
modelHook: useListModel,
elemPropsHook: useItem,
})<ListItemProps>((elemProps, Element) => {
return <Element {...elemProps} />;
});
export default () => {
const model = useListModel({
initialSelectedIds: ['first'],
orientation: 'horizontal',
});
return (
<>
<ListBox model={model}>
<SelectableItem data-id="first">First</SelectableItem>
<SelectableItem data-id="second">Second</SelectableItem>
<SelectableItem data-id="third">Third</SelectableItem>
</ListBox>
<p>Cursor ID: {getCursor(model.state)}</p>
<p>Selected ID: {model.state.selectedIds[0]}</p>
</>
);
};
String Children
Sometimes it is desired to allow the string children to be the identifiers for each item. This could
be useful for autocomplete components where the item’s text is the desired identifier. Normally, if
no data-id is provided to the item, the system will choose the registration index. This would be
'0', '1', and so on. By passing useListItemAllowChildStrings to an item component, it will
change this behavior to use the child text if no data-id is provided.
import React from 'react';
import {
ListBox,
useListItemRegister,
useListItemAllowChildStrings,
useListItemSelect,
useListModel,
} from '@workday/canvas-kit-react/collection';
import {composeHooks, createSubcomponent} from '@workday/canvas-kit-react/common';
const useItem = composeHooks(useListItemSelect, useListItemRegister, useListItemAllowChildStrings);
const Item = createSubcomponent('button')({
modelHook: useListModel,
elemPropsHook: useItem,
})((elemProps, Element) => {
return <Element {...elemProps} />;
});
export default () => {
const model = useListModel();
return (
<>
<ListBox model={model}>
<Item>First</Item>
<Item>Second</Item>
</ListBox>
<div>
Selected: <span id="selected">{model.state.selectedIds[0] || 'Nothing'}</span>
</div>
</>
);
};
Multiple Selection
The selection manager can be passed directly to the model configuration to handle different
selection types. This example passes the multiSelectionManager to handle selecting multiple items.
Cursor ID:
Selected IDs: first,second
import React from 'react';
import {
multiSelectionManager,
useListItemRegister,
useListItemRovingFocus,
useListItemSelect,
useListModel,
ListItemProps,
ListBox,
getCursor,
} from '@workday/canvas-kit-react/collection';
import {composeHooks, createSubcomponent} from '@workday/canvas-kit-react/common';
const useMultiSelectItem = composeHooks(
useListItemSelect,
useListItemRovingFocus,
useListItemRegister
);
const Item = createSubcomponent('button')({
displayName: 'MultiSelectableItem',
modelHook: useListModel,
elemPropsHook: useMultiSelectItem,
})<ListItemProps>((elemProps, Element, model) => {
return (
<Element
role="listitem"
{...elemProps}
style={{
background: model.state.selectedIds.includes(elemProps['data-id']) ? 'gray' : 'white',
}}
/>
);
});
export default () => {
const model = useListModel({
initialSelectedIds: ['first', 'second'],
selection: multiSelectionManager,
orientation: 'horizontal',
});
return (
<>
<ListBox model={model}>
<Item data-id="first">First</Item>
<Item data-id="second">Second</Item>
<Item data-id="third">Third</Item>
</ListBox>
<p>Cursor ID: {getCursor(model.state)}</p>
<p>
Selected IDs: {(model.state.selectedIds !== 'all' ? model.state.selectedIds : []).join(',')}
</p>
</>
);
};
Basic Grid
A grid is a two dimensional list. A columnCount config is added to inform how to break up an array
of items. A grid is very similar to a list - it receives items as a single dimension list and uses
the columnCount to determine keyboard navigation. Grids only support a single orientation.
import React from 'react';
import {Flex, Box} from '@workday/canvas-kit-react/layout';
import {
ListBox,
useGridModel,
useListItemSelect,
useListItemRovingFocus,
useListItemRegister,
} from '@workday/canvas-kit-react/collection';
import {composeHooks, createSubcomponent} from '@workday/canvas-kit-react/common';
const useItem = composeHooks(useListItemSelect, useListItemRovingFocus, useListItemRegister);
const Item = createSubcomponent('button')({
modelHook: useGridModel,
elemPropsHook: useItem,
})((elemProps, Element, model) => {
return (
<Box
as={Element}
{...elemProps}
width={40}
border="solid 1px black"
style={{
background: model.state.selectedIds.includes(elemProps['data-id']) ? 'gray' : 'white',
}}
/>
);
});
export default () => {
const model = useGridModel({
columnCount: 5,
// @ts-ignore Create an array of [{id: 1}, ...{id: n}]
items: [...Array(25).keys()].map(i => ({id: `${i + 1}`})),
// we don't need virtualization here
shouldVirtualize: false,
});
return (
<ListBox model={model} as={Flex} flexDirection="row" flexWrap="wrap" width={200}>
{item => <Item>{item.id}</Item>}
</ListBox>
);
};
Wrapping Grid
By default, navigating a grid does not wrap around when the user reaches the end of a row or column.
The grid model supports passing in a navigation manager. The collection system supports two types of
navigation managers, a non-wrapping navigationManager and the wrappingNavigationManager. The
following example passes the wrappingNavigationManager manager to the model. Observe how the
cursor wraps around columns and rows when an edge of a column or row is encountered.
import React from 'react';
import {Flex, Box} from '@workday/canvas-kit-react/layout';
import {
ListBox,
useGridModel,
useListItemSelect,
useListItemRovingFocus,
useListItemRegister,
wrappingNavigationManager,
} from '@workday/canvas-kit-react/collection';
import {composeHooks, createSubcomponent} from '@workday/canvas-kit-react/common';
const useItem = composeHooks(useListItemSelect, useListItemRovingFocus, useListItemRegister);
const Item = createSubcomponent('button')({
modelHook: useGridModel,
elemPropsHook: useItem,
})((elemProps, Element, model) => {
return (
<Box
as={Element}
{...elemProps}
width={40}
border="solid 1px black"
style={{
background: model.state.selectedIds.includes(elemProps['data-id']) ? 'gray' : 'white',
}}
/>
);
});
export default () => {
const model = useGridModel({
columnCount: 5,
// @ts-ignore Create an array of [{id: 1}, ...{id: n}]
items: [...Array(25).keys()].map(i => ({id: `${i + 1}`})),
// we don't need virtualization here
shouldVirtualize: false,
navigation: wrappingNavigationManager,
});
return (
<ListBox model={model} as={Flex} flexDirection="row" flexWrap="wrap" width={200}>
{item => <Item>{item.id}</Item>}
</ListBox>
);
};
Overflow Vertical List
A List can overflow vertically or horizontally to account for responsive resizing or an overflow of
items. Using multiple hooks from the Collection system like useOverflowListModel and ensuring that
orientationis set tovertical, you can achieve vertical overflow lists. In the example below,
when the window is resized vertically, items in the Sidebar will overflow into the “More Actions”
button.
import React from 'react';
import {ActionBar, useActionBarModel} from '@workday/canvas-kit-react/action-bar';
import {PrimaryButton} from '@workday/canvas-kit-react/button';
import {Box} from '@workday/canvas-kit-react/layout';
import styled from '@emotion/styled';
import {StyledType} from '@workday/canvas-kit-react/common';
type MyActionItem = {
id: string;
text: React.ReactNode;
};
const StyledActionbarList = styled(ActionBar.List)<StyledType>({
'> *': {
flex: '0 0 auto',
},
});
export default () => {
const [items] = React.useState<MyActionItem[]>([
{id: 'first', text: 'First Action'},
{id: 'second', text: 'Second Action'},
{id: 'third', text: 'Third Action'},
{id: 'fourth', text: 'Fourth Action'},
{id: 'fifth', text: 'Fifth Action'},
{id: 'sixth', text: 'Sixth Action'},
{id: 'seventh', text: 'Seventh Action'},
]);
const model = useActionBarModel({items, orientation: 'vertical', maximumVisible: 4});
return (
<>
<Box marginBottom="xl" height="50vh">
<ActionBar model={model}>
<StyledActionbarList
position="relative"
as="section"
aria-label="Overflow example actions"
flexDirection="column"
height="100%"
overflowButton={
<ActionBar.OverflowButton
cs={{overflow: 'visible', flex: 0}}
aria-label="More actions"
/>
}
>
{(item: MyActionItem, index) => (
<ActionBar.Item
as={index === 0 ? PrimaryButton : undefined}
onClick={() => console.log(item.id)}
>
{item.text}
</ActionBar.Item>
)}
</StyledActionbarList>
<ActionBar.Menu.Popper>
<ActionBar.Menu.Card maxWidth={300} maxHeight={200}>
<ActionBar.Menu.List>
{(item: MyActionItem) => (
<ActionBar.Menu.Item onClick={() => console.log(item.id)}>
{item.text}
</ActionBar.Menu.Item>
)}
</ActionBar.Menu.List>
</ActionBar.Menu.Card>
</ActionBar.Menu.Popper>
</ActionBar>
</Box>
</>
);
};
Component API
ListBox
ListBox
The ListBox component that offers vertical rendering of a collection in the form of a
2-dimension list. It supports virtualization, rendering only visible items in the DOM while also
providing aria attributes to allow screen readers to still navigate virtual lists. The ListBox
contains a basic ListBox.Item that renders list items that render correctly with virtualization
and adds aria-setsize and aria-posinset for screen readers.
The ListBox is very basic and only adds enough functionality to render correctly. No additional
behaviors are added to navigate or select. React Hooks are provided to add this functionality and
are used by higher level components like Menu and Menu.Item which utilize ListBox. The
ListBox contains two Box elements:
- Outer Box: Presentational container element responsible for overflow and height.
heightandmaxHeightprops will be applied here. - Inner Box: The element responsible for the virtual container. Height is controlled by the model and cannot be changed by the developer. All props and ref will be spread to this element.
Layout Component
ListBox supports all props from thelayout component.
Props
Props extend from ul. Changing the as prop will change the element interface.
Props extend from . If a model is passed, props from ListModelConfig are ignored.
| Name | Type | Description | Default |
|---|---|---|---|
children | ReactNode | (( | ||
marginTop | | Set the margin top of the list box. You must use this prop and not style any other way. The
| |
marginBottom | | Set the margin bottom of the list box. You must use this prop and not style any other way. The
| |
marginY | | Set the margin top and bottom of the list box. You must use this prop and not style any other way. The
| |
cs | | The | |
as | React.ElementType | Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care. | ul |
ref | React.Ref<R = ul> | Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If | |
model | | Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context. | |
elemPropsHook | ( | Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one. |
useListBox
(
model: ,
elemProps: {},
ref: React.Ref
) => {
style: {
position: 'relative';
height: number | undefined;
};
}ListBox.Item
The ListBox.Item is a simple placeholder for listbox items. The functionality of a
collection item varies between components. For example, a Tabs.Item and a Menu.Item have
shared functionality, but have different behavior. All collection-based components should
implement a custom Item subcomponent using the collection-based behavior hooks. The Roving
Tabindex example uses the elemPropsHook to provide additional
functionality. elemPropsHook is provided on all compound components and is useful in the
example to add additional functionality without making a new component.
Layout Component
Item supports all props from thelayout component.
Props
Props extend from li. Changing the as prop will change the element interface.
| Name | Type | Description | Default |
|---|---|---|---|
cs | | The | |
children | ReactNode | ||
as | React.ElementType | Optional override of the default element used by the component. Any valid tag or Component. If you provided a Component, this component should forward the ref using Note: Not all elements make sense and some elements may cause accessibility issues. Change this value with care. | li |
ref | React.Ref<R = li> | Optional ref. If the component represents an element, this ref will be a reference to the real DOM element of the component. If | |
model | | Optional model to pass to the component. This will override the default model created for the component. This can be useful if you want to access to the state and events of the model, or if you have nested components of the same type and you need to override the model provided by React Context. | |
elemPropsHook | ( | Optional hook that receives the model and all props to be applied to the element. If you use this, it is your responsibility to return props, merging as appropriate. For example, returning an empty object will disable all elemProps hooks associated with this component. This allows finer control over a component without creating a new one. |
useListItemRegister
This elemProps hook is the base of all item component hooks. It registers an item with a
collection and sets the data-id that is used by other hooks. It should always be the last
defined hook when using composeHooks (composeHooks executes hooks right to left and merges
props left to right). It is used by ListBox.Item and all *.Item subcomponents.
const useMyItem = composeHooks(
useListItemSelect, // additional hooks go here
useListItemRegister // always last
);
(
model: ,
elemProps: {
data-id: string;
data-text: string;
data-has-children: boolean;
children: ReactNode;
index: number;
disabled: boolean;
},
ref: React.Ref
) => {
ref: (instance: | null) => void;
data-id: string;
disabled: true | undefined;
aria-setsize: number | undefined;
aria-posinset: number | undefined;
data-index: number | undefined;
style: ;
id: string;
}Model
useListModel
The List model contains the the state and events necessary to track items, selection, and a cursor. Various hooks can be used for a List model to create common behaviors associated with lists, such as navigating a list with a keyboard, selection (single and multiple), and virtualization.
A list also has a "cursor". A cursor is often represented by focus, but it is not always a 1:1
mapping. Think of the cursor as the focus item within the list. If the list has browser focus, the
cursor will map to browser focus. Behaviors such as useListRovingFocus will map the cursor to the
active tab stop of the list. For more information, see
Roving Tabindex. useListRovingFocus
adds keyboard events that map to navigation events. A Navigation Manager is
used to map new cursor ids to these events. The ListModel takes an optional navigation
configuration to change the default navigation behavior. The default navigation manager is a
wrappingNavigationManager meaning the cursor will wrap around the
beginning and the ends. The cursor also provides a navigationManager that does
not wrap. This is the default navigation for grids.
The cursor also adds the concept of orientation which defaults to 'vertical'. A Tab list is an
example of a 'horizontal' list.
const list = useListModel({
// custom handling for selection. single and multi select are provided
selection: mySelectionManager,
// wrapping and non-wrapping navigation are provided
navigation: myNavigationManager,
items: [{ id: '1', text: 'First'}, { id: '2', text: 'Second' }],
getId: item => item.id, // get the unique identifier of your item
})
useListModel (config: ): useGridModel
The Grid model extends the ListModel and changes some config. For example, the columnCount is
required on the grid model's configuration and orientation is removed.
useGridModel (config: ): Navigation Manager
NavigationManager
The list and grid models accept a navigation config. If one is not provided, a default will be
chosen. It is possible to create a custom navigation manager to hand to the model if the default
doesn't work.
| Name | Type | Description | Default |
|---|---|---|---|
getFirst | | Get the first item in a collection. This will be called when the | |
getLast | | Get the last item in a collection. This will be called when the | |
getItem | ( | Get an item with the provided | |
getNext | | Get the next item after the provided | |
getNextRow | | For Grids: Get the cell in the next row from the provided | |
getPrevious | | Get the previous item before the provided | |
getPreviousRow | | For Grids: Get the cell in the previous row from the provided | |
getFirstOfRow | | For Grids: Get the first item in a row. This will be called when the | |
getLastOfRow | | For Grids: Get the last item in a row. This will be called when the | |
getNextPage | | Get the next "page". A "page" is application specific and usually means next visible screen.
If the viewport is scrollable, it would scroll so that the last item visible is now the first
item visible. This is called when the | |
getPreviousPage | | Get the next "page". A "page" is application specific and usually means previous visible
screen. If the viewport is scrollable, it would scroll so that the first item visible is now
the last item visible. This is called when the |
navigationManager
The navigationManager implements the Navigation Manager interface for lists
and grids, but does not wrap when the user hits a boundary of the collection. This is the default
navigation manager for grids.
wrappingNavigationManager
The wrappingNavigationManager implements the Navigation Manager interface
for lists and grids, and wraps when the user hits a boundary of the collection. Grids will wrap
columns, but not rows. This is the default navigation manager for lists.
Selection Manager
SelectionManager
The list and grid models accept a selection config. If one is not provided,
singleSelectManager is used. You can provide a custom select manager to suite your needs. A
selection manager is an object with a single select method that takes an id and previously
selected ids and returns a new set of selected ids.
The collection system provides two selection managers: singleSelectManager and
multiSelectionManager.
| Name | Type | Description | Default |
|---|---|---|---|
select | ( | Sets a new |
Hooks
useListItemRegister
This elemProps hook is the base of all item component hooks. It registers an item with a
collection and sets the data-id that is used by other hooks. It should always be the last
defined hook when using composeHooks (composeHooks executes hooks right to left and merges
props left to right). It is used by ListBox.Item and all *.Item subcomponents.
const useMyItem = composeHooks(
useListItemSelect, // additional hooks go here
useListItemRegister // always last
);
(
model: ,
elemProps: {
data-id: string;
data-text: string;
data-has-children: boolean;
children: ReactNode;
index: number;
disabled: boolean;
},
ref: React.Ref
) => {
ref: (instance: | null) => void;
data-id: string;
disabled: true | undefined;
aria-setsize: number | undefined;
aria-posinset: number | undefined;
data-index: number | undefined;
style: ;
id: string;
}useListItemAllowChildStrings
This elemProps hook allows for children values to be considered identifiers if the children are
strings. This can be useful for autocomplete or select components that allow string values. This
hook must be passed after {@link useListItemRegister } because this hook sets the data-id
attribute if one hasn't been defined by the application.
An example might look like:
const useMyListItem = composeHooks(
// any other hooks here
useListItemSelect,
useListItemRegister,
useListItemAllowChildStrings // always the last in the list
)
<MyList onSelect={({id}) => {
console.log(id) // will be "First" or "Second"
}}>
<MyList.Item>First</MyList.Item>
<MyList.Item>Second</MyList.Item>
</MyList>
(
model: ,
elemProps: {
data-id: string;
children: ReactNode;
item: <any>;
},
ref: React.Ref
) => {
data-id: string | undefined;
}useListItemRovingFocus
This elemProps hook is used for cursor navigation by using Roving
Tabindex. Only a single item in the
collection has a tab stop. Pressing an arrow key moves the tab stop to a different item in the
corresponding direction. See the Roving Tabindex example. This elemProps hook
should be applied to an *.Item component.
const useMyItem = composeHooks(
useListItemRovingFocus, // adds the roving tabindex support
useListItemRegister
);
(
model: ,
elemProps: {
data-id: string;
},
ref: React.Ref
) => {
onKeyDown: (event: <>) => void;
onClick: () => void;
data-focus-id: string;
tabIndex: 0 | unknown {
"kind": "unknown",
"value": "unknown",
"text": "SyntheticNode - PrefixUnaryExpression"
};
}useListItemSelect
This elemProps hook adds selection support to a *.Item subcomponent of a collection. It adds a
click handler that toggles selection status according to the Selection
Manager used.
const useMyItem = composeHooks(
useListItemSelect, // adds selection support to an item
useListItemRegister
);
(
model: ,
elemProps: {
data-id: string;
},
ref: React.Ref
) => {
onClick: (event: <>) => void;
}useListRenderItems
This hook is meant to be used inside the render function of List style components. It is used
by ListBox. This hook gives list-based components their static and dynamic APIs to handle list
items. This hook should only be used if you want to implement your own List. For example,
Tabs.List uses this hook, but Menu.List uses ListBox which uses this hook.
const MyList = createContainer('ul')({
modelHook: useListModel,
})((elemProps, Element, model) => {
return <Element {...elemProps}>{useListRenderItems(model, elemProps.children)}</Element>;
});
Props
| Name | Type | Description | Default |
|---|---|---|---|
state | { | ||
events | { | ||
getId | (item: ) => string | ||
getTextValue | (item: ) => string |
useListResetCursorOnBlur
This elemProps hook resets the cursor when the list looses focus. By default,
useListItemRovingFocus will leave the last focused item as the
focusable item in the list. Sometimes it is desireable to reset the cursor to the last selected
item. For example, Tabs.Item uses this hook to reset the tab stop to the currently selected tab.
const useMyItem = composeHooks(
useListResetCursorOnBlur, // adds the cursor reset to selected for roving tabindex
useListItemRovingFocus,
useListItemRegister
);
(
model: ,
elemProps: {},
ref: React.Ref
) => {
onKeyDown: (event: ) => void;
onFocus: () => void;
onBlur: () => void;
}useOverflowListItemMeasure
This elemProps hook measures a list item and reports it to an OverflowListModel. This is used
in overflow detection.
const useMyItem = composeHooks(
useOverflowListItemMeasure,
useListRegisterItem,
)
(
model: ,
elemProps: {
data-id: string;
},
ref: React.Ref
) => {
ref: (instance: | null) => void;
aria-hidden: true | undefined;
style: {};
inert: boolean | '' | undefined;
}useOverflowListMeasure
This elemProps hook measures a list and reports it to an OverflowListModel. This is used in
overflow detection.
(
model: ,
elemProps: {},
ref: React.Ref
) => {
ref: (instance: | null) => void;
}useOverflowListTarget
This elemProps hook measures an overflow list target and reports it to an OverflowListModel.
This is used in overflow detection.
(
model: ,
elemProps: {},
ref: React.Ref
) => {
ref: (instance: | null) => void;
aria-hidden: boolean;
tabIndex: 0 | unknown {
"kind": "unknown",
"value": "unknown",
"text": "SyntheticNode - PrefixUnaryExpression"
};
style: {
position: 'absolute';
left: unknown {
"kind": "unknown",
"value": "unknown",
"text": "SyntheticNode - PrefixUnaryExpression"
};
} | undefined;
}useListLoader
Create a data loader and a model. The list loader should be used on virtual data sets with possibly large amounts of data. The model should be passed to a collection component. The loader can be used to manipulate filters, sorters, and clear cache.
A simple loader using fetch could look like the following:
const {model, loader} = useListLoader(
{
total: 1000,
pageSize: 20,
async load({pageNumber, pageSize}) {
return fetch('/myUrl')
.then(response => response.json())
.then(response => {
return {total: response.total, items: response.items};
});
},
},
useListModel
);
<T, M extends ((args: any[]) => any) & >(
config: <T, > & unknown {
"kind": "unknown",
"value": "unknown",
"text": "M['TConfig']"
},
modelHook: M
) => {
model: <M>;
loader: ;
}Can't Find What You Need?
Check out our FAQ section which may help you find the information you're looking for. For further information, contact the #ask-canvas-design or #ask-canvas-kitchannels on Slack.