Skip to content

Commit

Permalink
[DST-363]: Adding Guideline-Page of how to use Multiselection (#3824)
Browse files Browse the repository at this point in the history
Co-authored-by: sarahgm <sarahgm@users.noreply.github.com>
Co-authored-by: Sebastian Sebald <sebastian.sebald@gmail.com>
Co-authored-by: sarahgm <sarah.gosmann@reservix.de>
Co-authored-by: sarahgm <38324334+sarahgm@users.noreply.github.com>
Co-authored-by: Sebastian Sebald <sebastian.sebald@reservix.de>
  • Loading branch information
6 people committed Mar 19, 2024
1 parent 8d2fea1 commit 879a0e1
Show file tree
Hide file tree
Showing 12 changed files with 343 additions and 1 deletion.
6 changes: 6 additions & 0 deletions .changeset/eleven-panthers-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@marigold/components": patch
"@marigold/docs": patch
---

Adding Multiselect guideline
68 changes: 68 additions & 0 deletions docs/content/concepts/multiple-selection.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
title: Multiple Selection
caption: Learn about how & when to use multiple selection
badge: new
---

The purpose of this guide is to provide clear instructions and guidelines for selecting the appropriate method of multiple selection within your design system. The guide will outline the different possibilities for multiple selection, explain their differences, and provide examples and recommendations for when to use each type.

### Different possibilities for multiple selection:

- [Checkbox Group](#checkbox-group)
- [Multiselect](#multiselect)
- [Tag Group](#taggroup)

So let us explain each different possibility for multiple selection:

### Checkbox Group

Checkbox Group is a type of multiple selection interfaces that presents options as checkboxes. User can Select Multiple options by checking the corresponding checkboxes.

It provides a familiar and straightforward interface for selecting multiple options.

#### When to use ?

- Use CheckboxGroup when the number of options is moderate to large, typically more than 10-15 options.
- Use CheckboxGroup when the selected options do not need to be visible within the selection interface.
- CheckboxGroup is useful when the primary focus is on selecting options rather than displaying the selected choices.

#### Examples

<ComponentDemo file="./multiselect-checkbox-group.demo.tsx" />

### Multiselect

Multiselect is a type of multiple selection interface that displays options in a list format with the ability to select multiple items.

It allows users to choose multiple options by clicking on the items in the list and provides a comprehensive view of available options and the selected items.

#### When to use ?

- Multiselect is a type of multiple selection interface that displays options in a list format with the ability to select multiple items.
- It allows users to choose multiple options by clicking on the items in the list.
- Multiselect is suitable when the number of options is large, and the selected options need to be visible within the selection interface.
- It provides a comprehensive view of available options and the selected items.

#### Examples

<ComponentDemo file="./multiselect.demo.tsx" />

### TagGroup

TagGroup is a type of multiple selection interface that allows users to select multiple options and displays the selected options as tags.

It is useful when the number of options is relatively small, and the selected options need to be visible at all times.

#### When to use ?

- Use TagGroup when the number of options is limited, typically up to 10-15 options.
- Use TagGroup when the selected options need to be visible to the user at all times.
- TagGroup is well-suited for situations where space is a concern and displaying selected options as tags provides a clear representation.

#### Examples

<ComponentDemo file="./tag-group-multiselect.demo.tsx" />

## Related

- [MultiSelect Recipe](/recipes/multiselect-recipe)
27 changes: 27 additions & 0 deletions docs/content/concepts/multiselect-checkbox-group.demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useState } from 'react';

import { Checkbox, CheckboxGroup } from '@marigold/components';

export default () => {
const [selected, setSelected] = useState<string[]>([]);
return (
<>
<CheckboxGroup
label="Choose your toppings:"
onChange={setSelected}
description="Just click on the options"
>
<Checkbox value="ham">🐖 Ham</Checkbox>
<Checkbox value="beef" disabled>
🐄 Beef (out of stock)
</Checkbox>
<Checkbox value="tuna">🐟 Tuna</Checkbox>
<Checkbox value="tomatos">🍅 Tomatos</Checkbox>
<Checkbox value="onions">🧅 Onions</Checkbox>
<Checkbox value="pineapple">🍍 Pineapple</Checkbox>
</CheckboxGroup>
<hr />
<pre>Selected values: {selected.join(', ')}</pre>
</>
);
};
14 changes: 14 additions & 0 deletions docs/content/concepts/multiselect.demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Multiselect } from '@marigold/components';

export default () => {
return (
<Multiselect label="Select favorite fruits">
<Multiselect.Item id="apple">🍎 Apple</Multiselect.Item>
<Multiselect.Item id="banana">🍌 Banana</Multiselect.Item>
<Multiselect.Item id="orange">🍊 Orange</Multiselect.Item>
<Multiselect.Item id="strawberry">🍓 Strawberry</Multiselect.Item>
<Multiselect.Item id="mango">🥭 Mango</Multiselect.Item>
<Multiselect.Item id="watermelon">🍉 Watermelon</Multiselect.Item>
</Multiselect>
);
};
28 changes: 28 additions & 0 deletions docs/content/concepts/tag-group-multiselect.demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useState } from 'react';

import { Tag } from '@marigold/components';

export default () => {
const [selected, setSelected] = useState(new Set(['parking']));

return (
<>
<Tag.Group
label="Amenities"
selectionMode="multiple"
selectedKeys={selected}
onSelectionChange={setSelected as any}
>
<Tag id="laundry">Laundry</Tag>
<Tag id="fitness">Fitness center</Tag>
<Tag id="parking">Parking</Tag>
<Tag id="pool">Swimming pool</Tag>
<Tag id="breakfast">Breakfast</Tag>
</Tag.Group>
<p>
Current selection (controlled):{' '}
{selected === 'all' ? 'all' : [...selected].join(', ')}
</p>
</>
);
};
14 changes: 14 additions & 0 deletions docs/content/recipes/multiselect-basic.demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Multiselect } from '@marigold/components';

