-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#338 Changes in Picker component related to enabling the multiselect option #357
Conversation
svg { | ||
pointer-events: none; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you elaborate on this one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To prevent the list open when the user is clicking on the selected item tag icon (multipicker mode), we need to check the event target. This prop will prevent the event handler to see svg as the target object, where we cannot pass the custom data-
attribute.
margin-bottom: 4px; | ||
margin-right: 4px; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about this? Or we want to avoid declaring 0 margin for remaining directions?
margin-bottom: 4px; | |
margin-right: 4px; | |
margin: 0 4px 4px 0; |
@@ -7,24 +7,31 @@ import { IPickerProps, Picker } from './Picker'; | |||
// eslint-disable-next-line @typescript-eslint/no-empty-function |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expect some test that would make sure that a single-select
is able to pick only one value (eg. by clicking two different options and making sure only the last one is triggered in onSelect
. Another idea is making sure that unselect
is working.
In general I believe we could come up with more testing cases.
options: [ | ||
{ key: 'groupA', name: 'Group A title header', groupHeader: true }, | ||
{ key: 'one', name: 'Option one' }, | ||
{ key: 'two', name: 'Option two' }, | ||
{ key: 'three', name: 'Option three' }, | ||
{ key: 'groupB', name: 'Group B title header', groupHeader: true }, | ||
{ key: 'four', name: 'Option four' }, | ||
{ key: 'five', name: 'Option five' }, | ||
{ key: 'six', name: 'Option six', disabled: true }, | ||
{ key: 'seven', name: 'Option seven', disabled: true }, | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you considered moving to a common variable, or do we think it won't work well in Storybook docs?
size?: TriggerSize; | ||
placeholder?: string; | ||
isRequired?: boolean; | ||
noSearchResultText?: string; | ||
multiselect?: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd avoid the boolean
type in favor of type
like mode/type
with values of single | multi
.
multiselect?: boolean; | |
type?: 'single' | 'multi'; |
disabled?: boolean; | ||
label?: string; | ||
error?: string; | ||
options: IPickerListItem[]; | ||
selectedOption?: IPickerListItem; | ||
selectedOptions?: IPickerListItem[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe selected
is enough for the name of the prop
?
onSelect(item); | ||
|
||
const itemsToSelect = items.filter( | ||
(item) => !item.disabled && !item.groupHeader && item.key !== 'select-all' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider placing select-all
in a variable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionally, I'd think about a helper function isItemSelectable
that would move this condition outside filter
const getSelectedItems = (items: IPickerListItem[]) => { | ||
if (!multiselect) { | ||
return <div>{items[0].name}</div>; | ||
} | ||
|
||
return ( | ||
<div> | ||
{items.map((item) => { | ||
return ( | ||
<Tag | ||
key={item.name} | ||
className={styles[`${baseClass}__tag`]} | ||
dismissible | ||
onRemove={() => handleItemRemove(item)} | ||
> | ||
{item.name} | ||
</Tag> | ||
); | ||
})} | ||
</div> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's more of a component than an internal helper function. Consider extracting.
)} | ||
onClick={handleOnSelectAllClick} | ||
> | ||
Select all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be a const
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionally, I believe developers should have control over this text. For example if used in non-English environment
size?: TriggerSize; | ||
onClick: () => void; | ||
onClick: (e: React.MouseEvent | KeyboardEvent) => void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if it's a proper approach to handle both mouse and keyboard events using the click
name. Either consider onTrigger
or handle two separate events, as it's misleading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- When you chose something and the picker is opened you should see the chosen values in the picker field, not the placeholder.
- When you chose something the icon that clears the picker should look differently.
@@ -70,32 +77,72 @@ export const Picker: React.FC<IPickerProps> = ({ | |||
} | |||
}, [isListOpen]); | |||
|
|||
const handleOnTriggerClick = () => { | |||
if (disabled) { | |||
const handleOnTrigger = (e: React.MouseEvent | KeyboardEvent) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const handleOnTrigger = (e: React.MouseEvent | KeyboardEvent) => { | |
const handleTrigger = (e: React.MouseEvent | KeyboardEvent) => { |
return; | ||
} | ||
|
||
return setIsListOpen((prev) => !prev); | ||
setIsListOpen((prev) => !prev); | ||
}; | ||
|
||
const handleOnClose = () => { | ||
setIsListOpen(false); | ||
}; | ||
|
||
const handleOnSelect = (item: IPickerListItem) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const handleOnSelect = (item: IPickerListItem) => { | |
const handleSelect = (item: IPickerListItem) => { |
}; | ||
|
||
const handleOnClearClick = () => { | ||
const handleOnClear = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const handleOnClear = () => { | |
const handleClear = () => { |
onFilter={handleOnFilter} | ||
> | ||
{selectedItem ? selectedItem.name : placeholder} | ||
{selected ? ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will trigger also for empty array, is this correct here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. After we removed the initial state of selected items, we could expect that an empty array can be passed. I will add an additional check here.
@@ -84,7 +84,7 @@ describe('<Trigger> component', () => { | |||
const { getByTestId } = renderComponent({ | |||
...defaultProps, | |||
isItemSelected: true, | |||
onClearClick, | |||
onClear: onClearClick, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this expected?
@@ -72,7 +72,7 @@ describe('<Trigger> component', () => { | |||
const onClick = vi.fn(); | |||
const { getByText } = renderComponent({ | |||
...defaultProps, | |||
onClick, | |||
onTrigger: onClick, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this expected?
type, | ||
onItemRemove, | ||
}) => { | ||
if (type === 'single') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't better to have 2 separate components, one for single picker
and second one for multiple picker
?
type={type} | ||
onItemRemove={handleItemRemove} | ||
onFilter={handleOnFilter} | ||
/> | ||
</Trigger> | ||
<PickerList | ||
selectedItemsKeys={selected ? getSelectedItemsKeys(selected) : null} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can move this whole condition inside a memoized array selectedItemKeys
. You can use useMemo
here.
}; | ||
|
||
const isItemSelectable = (item: IPickerListItem) => | ||
!item.disabled && !item.groupHeader && item.key !== 'select-all'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider having 'select-all'
in a shared constant
const getSelectedItemsKeys = (items: IPickerListItem[]) => | ||
items ? items.map((item) => item.key) : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider the following approach
const getSelectedItemsKeys = (items: IPickerListItem[]) => | |
items ? items.map((item) => item.key) : null; | |
const selectedItemsKeys = useMemo(() => { | |
if (!selected || !items) { | |
return null; | |
} | |
return items.map((item) => item.key); | |
}, [selected, items]); |
|
Resolves #338
Description
After releasing the
Picker
component as a new approach afterSelect
, we agreed that this component should be configurable to enable the multiselect feature.That requires some changes in the component logic, to unify the "singleselect" and "multiselect" mode.
multiselect
prop to enable this optionStorybook