Skip to content

Commit

Permalink
feat(collection): Add support for string children (#2202)
Browse files Browse the repository at this point in the history
Add string children support to the collection system via `useListItemAllowChildStrings` elemProps hook.

[category:Components]
  • Loading branch information
NicholasBoll committed May 5, 2023
1 parent b81bd7c commit 5fd436a
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 2 deletions.
3 changes: 2 additions & 1 deletion modules/react/collection/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export * from './lib/useListItemRovingFocus';
export * from './lib/useListItemSelect';
export * from './lib/useListModel';
export * from './lib/useGridModel';
export {ListBox} from './lib/ListBox';
export * from './lib/useListItemAllowChildStrings';
export {ListBox, ListBoxProps} from './lib/ListBox';
export {
singleSelectionManager,
multiSelectionManager,
Expand Down
37 changes: 37 additions & 0 deletions modules/react/collection/lib/useListItemAllowChildStrings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {createElemPropsHook} from '@workday/canvas-kit-react/common';
import {useListModel} from '@workday/canvas-kit-react/collection';

/**
* 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 defined _before_ {@link useListItemRegister} because it sets the `data-id`
* attribute if one hasn't been defined by the application.
*
* An example might look like:
* ```tsx
* const useMyListItem = composeHooks(
* // any other hooks here
* useListItemSelect,
* useListItemRegister,
* useListItemAllowChildStrings
* )
*
*
* <MyList onSelect={({id}) => {
* console.log(id) // will be "First" or "Second"
* }}>
* <MyList.Item>First</MyList.Item>
* <MyList.Item>Second</MyList.Item>
* </MyList>
* ```
*/
export const useListItemAllowChildStrings = createElemPropsHook(useListModel)(
(_, __, elemProps: {'data-id'?: string; children?: React.ReactNode} = {}) => {
const props: {'data-id'?: string} = {};
if (!elemProps['data-id'] && typeof elemProps.children === 'string') {
props['data-id'] = elemProps.children;
}

return props;
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';

import {renderHook} from '@testing-library/react-hooks';
import {useListModel, useListItemAllowChildStrings} from '@workday/canvas-kit-react/collection';

describe('useListItemAllowChildSiblings', () => {
it('should add a [data-id] attribute equal to the string value', () => {
const {result} = renderHook(() => {
const model = useListModel();
return useListItemAllowChildStrings(model, {children: 'Foobar'});
});

expect(result.current).toHaveProperty('data-id', 'Foobar');
});

it('should not add a [data-id] attribute if the child is not a string', () => {
const {result} = renderHook(() => {
const model = useListModel();
return useListItemAllowChildStrings(model, {children: <div>FooBar</div>});
});

expect(result.current).not.toHaveProperty('data-id');
});
});
17 changes: 16 additions & 1 deletion modules/react/collection/stories/Collection.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {SymbolDoc} from '@workday/canvas-kit-docs';
import {ListBox} from '@workday/canvas-kit-react/collection';

import {Basic} from './examples/Basic';
import {StringChildren} from './examples/StringChildren';
import {DynamicItems} from './examples/DynamicItems';
import {BasicVirtual} from './examples/BasicVirtual';
import {IdentifiedItems} from './examples/IdentifiedItems';
Expand Down Expand Up @@ -34,7 +35,7 @@ yarn add @workday/canvas-kit-react
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 List](#dynamic-list) example).
as [Dynamic List](#dynamic-list) example.

<ExampleCodeBlock code={Basic} />

Expand Down Expand Up @@ -88,6 +89,16 @@ uses `ListBox` and creates a custom `SelectableItem` elemProps hook and componen

<ExampleCodeBlock code={Selection} />

### 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.

<ExampleCodeBlock code={StringChildren} />

#### Multiple Selection

The `selection` manager can be passed directly to the model configuration to handle different
Expand Down Expand Up @@ -148,6 +159,10 @@ columns, but not rows. This is the default navigation manager for lists.

<SymbolDoc name="useListItemRegister" fileName="/react/" />

### `useListItemAllowChildStrings`

<SymbolDoc name="useListItemAllowChildStrings" fileName="/react/" />

### `useListItemRovingFocus`

<SymbolDoc name="useListItemRovingFocus" fileName="/react/" />
Expand Down
35 changes: 35 additions & 0 deletions modules/react/collection/stories/examples/StringChildren.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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 const StringChildren = () => {
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>
</>
);
};

0 comments on commit 5fd436a

Please sign in to comment.