export default () => {
return (
<Multiselect label="Select a country">
<Multiselect.Item id="Germany">🇩🇪 Germany</Multiselect.Item>
<Multiselect.Item id="France">🇫🇷 France</Multiselect.Item>
<Multiselect.Item id="India">🇮🇳 India</Multiselect.Item>
<Multiselect.Item id="Brazil">🇧🇷 Brazil</Multiselect.Item>
<Multiselect.Item id="Canada">🇨🇦 Canada</Multiselect.Item>
<Multiselect.Item id="Australia">🇦🇺 Australia</Multiselect.Item>
</Multiselect>
);
};
23 changes: 23 additions & 0 deletions docs/content/recipes/multiselect-recipe.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
title: MultiSelect
caption: Here you can find some recipes for using the MultiSelect component.
badge: new
---

The `<MultiSelect>` component combines a text input with a listbox, allowing users to filter a list of options to items matching a query, selecting multiple items or adding a new value.

## Usage

### Import

To import the component you just have to use this code below.

```tsx
import { MultiSelect } from '@marigold/components';
```

## Example

You can use the `MultiSelect` to enable users to select multiple values and search for them.

<ComponentDemo file="./multiselect-basic.demo.tsx" />
2 changes: 1 addition & 1 deletion packages/components/src/ComboBox/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export interface ComboBoxProps
defaultValue?: RAC.ComboBoxProps<any>['defaultInputValue'];
value?: RAC.ComboBoxProps<any>['inputValue'];
onChange?: RAC.ComboBoxProps<any>['onInputChange'];
children: ReactNode | ((item: any) => ReactNode);
children?: ReactNode | ((item: any) => ReactNode);
placeholder?: string;
}

Expand Down
38 changes: 38 additions & 0 deletions packages/components/src/Multiselect/Multiselect.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { Meta, StoryObj } from '@storybook/react';

import { Multiselect } from './Multiselect';

const meta = {
title: 'Components/Multiselect',
argTypes: {},
args: {
description: 'This is a help text description',
errorMessage: 'Something went wrong',
},
} satisfies Meta;

export default meta;

