diff --git a/frontend/src/app/main/ui/ds/controls/combobox.mdx b/frontend/src/app/main/ui/ds/controls/combobox.mdx index ce8f347c0d..93e6adf682 100644 --- a/frontend/src/app/main/ui/ds/controls/combobox.mdx +++ b/frontend/src/app/main/ui/ds/controls/combobox.mdx @@ -11,23 +11,27 @@ import * as ComboboxStories from "./combobox.stories"; # Combobox -Combobox lets users choose one option from an options menu or enter a custom value that is not listed in the menu. It combines the functionality of a dropdown menu and an input field, allowing for both selection and free-form input. +The `combobox*` component lets users choose one option from an options menu or enter a custom value that is not listed in the menu. It combines the functionality of a dropdown menu and an input field, allowing for both selection and free-form input. ## Variants -**Text**: We will use this variant when there are enough space and icons don't add any useful context. +We will use the text-only variant when there are enough space and icons don't add any useful context. -**Icon and text**: We will use this variant when there are enough space and icons add any useful context. +We will use the icon and text variant when there are enough space and icons add any useful context. +If we consider that empty options have a special meaning, we can move them to the end of the list, to a section separate from the rest. + + + ## Technical notes ### Icons -Each option of `combobox*` may accept an `icon`, which must contain an [icon ID](../foundations/assets/icon.mdx). +Each option of `combobox*` accepts an optional `icon`, which must contain an [icon ID](../foundations/assets/icon.mdx). These are available in the `app.main.ds.foundations.assets.icon` namespace. ```clj @@ -49,8 +53,6 @@ These are available in the `app.main.ds.foundations.assets.icon` namespace. ]}] ``` - - ## Usage guidelines (design) ### Where to Use diff --git a/frontend/src/app/main/ui/ds/controls/combobox.stories.jsx b/frontend/src/app/main/ui/ds/controls/combobox.stories.jsx index 88126a5bb1..448ba3ac57 100644 --- a/frontend/src/app/main/ui/ds/controls/combobox.stories.jsx +++ b/frontend/src/app/main/ui/ds/controls/combobox.stories.jsx @@ -11,7 +11,27 @@ import { userEvent, within, expect } from "@storybook/test"; const { Combobox } = Components; -let lastValue = null; +const options = [ + { id: "Monday", label: "Monday" }, + { id: "Tuesday", label: "Tuesday" }, + { id: "Wednesday", label: "Wednesday" }, + { id: "Thursday", label: "Thursday" }, + { id: "Friday", label: "Friday" }, + { id: "", label: "(Empty)" }, + { id: "Saturday", label: "Saturday" }, + { id: "Sunday", label: "Sunday" }, +]; + +const optionsWithIcons = [ + { id: "Monday", label: "Monday", icon: "fill-content" }, + { id: "Tuesday", label: "Tuesday", icon: "pentool" }, + { id: "Wednesday", label: "Wednesday" }, + { id: "Thursday", label: "Thursday" }, + { id: "Friday", label: "Friday" }, + { id: "", label: "(Empty)" }, + { id: "Saturday", label: "Saturday" }, + { id: "Sunday", label: "Sunday" }, +]; export default { title: "Controls/Combobox", @@ -20,76 +40,46 @@ export default { disabled: { control: "boolean" }, maxLength: { control: "number" }, hasError: { control: "boolean" }, + emptyToEnd: { control: "boolean" }, }, args: { disabled: false, maxLength: 10, hasError: false, - placeholder: "Select a month", - options: [ - { id: "January", label: "January" }, - { id: "February", label: "February" }, - { id: "March", label: "March" }, - { id: "April", label: "April" }, - { id: "May", label: "May" }, - { id: "June", label: "June" }, - { id: "July", label: "July" }, - { id: "August", label: "August" }, - { id: "September", label: "September" }, - { id: "October", label: "October" }, - { id: "November", label: "November" }, - { id: "December", label: "December" }, - ], - defaultSelected: "February", + placeholder: "Select a weekday", + emptyToEnd: false, + options: options, + defaultSelected: "Tuesday", }, parameters: { controls: { exclude: ["options", "defaultSelected"], }, - }, - render: ({ ...args }) => ( -
- -
- ), -}; - -export const Default = { - parameters: { docs: { story: { - height: "450px", + height: "320px", }, }, }, + render: ({ ...args }) => , }; +export const Default = {}; + export const WithIcons = { args: { - options: [ - { id: "January", label: "January", icon: "fill-content" }, - { id: "February", label: "February", icon: "pentool" }, - { id: "March", label: "March" }, - { id: "April", label: "April" }, - { id: "May", label: "May" }, - { id: "June", label: "June" }, - { id: "July", label: "July" }, - { id: "August", label: "August" }, - { id: "September", label: "September" }, - { id: "October", label: "October" }, - { id: "November", label: "November" }, - { id: "December", label: "December" }, - ], - }, - parameters: { - docs: { - story: { - height: "450px", - }, - }, + options: optionsWithIcons, }, }; +export const EmptyToEnd = { + args: { + emptyToEnd: true, + }, +}; + +let lastValue = null; + export const TestInteractions = { ...WithIcons, args: { @@ -167,8 +157,8 @@ export const TestInteractions = { await userEvent.keyboard("{ArrowDown}"); await userEvent.keyboard("{Enter}"); - expect(input).toHaveValue("February"); - expect(lastValue).toBe("February"); + expect(input).toHaveValue("Tuesday"); + expect(lastValue).toBe("Tuesday"); await userEvent.clear(input); // Arrow up @@ -177,11 +167,11 @@ export const TestInteractions = { await userEvent.keyboard("{ArrowUp}"); await userEvent.keyboard("{ArrowUp}"); - expect(combobox).toHaveAttribute("aria-activedescendant", "November"); + expect(combobox).toHaveAttribute("aria-activedescendant", "Saturday"); await userEvent.keyboard("{Enter}"); - expect(input).toHaveValue("November"); - expect(lastValue).toBe("November"); + expect(input).toHaveValue("Saturday"); + expect(lastValue).toBe("Saturday"); await userEvent.clear(input); // Home @@ -191,21 +181,21 @@ export const TestInteractions = { await userEvent.keyboard("{ArrowDown}"); await userEvent.keyboard("{ArrowDown}"); await userEvent.keyboard("{Home}"); - expect(combobox).toHaveAttribute("aria-activedescendant", "January"); + expect(combobox).toHaveAttribute("aria-activedescendant", "Monday"); await userEvent.keyboard("{Enter}"); - expect(input).toHaveValue("January"); - expect(lastValue).toBe("January"); + expect(input).toHaveValue("Monday"); + expect(lastValue).toBe("Monday"); await userEvent.clear(input); }); - await step("Filter with 'Ju' and select July", async () => { + await step("Filter with 'es' (Tuesday, Wednesday) and select Wednesday", async () => { await userEvent.clear(input); await userEvent.keyboard("{Escape}"); await userEvent.click(input); - await userEvent.type(input, "Ju"); + await userEvent.type(input, "es"); const options = await canvas.findAllByTestId("dropdown-option"); expect(options).toHaveLength(2); @@ -215,8 +205,8 @@ export const TestInteractions = { await userEvent.keyboard("{Enter}"); - expect(input).toHaveValue("July"); - expect(lastValue).toBe("July"); + expect(input).toHaveValue("Wednesday"); + expect(lastValue).toBe("Wednesday"); }); await step("Close dropdown when focusing out", async () => { diff --git a/frontend/src/app/main/ui/ds/controls/select.mdx b/frontend/src/app/main/ui/ds/controls/select.mdx index 8d8b879f5d..0ecdf1e7af 100644 --- a/frontend/src/app/main/ui/ds/controls/select.mdx +++ b/frontend/src/app/main/ui/ds/controls/select.mdx @@ -11,17 +11,22 @@ import * as SelectStories from "./select.stories"; # Select -Select lets users choose one option from an options menu. +The `select*` component lets users choose one option from an options menu. ## Variants -**Text**: We will use this variant when there are enough space and icons don't add any useful context. +We will use the text-only variant when there are enough space and icons don't add any useful context. -**Icon and text**: We will use this variant when there are enough space and icons add any useful context. +We will use the icon and text variant when there are enough space and icons add any useful context. + +If we consider that empty options have a special meaning, we can move them to the end of the list, to a section separate from the rest. + + + ## Technical notes ### Icons @@ -49,8 +54,6 @@ These are available in the `app.main.ds.foundations.assets.icon` namespace. ]}] ``` - - ## Usage guidelines (design) ### Where to use diff --git a/frontend/src/app/main/ui/ds/controls/select.stories.jsx b/frontend/src/app/main/ui/ds/controls/select.stories.jsx index 68774e833e..2529b851dc 100644 --- a/frontend/src/app/main/ui/ds/controls/select.stories.jsx +++ b/frontend/src/app/main/ui/ds/controls/select.stories.jsx @@ -9,34 +9,42 @@ import Components from "@target/components"; const { Select } = Components; +const options = [ + { id: "option-code", label: "Code" }, + { id: "option-design", label: "Design" }, + { id: "", label: "(Empty)" }, + { id: "option-menu", label: "Menu" }, +]; + +const optionsWithIcons = [ + { id: "option-code", label: "Code", icon: "fill-content" }, + { id: "option-design", label: "Design", icon: "pentool" }, + { id: "", label: "(Empty)" }, + { id: "option-menu", label: "Menu" }, +]; + export default { title: "Controls/Select", component: Select, argTypes: { disabled: { control: "boolean" }, + emptyToEnd: { control: "boolean" }, }, args: { disabled: false, - options: [ - { - label: "Code", - id: "option-code", - }, - { - label: "Design", - id: "option-design", - }, - { - label: "Menu", - id: "option-menu", - }, - ], + options: options, + emptyToEnd: false, defaultSelected: "option-code", }, parameters: { controls: { exclude: ["options", "defaultSelected"], }, + docs: { + story: { + height: "200px", + }, + }, }, render: ({ ...args }) =>