From 879a0e12368318f4535792ed09917481fbd46f3b Mon Sep 17 00:00:00 2001 From: Osama Abdul Latif <62595605+OsamaAbdellateef@users.noreply.github.com> Date: Tue, 19 Mar 2024 12:33:11 +0200 Subject: [PATCH] [DST-363]: Adding Guideline-Page of how to use `Multiselection` (#3824) Co-authored-by: sarahgm Co-authored-by: Sebastian Sebald Co-authored-by: sarahgm Co-authored-by: sarahgm <38324334+sarahgm@users.noreply.github.com> Co-authored-by: Sebastian Sebald --- .changeset/eleven-panthers-remain.md | 6 + docs/content/concepts/multiple-selection.mdx | 68 ++++++++++ .../multiselect-checkbox-group.demo.tsx | 27 ++++ docs/content/concepts/multiselect.demo.tsx | 14 +++ .../concepts/tag-group-multiselect.demo.tsx | 28 +++++ .../recipes/multiselect-basic.demo.tsx | 14 +++ docs/content/recipes/multiselect-recipe.mdx | 23 ++++ packages/components/src/ComboBox/ComboBox.tsx | 2 +- .../src/Multiselect/Multiselect.stories.tsx | 38 ++++++ .../src/Multiselect/Multiselect.tsx | 119 ++++++++++++++++++ packages/components/src/Multiselect/index.ts | 4 + packages/components/src/index.ts | 1 + 12 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 .changeset/eleven-panthers-remain.md create mode 100644 docs/content/concepts/multiple-selection.mdx create mode 100644 docs/content/concepts/multiselect-checkbox-group.demo.tsx create mode 100644 docs/content/concepts/multiselect.demo.tsx create mode 100644 docs/content/concepts/tag-group-multiselect.demo.tsx create mode 100644 docs/content/recipes/multiselect-basic.demo.tsx create mode 100644 docs/content/recipes/multiselect-recipe.mdx create mode 100644 packages/components/src/Multiselect/Multiselect.stories.tsx create mode 100644 packages/components/src/Multiselect/Multiselect.tsx create mode 100644 packages/components/src/Multiselect/index.ts diff --git a/.changeset/eleven-panthers-remain.md b/.changeset/eleven-panthers-remain.md new file mode 100644 index 0000000000..51f79077d3 --- /dev/null +++ b/.changeset/eleven-panthers-remain.md @@ -0,0 +1,6 @@ +--- +"@marigold/components": patch +"@marigold/docs": patch +--- + +Adding Multiselect guideline diff --git a/docs/content/concepts/multiple-selection.mdx b/docs/content/concepts/multiple-selection.mdx new file mode 100644 index 0000000000..48cbbb0b9e --- /dev/null +++ b/docs/content/concepts/multiple-selection.mdx @@ -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 + + + +### 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 + + + +### 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 + + + +## Related + +- [MultiSelect Recipe](/recipes/multiselect-recipe) diff --git a/docs/content/concepts/multiselect-checkbox-group.demo.tsx b/docs/content/concepts/multiselect-checkbox-group.demo.tsx new file mode 100644 index 0000000000..80e34b1b1a --- /dev/null +++ b/docs/content/concepts/multiselect-checkbox-group.demo.tsx @@ -0,0 +1,27 @@ +import { useState } from 'react'; + +import { Checkbox, CheckboxGroup } from '@marigold/components'; + +export default () => { + const [selected, setSelected] = useState([]); + return ( + <> + + 🐖 Ham + + 🐄 Beef (out of stock) + + 🐟 Tuna + 🍅 Tomatos + 🧅 Onions + 🍍 Pineapple + +
+
Selected values: {selected.join(', ')}
+ + ); +}; diff --git a/docs/content/concepts/multiselect.demo.tsx b/docs/content/concepts/multiselect.demo.tsx new file mode 100644 index 0000000000..b059b7fbb1 --- /dev/null +++ b/docs/content/concepts/multiselect.demo.tsx @@ -0,0 +1,14 @@ +import { Multiselect } from '@marigold/components'; + +export default () => { + return ( + + 🍎 Apple + 🍌 Banana + 🍊 Orange + 🍓 Strawberry + 🥭 Mango + 🍉 Watermelon + + ); +}; diff --git a/docs/content/concepts/tag-group-multiselect.demo.tsx b/docs/content/concepts/tag-group-multiselect.demo.tsx new file mode 100644 index 0000000000..96dfe9ab9f --- /dev/null +++ b/docs/content/concepts/tag-group-multiselect.demo.tsx @@ -0,0 +1,28 @@ +import { useState } from 'react'; + +import { Tag } from '@marigold/components'; + +export default () => { + const [selected, setSelected] = useState(new Set(['parking'])); + + return ( + <> + + Laundry + Fitness center + Parking + Swimming pool + Breakfast + +

+ Current selection (controlled):{' '} + {selected === 'all' ? 'all' : [...selected].join(', ')} +

+ + ); +}; diff --git a/docs/content/recipes/multiselect-basic.demo.tsx b/docs/content/recipes/multiselect-basic.demo.tsx new file mode 100644 index 0000000000..4ed79bb57f --- /dev/null +++ b/docs/content/recipes/multiselect-basic.demo.tsx @@ -0,0 +1,14 @@ +import { Multiselect } from '@marigold/components'; + +export default () => { + return ( + + 🇩🇪 Germany + 🇫🇷 France + 🇮🇳 India + 🇧🇷 Brazil + 🇨🇦 Canada + 🇦🇺 Australia + + ); +}; diff --git a/docs/content/recipes/multiselect-recipe.mdx b/docs/content/recipes/multiselect-recipe.mdx new file mode 100644 index 0000000000..39f39f725c --- /dev/null +++ b/docs/content/recipes/multiselect-recipe.mdx @@ -0,0 +1,23 @@ +--- +title: MultiSelect +caption: Here you can find some recipes for using the MultiSelect component. +badge: new +--- + +The `` 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. + + diff --git a/packages/components/src/ComboBox/ComboBox.tsx b/packages/components/src/ComboBox/ComboBox.tsx index b7699b4e28..8d062c03b8 100644 --- a/packages/components/src/ComboBox/ComboBox.tsx +++ b/packages/components/src/ComboBox/ComboBox.tsx @@ -45,7 +45,7 @@ export interface ComboBoxProps defaultValue?: RAC.ComboBoxProps['defaultInputValue']; value?: RAC.ComboBoxProps['inputValue']; onChange?: RAC.ComboBoxProps['onInputChange']; - children: ReactNode | ((item: any) => ReactNode); + children?: ReactNode | ((item: any) => ReactNode); placeholder?: string; } diff --git a/packages/components/src/Multiselect/Multiselect.stories.tsx b/packages/components/src/Multiselect/Multiselect.stories.tsx new file mode 100644 index 0000000000..6a6ea5fbe2 --- /dev/null +++ b/packages/components/src/Multiselect/Multiselect.stories.tsx @@ -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 = { + render: () => { + return ( + <> + + Red Panda + Cat + Dog + Aardvark + Kangaroo + Snake + Vegan + Margrita + + + ); + }, +}; diff --git a/packages/components/src/Multiselect/Multiselect.tsx b/packages/components/src/Multiselect/Multiselect.tsx new file mode 100644 index 0000000000..33e3e05fb3 --- /dev/null +++ b/packages/components/src/Multiselect/Multiselect.tsx @@ -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 { + label?: string; + children?: ReactNode; + defaultSelectedKeys?: 'all' | Iterable; +} + +// Component +// --------------- +export const Multiselect = ({ + label, + children, + ...props +}: MultiSelectProps) => { + // Fake react-aria collection items + const items = Children.map(children, ({ props }: any) => props); + + const list = useListData({ + 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) => { + const next: Set = + 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 ( +
+ null} + > + {(item: MultiSelectItemProps) => ( + + {item.children} + + )} + + + {unselected.map((item: MultiSelectItemProps) => ( + + {item.children} + + ))} + +
+ ); +}; + +Multiselect.Item = Item; diff --git a/packages/components/src/Multiselect/index.ts b/packages/components/src/Multiselect/index.ts new file mode 100644 index 0000000000..0036d3fd38 --- /dev/null +++ b/packages/components/src/Multiselect/index.ts @@ -0,0 +1,4 @@ +/** + * @private + */ +export * from './Multiselect'; diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 566540abba..2538bf5982 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -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';