diff --git a/packages/react-components/src/components/Picker/Picker.spec.tsx b/packages/react-components/src/components/Picker/Picker.spec.tsx index 50af3473a..ae7f7f3b7 100644 --- a/packages/react-components/src/components/Picker/Picker.spec.tsx +++ b/packages/react-components/src/components/Picker/Picker.spec.tsx @@ -3,7 +3,7 @@ import { render, vi } from 'test-utils'; import userEvent from '@testing-library/user-event'; import noop from '../../utils/noop'; import { IPickerProps, Picker, PickerType } from './Picker'; -import { defaultOptions, SELECT_ALL_OPTION_KEY } from './constants'; +import { defaultOptions } from './constants'; // eslint-disable-next-line @typescript-eslint/no-empty-function window.HTMLElement.prototype.scrollIntoView = () => {}; diff --git a/packages/react-components/src/components/Picker/Picker.stories.css b/packages/react-components/src/components/Picker/Picker.stories.css new file mode 100644 index 000000000..dc947d5f2 --- /dev/null +++ b/packages/react-components/src/components/Picker/Picker.stories.css @@ -0,0 +1,31 @@ +.custom-picker-option { + display: flex; + flex-flow: row; + align-items: center; +} + +.image { + margin-right: 15px; + border-radius: 50%; + width: 45px; + height: auto; +} + +.image.selected { + width: 20px; +} + +.title { + font-size: 18px; + font-weight: 700; +} + +.title.selected { + font-size: 14px; + font-weight: 400; +} + +.description { + color: var(--content-subtle); + font-size: 14px; +} diff --git a/packages/react-components/src/components/Picker/Picker.stories.tsx b/packages/react-components/src/components/Picker/Picker.stories.tsx index d0057938d..0c7e2d254 100644 --- a/packages/react-components/src/components/Picker/Picker.stories.tsx +++ b/packages/react-components/src/components/Picker/Picker.stories.tsx @@ -5,6 +5,8 @@ import { IPickerProps, Picker as PickerComponent } from './Picker'; import { defaultExtendedOptions, defaultOptions } from './constants'; import { IPickerListItem } from './PickerList'; +import './Picker.stories.css'; + export default { title: 'Components/Picker', component: PickerComponent, @@ -91,3 +93,129 @@ PickerInMultiselectModeWithSelectedOptions.args = { options: defaultExtendedOptions, selectAllOptionText: 'Select all', }; + +const CustomPickerOption: React.FC = ({ children }) => ( +
{children}
+); + +export const PickerWithOptionsAsCustomElements = StoryTemplate.bind({}); +PickerWithOptionsAsCustomElements.args = { + options: [ + { + key: 'one', + name: 'Example custom element one', + customElement: { + listItemBody: ( + + +
+
Example custom element one
+
Example custom element
+
+
+ ), + selectedItemBody: ( + + +
Example custom element one
+
+ ), + }, + }, + { + key: 'two', + name: 'Example custom element two', + customElement: { + listItemBody: ( + + +
+
Example custom element two
+
Example custom element
+
+
+ ), + selectedItemBody: ( + + +
Example custom element two
+
+ ), + }, + }, + ], +}; + +export const PickerInMultiselectModeWithOptionsAsCustomElements = + StoryTemplate.bind({}); +PickerInMultiselectModeWithOptionsAsCustomElements.args = { + type: 'multi', + options: [ + { + key: 'one', + name: 'Example custom element one', + customElement: { + listItemBody: ( + + +
+
Example custom element one
+
Example custom element
+
+
+ ), + selectedItemBody: ( + + +
Example custom element one
+
+ ), + }, + }, + { + key: 'two', + name: 'Example custom element two', + customElement: { + listItemBody: ( + + +
+
Example custom element two
+
Example custom element
+
+
+ ), + selectedItemBody: ( + + +
Example custom element two
+
+ ), + }, + }, + ], +}; diff --git a/packages/react-components/src/components/Picker/PickerList.module.scss b/packages/react-components/src/components/Picker/PickerList.module.scss index adabafeea..e87aaed55 100644 --- a/packages/react-components/src/components/Picker/PickerList.module.scss +++ b/packages/react-components/src/components/Picker/PickerList.module.scss @@ -27,6 +27,7 @@ $base-class: 'picker-list'; } &__item { + box-sizing: border-box; display: flex; align-items: center; justify-content: space-between; @@ -34,6 +35,7 @@ $base-class: 'picker-list'; cursor: pointer; padding: 6px 11px; height: 36px; + overflow: hidden; &:hover { background-color: var(--surface-basic-hover); @@ -68,5 +70,10 @@ $base-class: 'picker-list'; font-size: 12px; font-weight: 600; } + + &__custom { + width: 100%; + height: auto; + } } } diff --git a/packages/react-components/src/components/Picker/PickerList.spec.tsx b/packages/react-components/src/components/Picker/PickerList.spec.tsx index 27570b904..b46575ae2 100644 --- a/packages/react-components/src/components/Picker/PickerList.spec.tsx +++ b/packages/react-components/src/components/Picker/PickerList.spec.tsx @@ -103,4 +103,32 @@ describe(' component', () => { expect(getByText('Select all')).toBeVisible(); }); + + it('should display custom components as options', () => { + const { getByText } = renderComponent({ + ...defaultProps, + isOpen: true, + items: [ + { + key: 'custom-one', + name: 'Custom one', + customElement: { + listItemBody:
List custom one
, + selectedItemBody:
Selected custom one
, + }, + }, + { + key: 'custom-two', + name: 'Custom two', + customElement: { + listItemBody:
List custom two
, + selectedItemBody:
Selected custom two
, + }, + }, + ], + }); + + expect(getByText('List custom one')).toBeVisible(); + expect(getByText('List custom two')).toBeVisible(); + }); }); diff --git a/packages/react-components/src/components/Picker/PickerList.tsx b/packages/react-components/src/components/Picker/PickerList.tsx index 89e8854d6..4bc166b43 100644 --- a/packages/react-components/src/components/Picker/PickerList.tsx +++ b/packages/react-components/src/components/Picker/PickerList.tsx @@ -12,6 +12,10 @@ const itemClassName = `${baseClass}__item`; export interface IPickerListItem { key: string; name: string; + customElement?: { + listItemBody: React.ReactElement; + selectedItemBody: React.ReactElement; + }; groupHeader?: boolean; disabled?: boolean; } @@ -182,6 +186,18 @@ export const PickerList: React.FC = ({ ); }; + const getOptionContent = (item: IPickerListItem) => { + if (item?.customElement) { + return ( +
+ {item.customElement.listItemBody} +
+ ); + } + + return item.name; + }; + if (!isOpen) { return null; } @@ -219,10 +235,12 @@ export const PickerList: React.FC = ({ aria-disabled={item.disabled} id={item.key} key={item.key} - className={styles[itemClassName]} + className={cx(styles[itemClassName], { + [styles[`${itemClassName}__custom`]]: item?.customElement, + })} onClick={() => !item.disabled && handleOnClick(item)} > - {item.name} + {getOptionContent(item)} {isItemSelected(item.key) && } ); diff --git a/packages/react-components/src/components/Picker/Trigger.tsx b/packages/react-components/src/components/Picker/Trigger.tsx index 5e3c2e650..7530d5078 100644 --- a/packages/react-components/src/components/Picker/Trigger.tsx +++ b/packages/react-components/src/components/Picker/Trigger.tsx @@ -87,14 +87,7 @@ export const Trigger: React.FC = ({ onClick={handleTriggerClick} tabIndex={0} > -
- {children} -
+
{children}
component', () => { name: 'Option three', }); }); + + it('should show custom component as selected item in single mode', () => { + const { queryByText } = renderComponent({ + ...defaultProps, + items: [ + { + key: 'custom-one', + name: 'Custom one', + customElement: { + listItemBody:
List custom one
, + selectedItemBody:
Selected custom one
, + }, + }, + ], + }); + + expect(queryByText('Selected custom one')).toBeVisible(); + }); + + it('should show custom components as selected items in multiselect mode', () => { + const { queryByText } = renderComponent({ + ...defaultProps, + type: 'multi', + items: [ + { + key: 'custom-one', + name: 'Custom one', + customElement: { + listItemBody:
List custom one
, + selectedItemBody:
Selected custom one
, + }, + }, + { + key: 'custom-two', + name: 'Custom two', + customElement: { + listItemBody:
List custom two
, + selectedItemBody:
Selected custom two
, + }, + }, + ], + }); + + expect(queryByText('Selected custom one')).toBeVisible(); + expect(queryByText('Selected custom two')).toBeVisible(); + }); }); diff --git a/packages/react-components/src/components/Picker/TriggerBody.tsx b/packages/react-components/src/components/Picker/TriggerBody.tsx index 87618d18f..fdf5ad1da 100644 --- a/packages/react-components/src/components/Picker/TriggerBody.tsx +++ b/packages/react-components/src/components/Picker/TriggerBody.tsx @@ -29,12 +29,20 @@ export const TriggerBody: React.FC = ({ }) => { const shouldDisplaySearch = isOpen && !isSearchDisabled; - const getSingleItem = (name: string) => { - if (isOpen && !isSearchDisabled) { + const getSingleItem = (item: IPickerListItem) => { + if (type === 'single' && isOpen && !isSearchDisabled) { return null; } - return name; + if (item?.customElement) { + return ( +
+ {item.customElement.selectedItemBody} +
+ ); + } + + return item.name; }; const handleOnChange = (e: React.ChangeEvent) => { @@ -57,7 +65,7 @@ export const TriggerBody: React.FC = ({ return (
{type === 'single' - ? getSingleItem(items[0].name) + ? getSingleItem(items[0]) : items.map((item) => { return ( = ({ dismissible onRemove={() => onItemRemove(item)} > - {item.name} + {getSingleItem(item)} ); })}