export const Basic: StoryObj<typeof Multiselect> = {
render: () => {
return (
<>
<Multiselect
label="Animals"
// disabledKeys={['snake']}
defaultSelectedKeys={['cat', 'dog']}
>
<Multiselect.Item id="red-panda">Red Panda</Multiselect.Item>
<Multiselect.Item id="cat">Cat</Multiselect.Item>
<Multiselect.Item id="dog">Dog</Multiselect.Item>
<Multiselect.Item id="aardvark">Aardvark</Multiselect.Item>
<Multiselect.Item id="kangaroo">Kangaroo</Multiselect.Item>
<Multiselect.Item id="snake">Snake</Multiselect.Item>
<Multiselect.Item id="vegan">Vegan</Multiselect.Item>
<Multiselect.Item id="margrita">Margrita</Multiselect.Item>
</Multiselect>
</>
);
},
};
119 changes: 119 additions & 0 deletions packages/components/src/Multiselect/Multiselect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* - list of removable tags
* - combobox to search for an item
*
* - selecting an item from the combobox -> adds it to the tags, clears combobox input
* - combobox only shows unselected items
*/
import { Children, ReactNode, useState } from 'react';
import { Key } from 'react-aria-components';
import type RAC from 'react-aria-components';

import { useListData } from '@react-stately/data';

import { ComboBox } from '../ComboBox';
import { Tag } from '../TagGroup';

// Item
// ---------------
export interface MultiSelectItemProps {
id: Key;
children: ReactNode;
}

const Item = (_: MultiSelectItemProps) => null;

// Props
// ---------------
export interface MultiSelectProps extends RAC.ComboBoxProps<object> {
label?: string;
children?: ReactNode;
defaultSelectedKeys?: 'all' | Iterable<Key>;
}

// Component
// ---------------
export const Multiselect = ({
label,
children,
...props
}: MultiSelectProps) => {
// Fake react-aria collection items
const items = Children.map(children, ({ props }: any) => props);

const list = useListData<MultiSelectItemProps>({
initialItems: items,
initialSelectedKeys: props.defaultSelectedKeys,
getKey: item => item.id,
});

const selected = list.items.filter(item =>
list.selectedKeys === 'all' ? true : list.selectedKeys.has(item.id)
);
const unselected = list.items.filter(item => !selected.includes(item));

// Remove tag
const setUnselected = (keys: Set<Key>) => {
const next: Set<Key> =
list.selectedKeys === 'all' ? new Set(items) : new Set(list.selectedKeys);

if (list.selectedKeys !== 'all') {
keys.forEach(key => {
next.delete(key);
});
}

list.setSelectedKeys(next);
};

// Combobox Stuff
const [value, setValue] = useState('');
const selectItem = (key: Key) => {
// add to selected items
if (list.selectedKeys !== 'all') {
const next = list.selectedKeys.add(key);
list.setSelectedKeys(next);
}

// Clear combobox
const input = document.activeElement as HTMLInputElement;
setTimeout(() => {
setValue('');
}, 0);
input.focus();
};

return (
<div className="flex flex-wrap gap-1">
<Tag.Group
items={selected}
allowsRemoving
onRemove={setUnselected}
renderEmptyState={() => null}
>
{(item: MultiSelectItemProps) => (
<Tag key={item.id} id={item.id}>
{item.children}
</Tag>
)}
</Tag.Group>
<ComboBox
value={value}
onChange={setValue}
onSelectionChange={selectItem}
menuTrigger="focus"
disabled={unselected.length === 0}
placeholder={unselected.length === 0 ? 'All items selected' : ''}
{...props}
>
{unselected.map((item: MultiSelectItemProps) => (
<ComboBox.Item key={item.id} id={item.id}>
{item.children}
</ComboBox.Item>
))}
</ComboBox>
</div>
);
};

Multiselect.Item = Item;
4 changes: 4 additions & 0 deletions packages/components/src/Multiselect/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* @private
*/
export * from './Multiselect';
1 change: 1 addition & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export * from './Link';
export * from './List';
export * from './Menu';
export * from './Message';
export * from './Multiselect';
export * from './NumberField';
export * from './Overlay';
export * from './Provider';
Expand Down

0 comments on commit 879a0e1

Please sign in to comment.