From 3d657a6383ac15ae8cc96d30d44a8b43356f86ec Mon Sep 17 00:00:00 2001 From: NikoHelle Date: Fri, 18 Oct 2024 11:02:17 +0300 Subject: [PATCH 1/5] (hds-2069) (hds-2060) Ran site scaffold --- site/src/data/components.json | 15 +++++++- .../docs/components/select/accessibility.mdx | 12 +++++++ site/src/docs/components/select/code.mdx | 31 ++++++++++++++++ .../docs/components/select/customisation.mdx | 14 ++++++++ site/src/docs/components/select/index.mdx | 21 +++++++++++ site/src/docs/components/select/tabs.mdx | 34 ++++++++++++++++++ .../images/components/overview/select@2x.png | Bin 0 -> 4526 bytes 7 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 site/src/docs/components/select/accessibility.mdx create mode 100644 site/src/docs/components/select/code.mdx create mode 100644 site/src/docs/components/select/customisation.mdx create mode 100644 site/src/docs/components/select/index.mdx create mode 100644 site/src/docs/components/select/tabs.mdx create mode 100644 site/static/images/components/overview/select@2x.png diff --git a/site/src/data/components.json b/site/src/data/components.json index 5c670342d4..c087cbba17 100644 --- a/site/src/data/components.json +++ b/site/src/data/components.json @@ -206,7 +206,7 @@ "height": 180, "width": 280 } - }, + }, { "name": "Icon", "text": "Icons are used for providing visual cues and highlighting actions.", @@ -389,6 +389,19 @@ "width": 280 } }, + { + "name": "Select", + "text": "Select offers a user a list of options, of which one or multiple can be selected.", + "linkboxAriaLabel": "Select component", + "linkAriaLabel": "Go to the Select component page", + "href": "/components/select", + "imgProps": { + "src": "/images/components/overview/select@2x.png", + "alt": "An illustration of the Select component.", + "height": 180, + "width": 280 + } + }, { "name": "SelectionGroup", "text": "Selection group allows grouping form selection elements to each other.", diff --git a/site/src/docs/components/select/accessibility.mdx b/site/src/docs/components/select/accessibility.mdx new file mode 100644 index 0000000000..ac01b8a95d --- /dev/null +++ b/site/src/docs/components/select/accessibility.mdx @@ -0,0 +1,12 @@ +--- +slug: '/components/select/accessibility' +title: 'Select - Accessibility' +--- + +import TabsLayout from './tabs.mdx'; + +export default ({ children, pageContext }) => {children}; + +## Accessibility + +### Pay attention to diff --git a/site/src/docs/components/select/code.mdx b/site/src/docs/components/select/code.mdx new file mode 100644 index 0000000000..57fd06e878 --- /dev/null +++ b/site/src/docs/components/select/code.mdx @@ -0,0 +1,31 @@ +--- +slug: '/components/select/code' +title: 'Select - Code' +--- + +import { Select } from 'hds-react'; +import TabsLayout from './tabs.mdx'; + +export default ({ children, pageContext }) => {children}; + +## Code + +### Code examples + + + +```jsx + import { Select } from 'hds-react'; + + () => +``` + +```html +
+``` + +
+ +### Packages + +### Properties diff --git a/site/src/docs/components/select/customisation.mdx b/site/src/docs/components/select/customisation.mdx new file mode 100644 index 0000000000..19742f4bf6 --- /dev/null +++ b/site/src/docs/components/select/customisation.mdx @@ -0,0 +1,14 @@ +--- +slug: '/components/select/customisation' +title: 'Select - Customisation' +--- + +import TabsLayout from './tabs.mdx'; + +export default ({ children, pageContext }) => {children}; + +## Customisation + +### Customisation properties + +### Customisation example diff --git a/site/src/docs/components/select/index.mdx b/site/src/docs/components/select/index.mdx new file mode 100644 index 0000000000..d8a1c4e17e --- /dev/null +++ b/site/src/docs/components/select/index.mdx @@ -0,0 +1,21 @@ +--- +slug: '/components/select' +title: 'Select' +navTitle: 'Select' +--- + +import TabsLayout from './tabs.mdx'; + +export default ({ children, pageContext }) => {children}; + +## Usage + +### Example + + +
+
+ +### Principles + +### Variations diff --git a/site/src/docs/components/select/tabs.mdx b/site/src/docs/components/select/tabs.mdx new file mode 100644 index 0000000000..64b1dbc3f5 --- /dev/null +++ b/site/src/docs/components/select/tabs.mdx @@ -0,0 +1,34 @@ +--- +slug: '/components/select/tabs' +title: 'Select' +--- + +import StatusLabelTooltip from '../../../components/StatusLabelTooltip'; +import LeadParagraph from '../../../components/LeadParagraph'; +import Layout from '../../../components/layout'; +import PageTabs from '../../../components/PageTabs'; +import StatusLabel from '../../../components/StatusLabel'; +import Notification from '../../../components/Notification'; + +
+ Draft + + Accessible + + +
+ + + A select offers a user a list of options, of which one or multiple can be selected. Select components are often used + as part of forms and filters. + + + + + Usage + Code + Accessibility + Customisation + + {props.children} + diff --git a/site/static/images/components/overview/select@2x.png b/site/static/images/components/overview/select@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ca99a1f7bb1a4e504a55ce0f5e5ae5157e1445e1 GIT binary patch literal 4526 zcmeHL`%@EF6y9AHW)X-RMPYczB2ch8C@LaC$p#}y6v9J`t%}J&TfmVw0$K@4RDuE$ zixw0V(Tr5BinfA{JYsoCEl?tLM0xr`-hv?EmGrJN{R`UZ!{LXWJ@?yl?m6e)bH96L zkNWZ0M)S<(K@eob<*))E2w4R|a2SRFr1nxE1H9n40JbM|z0sl<3|L=&#{J622l^Pu z7z9hhA(T=9j~RF%Nas8Z>3}y(Vds%Qzap#7>-;IhVU(g}n;!u|bW<*ixh)AEY`+<~ zjk~z6JKP-iMAK0Es3GbOH`R;=$`7L~_o~f{Ih=TwrrZeed7Wgzz7|Y7(3}nDQ_@yRF6_8JW1Ko+Af^(27;sFnG0@i z<>qSce|aE=e)THu9A4?@=%pC{A+%|zqufoE*>(gHyXwx+!W)0O>kQ8KGe97bwlo%? z99LcRKZ_jMoYVrYDB8d%E|6lDM-tNi#D!-V~~?k7pj3 zem7_sw~vjWdVp--OhPfMd_r#0i2Mbnn5`6D?i z`4vec&u;X)QDiGzoHQ}ip63!J)Qoqh)C@03wYq7rfFK3th~&piS64frlWKy?yV(O0 z;Qw)|h=>T`@wi~~y20kD>30dBQ3CFXr@7f%DZ?a~X(p_87=4XCo}ube-f^3XRs+__6OK4tKPk)^1tb;vJN#EC6BpAIQncDPW{{^leFQZbq`K z7!@g6O*4PUKp!{VrPMMk4=S&-K~C3za^OcV^QoM6kup82;(j{FdO29GQ(RYj7%5t$ zZkn)q@J^jQ98~gL*7k};ooVC;3XKff=XYBmHXM3pVYX$>x5$3)5}V9QFrYqFtwpG`pi zaPY)O8fC=64(WU=M@Pq!n7|@iQm|U=Tj1f7@LbDJR1vci(^;Xf7_^TC`O0y9sykyU`Fl9Ik~KgUDH)O%bV~Rq+C6cvCZdyHad1*S5^}hF zBWs>}^3+&&mGlHV&H*`O;B6vrk4)UR&sn`KC=2kIx$zrzg1N;Rn}r|+7GR*5D?`); zV>Ay_=}BMRr%tOsDf7y|;zlKT2yFMgLf6%Ry1a~WIlZWl2Q(g^2jmtVcHA2n(gC?u zUsfyx8tWVg&fhzA!t|`L`s>+IUQTDqOl<4VV+gohq}s4YV}=kmqYHA^Ne%^{bi;Q7 zMir6Ey0Qi~FG&X>62I9IoFLUZYpQ4iNZA}Zw+?glFn2CKWG3k1XTWW2z1wdWfnOyM McN33w-BTd>14W3|DF6Tf literal 0 HcmV?d00001 From 4b6720e60ccadadf069da04256a6025ed9829154 Mon Sep 17 00:00:00 2001 From: NikoHelle Date: Fri, 18 Oct 2024 14:08:13 +0300 Subject: [PATCH 2/5] (hds-2069) (hds-2060) Docs --- .../docs/components/select/accessibility.mdx | 10 + site/src/docs/components/select/code.mdx | 516 +++++++++++++++++- .../docs/components/select/customisation.mdx | 115 ++++ site/src/docs/components/select/index.mdx | 241 +++++++- site/src/docs/components/select/tabs.mdx | 6 +- 5 files changed, 877 insertions(+), 11 deletions(-) diff --git a/site/src/docs/components/select/accessibility.mdx b/site/src/docs/components/select/accessibility.mdx index ac01b8a95d..4ae745f3b0 100644 --- a/site/src/docs/components/select/accessibility.mdx +++ b/site/src/docs/components/select/accessibility.mdx @@ -10,3 +10,13 @@ export default ({ children, pageContext }) => {children}; ## Code -### Code examples - ```jsx - import { Select } from 'hds-react'; +import { Select } from 'hds-react'; - () => +() => { + return ( + <> + ``` - +Sometimes the value shown to the user should be different than the stored value. Then the options should be objects: + +```jsx + +// as these props + +``` + +If there is only one group and the label is empty, the label is not shown. Multiple groups should all have labels. If the Select has `multiSelect` set to `true`, the group label can also be selected. Selecting it selects all options in that group. Selecting all options individually also selects the group's label. + +Internally, all options are set into a group even if the `groups` property is not passed in props. The first option of all groups is always an option with `option.isGroupLabel` set to `true`. Even if the label was not given. + +This ensures the data is always in the same format internally and in all callbacks, despite what format options were passed to the component. + +The data and selected options are easily handled with utility functions. + +### Options as HTML + +Options can also be defined the same way the native ` + + + + + + + + + +``` + +### Changing options at runtime + +Options can be changed by passing a new set of `groups` or `options` as props to the component. This will naturally create a new instance of the component and clear the component's internal state. + +The `onChange` callback can also return a new set of options. This is the easiest way to manipulate existing options. It can be used when there should be a minimum or maximum number of options or if at least one option must be selected: + +```jsx +import { Select, iterateAndCopyGroup } from 'hds-react'; + +// some properties are omitted to make the example clearer + {...} />; +``` + +| Argument | Description | Values | Default value | +| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------- | +| `selectedOptions` | An array of selected options in object format. | `Option[]` | [] | +| `clickedOption` | The option that was clicked. Can be a group label too. The option data is prior to the selection, so its `selected` property is not updated. | `Option` | - | +| `data` | All data of the Select component. | SelectData | - | +| [Table 2: Arguments of the onChange callback ] | + +The `onChange` can return data that is stored immediately to the Select component. Returning any data is optional. + +| Property | Description | Values | Default value | +| ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------- | +| `groups` | Array of `groups` in the same format as passed to the Select component. Cannot be used with `options`. | `Option[]` | - | +| `invalid` | The `invalid` property. | `boolean` | - | +| `options` | Array of `options` in the same format as passed to the Select component. Cannot be used with `groups`. | `Option[]` | - | +| `texts` | Updated texts in the same format as passed to the Select component. Does not need to include all texts. Only updated ones. | `Texts` | - | +| [Table 3: Properties of the return object of the onChange callback] | + +### Texts + +The component has default texts in Finnish, English, and Swedish. The used language is passed in `texts.language`. The value can be `fi`, `en` or `sv`. Default is `fi`. + +If additional texts are passed, those values are checked first and any missing texts are picked from the default texts with the given language. + +Usually `label` and `placeholder` are given to instruct the user what to do. + +```jsx +// some properties are omitted to make the example clearer + {return key} />; +``` + +Possible values for the `key` are listed below. The `contents` is an object used for text interpolation, and it contains information about the current state of the component. + +| Key | Purpose | +| -------------------------------------- | ---------------------------------------------------------------------------------------------- | +| `assistive` | Assistive text shown below the button and tags. | +| `choiceCount_multiple` | Tells the user how many choices are available. Plural. | +| `choiceCount_one` | Tells the user there is only one choice available. | +| `clearButtonAriaLabel_multiple` | The `aria-label` for the clear button when there are multiple choices to clear. | +| `clearButtonAriaLabel_one` | The `aria-label` for the clear button when there is only one choice to clear. | +| `dropdownButtonAriaLabel` | The `aria-label` for the main button. | +| `error` | Error text. Shown only if `invalid` is `true`. | +| `filterClearButtonAriaLabel` | The `aria-label` for the clear button of the filter input field . | +| `filteredWithoutResultsInfo` | Text shown when there are no options with the given filter. | +| `filterLabel` | Label of the filter input field. | +| `filterPlaceholder` | Placeholder of the filter input field. | +| `filterResults` | Screenreader information shown when options have been filtered. | +| `filterResultsCount_multiple` | Shown with the `filterResults` text informing there are multiple options. | +| `filterResultsCount_one` | Shown with the `filterResults` text informing there is only one option. | +| `filterWithAnotherTerm` | Suggestion shown when there are no options with the given filter. | +| `label` | Label of the component. | +| `multiSelectGroupAriaLabel` | The `aria-label` for each group in a multiselect. | +| `noSelectedOptions` | Part of the main button `aria-label` when there are no options selected. | +| `placeholder` | Placeholder of the component. | +| `required` | Part of the main button `aria-label` informing a selection is required. | +| `searchClearButtonAriaLabel` | The `aria-label` for the clear button of the filter input field. | +| `searchedWithoutResultsInfo` | Text shown when there are results with the given search. | +| `searchErrorText` | Text shown when the search resulted in an error. | +| `searchErrorTitle` | Title shown when the search resulted in an error. | +| `searching` | Screenreader information shown when search started. | +| `searchLabel` | Label of the search input field. | +| `searchPlaceholder` | Placeholder of the search input field. | +| `searchResults_multiple` | Screenreader information shown when search resulted in multiple options. | +| `searchResults_one` | Screenreader information shown when search resulted in one option. | +| `searchWithAnotherTerm` | Suggestion shown when there are no options with the given filter. | +| `selectedOptionsCount_multiple` | Part of the main button `aria-label` when there are multiple options selected. | +| `selectedOptionsCount_one` | Part of the main button `aria-label` when there is one option selected. | +| `selectedOptionsCount_zero` | Part of the main button `aria-label` when there are no options selected. | +| `tagRemoveSelectionAriaLabel` | The `aria-label` for delete button in a tag. | +| `tagsClearAllButton` | Clear all button text below tags. | +| `tagsClearAllButtonAriaLabel_multiple` | The `aria-label` for the clear all button below tags when there are multiple choices to clear. | +| `tagsClearAllButtonAriaLabel_one` | The `aria-label` for the clear all button below tags when there is only one choice to clear. | +| `tagsPartiallyHidden` | Screenreader information telling some tags were hidden after pressing the "Show less" button | +| `tagsRemaining_multiple` | Screenreader information telling there are multiple choices left after a tag was removed. | +| `tagsRemaining_one` | Screenreader information telling there is only one choice left after a tag was removed. | +| `tagsShowAllButton` | Show all button text below tags. | +| `tagsShowAllButtonAriaLabel` | The `aria-label` for the show all button below tags. | +| `tagsShowLessButton` | Show all button text below tags. | +| `tagsShowLessButtonAriaLabel` | The `aria-label` for the show all button below tags. | +| [Table 4: Text keys] | | + +Note that several texts are also used in `aria-labels`. For example, `label` and `placeholder` are included in the `aria-label` when needed. Some other texts are also not shown in the user interface and are only to notify screen readers. + +Current texts can be seen in the source code. + +#### Text templates and interpolation + +In some cases, the outputted text depends on the option that was selected or the number of selections. For example, when a search is finished, a notification is rendered for the screen reader: "Found 5 options for search term 'Banana'." + +The key of the text template is `searchResults` and its contents are `Found {{numberIndicator}} options for search term "{{value}}"`. + +The Select component creates the content for the interpolation when a text is needed and interpolates the template with the content. + +| Property | Description | Values | Default value | +| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | +| `label` | Label or any related string needed for the outputted text. For example, `option.label` if an option is related. | `string` | - | +| `numberIndicator` | Related number needed for the outputted text. Like number of search results, filtered options, etc. | `number` | - | +| `selectionCount` | Number of selected options. | `number` | - | +| `value` | Label or any related string needed for the outputted text. For example, filtered or searched string or `option.value`. | `string` | - | +| [Table 5: Content of the text interpolation] | + +#### Updating texts + +Texts can be changed with props, using the text function, and also at runtime when the `onChange` is called. If the `onChange` returns a `texts` property, the texts will be appended to the current texts. + +This is a convenient way to show error or assistive texts after the user makes changes. + +### Filtering + +Options can be filtered with a function. If the `filter` property is set, the component will show an input field where the user can write text to filter options with. + +HDS provides a `defaultFilter` that checks if the label of an option starts with given text. The filter function should return `true` or `false`. The returned value is set to the option's `visible` property, and options are then hidden. Group labels are not filtered. They are visible if any of their options are visible. + +The `filter` function has the following signature: + +```jsx +// some properties are omitted to make the example clearer +{...}} />; +``` + +It is called with the following arguments: + +| Argument | Description | Values | +| -------------------------------------------- | --------------------------------- | ---------------------------------------------------------------------------------- | +| `searchValue` | The text user has inputted. | `string` | +| `selectedOptions` | Currently selected options. | `Option[]` | +| `data` | All data of the Select component. | SelectData | +| [Table 7: Arguments of the filter function ] | + +The function should return either `groups` or `options`. + +| Property | Description | Values | Default value | +| ----------------------------------------------- | ------------------------------------------------------------------------ | ---------- | ------------- | +| `groups` | Array of `groups` in the same format as passed to the Select component. | `Option[]` | - | +| `options` | Array of `options` in the same format as passed to the Select component. | `Option[]` | - | +| [Table 8: Return values of the search function] | + +If the search results in an error, an error message is shown to the user. + +### useSelectStorage hook + +The properties passed to the Select component must be memoized or React will render a new instance of the component when props change. This means user's selections could be lost when the component re-renders. + +Properties and selected options may be stored to the parent component's state, and the user's changes have to be syncronized manually when the `onChange` is called. + +The `useSelectStorage` is an utility hook that manages the memoisation and data updates automatically. It accepts the same properties as the Select function, except the "value" property. The selected values should not be reset again manually after initialisation. + +```jsx +const storage = useSelectStorage({ + groups: [ + { + label: 'Group 1', + options: [...], + }, + { + label: 'Group 2', + options: [...], + }, + ], + onChange: (selectedOptions) => { + // handle changes + }, + multiSelect: true, + filter: defaultFilter, + disabled: false, + open: false, + invalid: false, + id: 'hds-select-component', +}); + { + console.log('selectedOptions:' + selectedOptions.map((option) => option.value).join(', ')); + }} +/> + + + + + + { + console.log('selectedOptions:' + selectedOptions.map((option) => option.value).join(', ')); + }} + /> + + +#### Select with filter + +A filtering function is recommended when the user needs to choose from more than 15 options (up to hundreds of options). + + + { + console.log('selectedOptions:' + selectedOptions.map((option) => option.value).join(', ')); + }} + /> + + +#### Multiselect + +The multiselect variant is used when the user needs to select one or more options from a list of 7 to 15 items. If more than 15 options are available, consider adding a filtering function. Multiselect is a complex component, so always be considerate when using this. + + + { + console.log('selectedOptions:' + selectedOptions.map((option) => option.value).join(', ')); + }} + /> + + +#### Multiselect with filter and groups + +Options can be grouped to provide additional context or to visually separate them. Each group requires a title for better organization. A group can also allow the user to select all items by naming it “Select All” with a checkbox. + + + { + console.log('selectedOptions:' + selectedOptions.map((option) => option.value).join(', ')); + }} + /> + + +#### Multiselect with tags somewhere else + +Selected options can be displayed in another section of the page. This approach allows options to be grouped in the same location with multiple select components, such as in filtering search pages. + +An example of this complex variant is in the Storybook. + +### Keyboard usage + +- When select is closed: + + - Open it by pressing either `Enter`, `Space` or `ArrowDown`. + - Writing text also opens it and the typed text is copied to the input field. If an input field does not exist, an option matching the text is focused. + +- When select is open: + + - Move up and down using `ArrowUp` and `ArrowDown` + - Select an option by pressing either `Space` or `Enter` and dropdown closes if it is not a multiselect. + - Collapse dropdown without selecting by pressing `Escape`. + - Move to the first element by pressing `PageUp` or `Home`. + - Move to the last element by pressing `PageDown` or `End`. + +- When open or closed + - Writing text anywhere copies the typed text to the input field. If an input field does not exist, an option matching the text is focused. The `defaultFilter` is used to find a matching option. + - Writing also opens the dropdown. diff --git a/site/src/docs/components/select/tabs.mdx b/site/src/docs/components/select/tabs.mdx index 64b1dbc3f5..3195b929c3 100644 --- a/site/src/docs/components/select/tabs.mdx +++ b/site/src/docs/components/select/tabs.mdx @@ -10,6 +10,8 @@ import PageTabs from '../../../components/PageTabs'; import StatusLabel from '../../../components/StatusLabel'; import Notification from '../../../components/Notification'; +# Select +
Draft @@ -19,8 +21,8 @@ import Notification from '../../../components/Notification';
- A select offers a user a list of options, of which one or multiple can be selected. Select components are often used - as part of forms and filters. + The select is a dialog that allows users to quickly navigate and select one or multiple items from a list. It includes + a text input for filtering and supports item grouping. Dropdowns are often used as part of forms and filters. From a0bbca8d9e0643b6d7981ed571508a39105855cd Mon Sep 17 00:00:00 2001 From: NikoHelle Date: Fri, 18 Oct 2024 15:32:56 +0300 Subject: [PATCH 3/5] (hds-2069) (hds-2060) Migration guide --- .../tutorials/migrate-to-4.0.0.mdx | 104 +++++++++++++++--- 1 file changed, 89 insertions(+), 15 deletions(-) diff --git a/site/src/docs/getting-started/tutorials/migrate-to-4.0.0.mdx b/site/src/docs/getting-started/tutorials/migrate-to-4.0.0.mdx index 9f20bf8a52..bec8e9c842 100644 --- a/site/src/docs/getting-started/tutorials/migrate-to-4.0.0.mdx +++ b/site/src/docs/getting-started/tutorials/migrate-to-4.0.0.mdx @@ -15,13 +15,10 @@ import Notification from '../../../components/Notification'; This guide helps you to start using HDS version 4.0.0. - When updating to HDS 4.0.0 check and test at least these: - - dropdown(Combobox and select) is replaced with whole new select - - cookieConsent is replaced with new CookieConsent and now also available in core and standalone js - - separate ariaLabel is replaced with native aria-label - - iconLeft is now iconStart and iconRight is now iconEnd - - size variants in components are changed to enums - - Use of Sketch ends, check Figma files instead + When updating to HDS 4.0.0 check and test at least these: - dropdown(Combobox and select) is replaced with whole new + select - cookieConsent is replaced with new CookieConsent and now also available in core and standalone js - separate + ariaLabel is replaced with native aria-label - iconLeft is now iconStart and iconRight is now iconEnd - size variants + in components are changed to enums - Use of Sketch ends, check Figma files instead ## Breaking changes @@ -188,11 +185,93 @@ The `iconLeft` prop has been renamed to `iconStart` React storybook examples +### Select component rewritten + +The Select component has been rewritten, and most props have changed. There is now one `Select` component instead of `Select` and `Combobox`. + +Select with a `filter` function is the same as Combobox. + +### Passing options and option groups + +Supported formats for the options are now: + +- array of strings `["Option1"]` +- array of option objects `[{label:"Option 1", value:"Option 1 value"}]` +- native html with `` and `