Skip to content
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

Merged
merged 18 commits into from
Nov 10, 2022

Conversation

marcinsawicki
Copy link
Contributor

@marcinsawicki marcinsawicki commented Jul 26, 2022

Resolves #338

Description

After releasing the Picker component as a new approach after Select, 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.

  • Added multiselect prop to enable this option
  • Added possibility to pass own class name
  • Changed the styles of components, enabling the 100% width, to make it easier to fit with own design

Storybook

@marcinsawicki marcinsawicki added the feature New feature or request label Jul 26, 2022
@marcinsawicki marcinsawicki linked an issue Jul 26, 2022 that may be closed by this pull request
@marcinsawicki marcinsawicki added the blocked Work is blocked label Jul 26, 2022
@marcinsawicki marcinsawicki removed the blocked Work is blocked label Jul 26, 2022
@marcinsawicki marcinsawicki changed the title #338 Changes related to enabling the multiselect option #338 Changes in Picker component related to enabling the multiselect option Jul 26, 2022
Comment on lines +5 to +7
svg {
pointer-events: none;
}

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?

Copy link
Contributor Author

@marcinsawicki marcinsawicki Jul 29, 2022

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.

Comment on lines 39 to 40
margin-bottom: 4px;
margin-right: 4px;

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?

Suggested change
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

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.

Comment on lines 118 to 128
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 },
],

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;
Copy link

@sgraczyk sgraczyk Jul 28, 2022

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.

Suggested change
multiselect?: boolean;
type?: 'single' | 'multi';

disabled?: boolean;
label?: string;
error?: string;
options: IPickerListItem[];
selectedOption?: IPickerListItem;
selectedOptions?: IPickerListItem[];

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'

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

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

Comment on lines 145 to 166
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>
);
};

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be a const

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;

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.

Copy link

@kstvsko kstvsko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. When you chose something and the picker is opened you should see the chosen values in the picker field, not the placeholder.

image

  1. When you chose something the icon that clears the picker should look differently.

image

3. On the default state there is something weird near the picker icon.

image

4. When you select something and then deselect it and stop focusing on the picker the icon to clear the picker stays visible and there is no placeholder text.

image

@@ -70,32 +77,72 @@ export const Picker: React.FC<IPickerProps> = ({
}
}, [isListOpen]);

const handleOnTriggerClick = () => {
if (disabled) {
const handleOnTrigger = (e: React.MouseEvent | KeyboardEvent) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const handleOnSelect = (item: IPickerListItem) => {
const handleSelect = (item: IPickerListItem) => {

};

const handleOnClearClick = () => {
const handleOnClear = () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const handleOnClear = () => {
const handleClear = () => {

onFilter={handleOnFilter}
>
{selectedItem ? selectedItem.name : placeholder}
{selected ? (
Copy link

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?

Copy link
Contributor Author

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,
Copy link

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,
Copy link

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') {
Copy link
Collaborator

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}

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';

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

Comment on lines 143 to 144
const getSelectedItemsKeys = (items: IPickerListItem[]) =>
items ? items.map((item) => item.key) : null;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider the following approach

Suggested change
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]);

@jpyzio123
Copy link
Contributor

  1. Picker with multiselect set to true has different height CSS than picker with it set to false. Non-multiselect picker has specific height values depending on size property, while multiselect picker has the height set to auto
    https://user-images.githubusercontent.com/46003125/198558578-f423ad95-7552-41d0-9d87-6600d0e236b5.mp4

  2. Option to Select all is still visible and clickable with multiselect set to false, I think it shouldn't be.
    https://user-images.githubusercontent.com/46003125/198559669-a206b8bc-733a-412e-8997-42a2bec0a90c.mp4

@jpyzio123
Copy link
Contributor

in type: multi, the option selectAllOption set to False is still visible and possible to add as regular option
image

@marcinsawicki marcinsawicki merged commit 75cca89 into v1 Nov 10, 2022
@marcinsawicki marcinsawicki deleted the DS-173 branch November 10, 2022 11:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

[Picker] Enable multi-select
6 participants