From 32ca3fd0640756170ae7df17d19177f1d9fc5c20 Mon Sep 17 00:00:00 2001 From: thushara joseph Date: Tue, 7 May 2024 15:58:34 +0530 Subject: [PATCH 01/13] added examples for secondary label for combobox --- .../stories/combo-box/combo-box.stories.tsx | 50 +++++++++++++++++++ site/docs/components/combo-box/examples.mdx | 6 +++ .../src/examples/combo-box/SecondaryLabel.tsx | 48 ++++++++++++++++++ site/src/examples/combo-box/index.ts | 1 + 4 files changed, 105 insertions(+) create mode 100644 site/src/examples/combo-box/SecondaryLabel.tsx diff --git a/packages/core/stories/combo-box/combo-box.stories.tsx b/packages/core/stories/combo-box/combo-box.stories.tsx index 6d5bb9ab7ba..09cc6d68bf6 100644 --- a/packages/core/stories/combo-box/combo-box.stories.tsx +++ b/packages/core/stories/combo-box/combo-box.stories.tsx @@ -8,6 +8,8 @@ import { ComboBoxProps, Option, OptionGroup, + Text, + Label } from "@salt-ds/core"; import { CountryCode, @@ -652,3 +654,51 @@ MultiplePillsTruncated.args = { multiselect: true, truncate: true, }; + +export const SecondaryLabel: StoryFn> = (args) => { + const [value, setValue] = useState(args.defaultValue?.toString() ?? ""); + + const handleChange = (event: ChangeEvent) => { + // React 16 backwards compatibility + event.persist(); + + const value = event.target.value; + setValue(value); + }; + + const handleSelectionChange = ( + event: SyntheticEvent, + newSelected: Person[] + ) => { + if (newSelected.length === 1) { + setValue(newSelected[0].firstName); + } else { + setValue(""); + } + }; + + const options = people.filter( + (person) => + person.firstName.toLowerCase().includes(value.trim().toLowerCase()) || + person.lastName.toLowerCase().includes(value.trim().toLowerCase()) + ); + + return ( + + {...args} + onChange={handleChange} + onSelectionChange={handleSelectionChange} + value={value} + valueToString={(person) => person.displayName} + > + {options.map((person) => ( + + ))} + + ); +}; diff --git a/site/docs/components/combo-box/examples.mdx b/site/docs/components/combo-box/examples.mdx index e12201ea6f1..5300aefa535 100644 --- a/site/docs/components/combo-box/examples.mdx +++ b/site/docs/components/combo-box/examples.mdx @@ -176,5 +176,11 @@ When the `value` prop is an object, the `valueToString` prop must be provided to Use the `truncate` prop when you are limited on space and want to prevent the multi-select combobox from spanning multiple lines. + + + +## Secondary Label + +You can pass secondary content as children to the `Option` component to achieve this use-case. diff --git a/site/src/examples/combo-box/SecondaryLabel.tsx b/site/src/examples/combo-box/SecondaryLabel.tsx new file mode 100644 index 00000000000..15344e1d4f0 --- /dev/null +++ b/site/src/examples/combo-box/SecondaryLabel.tsx @@ -0,0 +1,48 @@ +import { ChangeEvent, ReactElement, SyntheticEvent, useState } from "react"; +import { ComboBox, Label, Option, StackLayout, Text } from "@salt-ds/core"; +import { largestCities, LargeCity } from "./exampleData"; + +export const SecondaryLabel = (): ReactElement => { + const [value, setValue] = useState(""); + + const handleChange = (event: ChangeEvent) => { + const value = event.target.value; + setValue(value); + }; + + const handleSelectionChange = ( + event: SyntheticEvent, + newSelected: LargeCity[] + ) => { + if (newSelected.length === 1) { + setValue(newSelected[0].name); + } else { + setValue(""); + } + }; + + const cities = largestCities.slice(0, 5); + + return ( + value.name} + > + {cities + .filter((city) => + city.name.toLowerCase().includes(value.trim().toLowerCase()) + ) + .map((city) => ( + + ))} + + ); +}; diff --git a/site/src/examples/combo-box/index.ts b/site/src/examples/combo-box/index.ts index ab5451bae24..4b0cb54028d 100644 --- a/site/src/examples/combo-box/index.ts +++ b/site/src/examples/combo-box/index.ts @@ -14,3 +14,4 @@ export * from "./CustomFiltering"; export * from "./ServerSideData"; export * from "./ObjectValues"; export * from "./Truncation"; +export * from "./SecondaryLabel"; From d70534ca3dac43e29f252937b9a67fc7679f217f Mon Sep 17 00:00:00 2001 From: thushara joseph Date: Thu, 9 May 2024 11:16:26 +0530 Subject: [PATCH 02/13] prettier --- packages/core/stories/combo-box/combo-box.stories.tsx | 8 ++++---- site/docs/components/combo-box/examples.mdx | 1 + site/src/examples/combo-box/SecondaryLabel.tsx | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/core/stories/combo-box/combo-box.stories.tsx b/packages/core/stories/combo-box/combo-box.stories.tsx index 49e9e7631d0..29a35b478ab 100644 --- a/packages/core/stories/combo-box/combo-box.stories.tsx +++ b/packages/core/stories/combo-box/combo-box.stories.tsx @@ -9,7 +9,7 @@ import { Option, OptionGroup, Text, - Label + Label, } from "@salt-ds/core"; import { CountryCode, @@ -692,10 +692,10 @@ export const SecondaryLabel: StoryFn> = (args) => { valueToString={(person) => person.displayName} > {options.map((person) => ( - ))} diff --git a/site/docs/components/combo-box/examples.mdx b/site/docs/components/combo-box/examples.mdx index 5300aefa535..60e942f1f57 100644 --- a/site/docs/components/combo-box/examples.mdx +++ b/site/docs/components/combo-box/examples.mdx @@ -182,5 +182,6 @@ Use the `truncate` prop when you are limited on space and want to prevent the mu ## Secondary Label You can pass secondary content as children to the `Option` component to achieve this use-case. + diff --git a/site/src/examples/combo-box/SecondaryLabel.tsx b/site/src/examples/combo-box/SecondaryLabel.tsx index 15344e1d4f0..78263dc3b3b 100644 --- a/site/src/examples/combo-box/SecondaryLabel.tsx +++ b/site/src/examples/combo-box/SecondaryLabel.tsx @@ -39,7 +39,7 @@ export const SecondaryLabel = (): ReactElement => { ))} From e93acb3d3ba463aebbca31b149a725cdc8cfff66 Mon Sep 17 00:00:00 2001 From: thushara joseph Date: Thu, 9 May 2024 21:26:17 +0530 Subject: [PATCH 03/13] PR comments --- .../stories/combo-box/combo-box.stories.tsx | 41 ++++++++++++------- .../src/examples/combo-box/SecondaryLabel.tsx | 30 +++++++------- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/packages/core/stories/combo-box/combo-box.stories.tsx b/packages/core/stories/combo-box/combo-box.stories.tsx index 29a35b478ab..2dda5c0dca3 100644 --- a/packages/core/stories/combo-box/combo-box.stories.tsx +++ b/packages/core/stories/combo-box/combo-box.stories.tsx @@ -9,7 +9,6 @@ import { Option, OptionGroup, Text, - Label, } from "@salt-ds/core"; import { CountryCode, @@ -655,6 +654,20 @@ MultiplePillsTruncated.args = { truncate: true, }; +export type LargeCity = { + name: string; + countryCode: CountryCode; +}; + +export const largestCities: LargeCity[] = [ + { name: "Tokyo", countryCode: "JP" }, + { name: "Delhi", countryCode: "IN" }, + { name: "Shanghai", countryCode: "CN" }, + { name: "São Paulo", countryCode: "BR" }, + { name: "Mexico City", countryCode: "MX" }, + { name: "Cairo", countryCode: "EG" }, +]; + export const SecondaryLabel: StoryFn> = (args) => { const [value, setValue] = useState(args.defaultValue?.toString() ?? ""); @@ -668,34 +681,34 @@ export const SecondaryLabel: StoryFn> = (args) => { const handleSelectionChange = ( event: SyntheticEvent, - newSelected: Person[] + newSelected: LargeCity[] ) => { if (newSelected.length === 1) { - setValue(newSelected[0].firstName); + setValue(newSelected[0].name); } else { setValue(""); } }; - const options = people.filter( - (person) => - person.firstName.toLowerCase().includes(value.trim().toLowerCase()) || - person.lastName.toLowerCase().includes(value.trim().toLowerCase()) + const options = largestCities.filter( + (city) => + city.name.toLowerCase().includes(value.trim().toLowerCase()) || + city.countryCode.toLowerCase().includes(value.trim().toLowerCase()) ); return ( - - {...args} + person.displayName} + style={{ width: "266px" }} + valueToString={(city) => city.name} > - {options.map((person) => ( - ))} diff --git a/site/src/examples/combo-box/SecondaryLabel.tsx b/site/src/examples/combo-box/SecondaryLabel.tsx index 78263dc3b3b..eb729a68bea 100644 --- a/site/src/examples/combo-box/SecondaryLabel.tsx +++ b/site/src/examples/combo-box/SecondaryLabel.tsx @@ -1,5 +1,5 @@ import { ChangeEvent, ReactElement, SyntheticEvent, useState } from "react"; -import { ComboBox, Label, Option, StackLayout, Text } from "@salt-ds/core"; +import { ComboBox, Option, StackLayout, Text } from "@salt-ds/core"; import { largestCities, LargeCity } from "./exampleData"; export const SecondaryLabel = (): ReactElement => { @@ -21,7 +21,13 @@ export const SecondaryLabel = (): ReactElement => { } }; - const cities = largestCities.slice(0, 5); + const options = largestCities + .slice(0, 5) + .filter( + (city) => + city.name.toLowerCase().includes(value.trim().toLowerCase()) || + city.countryCode.toLowerCase().includes(value.trim().toLowerCase()) + ); return ( { style={{ width: "266px" }} valueToString={(value) => value.name} > - {cities - .filter((city) => - city.name.toLowerCase().includes(value.trim().toLowerCase()) - ) - .map((city) => ( - - ))} + {options.map((city) => ( + + ))} ); }; From c25d56f8cf7635e4be3f9b59e09122eafb7176ef Mon Sep 17 00:00:00 2001 From: thushara joseph Date: Tue, 14 May 2024 13:10:54 +0530 Subject: [PATCH 04/13] added storybook example for dropdown secondary label --- .../stories/dropdown/dropdown.stories.tsx | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/core/stories/dropdown/dropdown.stories.tsx b/packages/core/stories/dropdown/dropdown.stories.tsx index 19ffcf5eb5c..1b6e61c955f 100644 --- a/packages/core/stories/dropdown/dropdown.stories.tsx +++ b/packages/core/stories/dropdown/dropdown.stories.tsx @@ -7,10 +7,11 @@ import { FormFieldHelperText, FormFieldLabel, StackLayout, + Text, } from "@salt-ds/core"; import { Meta, StoryFn } from "@storybook/react"; -import { GB, US } from "@salt-ds/countries"; +import { CountryCode, GB, US } from "@salt-ds/countries"; import { SyntheticEvent, useState } from "react"; import { LocationIcon } from "@salt-ds/icons"; @@ -313,3 +314,36 @@ export const ObjectValue: StoryFn> = (args) => { ); }; + +export type LargeCity = { + name: string; + countryCode: CountryCode; +}; + +export const largestCities: LargeCity[] = [ + { name: "Tokyo", countryCode: "JP" }, + { name: "Delhi", countryCode: "IN" }, + { name: "Shanghai", countryCode: "CN" }, + { name: "São Paulo", countryCode: "BR" }, + { name: "Mexico City", countryCode: "MX" }, + { name: "Cairo", countryCode: "EG" }, +]; + +export const SecondaryLabel: StoryFn = (args) => { + return ( + city.name} + placeholder="Secondary label example" + > + {largestCities.map((city) => ( + + ))} + + ); +}; From 95cacef5c3c67fd4bc085bd4e445cec6bae292bc Mon Sep 17 00:00:00 2001 From: thushara joseph Date: Mon, 20 May 2024 14:10:39 +0530 Subject: [PATCH 05/13] added dropdown example in site docs --- site/docs/components/combo-box/examples.mdx | 2 +- site/docs/components/dropdown/examples.mdx | 7 ++++++ site/src/examples/dropdown/SecondaryLabel.tsx | 22 +++++++++++++++++++ site/src/examples/dropdown/index.ts | 1 + 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 site/src/examples/dropdown/SecondaryLabel.tsx diff --git a/site/docs/components/combo-box/examples.mdx b/site/docs/components/combo-box/examples.mdx index 60e942f1f57..a0e6b10ae2d 100644 --- a/site/docs/components/combo-box/examples.mdx +++ b/site/docs/components/combo-box/examples.mdx @@ -181,7 +181,7 @@ Use the `truncate` prop when you are limited on space and want to prevent the mu ## Secondary Label -You can pass secondary content as children to the `Option` component to achieve this use-case. +You can pass secondary label's content as children to the `Option` component to achieve this use-case. diff --git a/site/docs/components/dropdown/examples.mdx b/site/docs/components/dropdown/examples.mdx index 3aece44721f..9bc880ed38c 100644 --- a/site/docs/components/dropdown/examples.mdx +++ b/site/docs/components/dropdown/examples.mdx @@ -159,5 +159,12 @@ Start adornments are typically used to describe the purpose of the field (e.g., In order to support advanced use-cases, like where the options are fetched from a server. The `value` prop passed to `Option` can be an object. When the `value` prop is an object, the `valueToString` prop must be provided to `ComboBox` to specify how to display the option in the button. + + + +## Secondary Label + +You can pass secondary label's content as children to the `Option` component to achieve this use-case. + diff --git a/site/src/examples/dropdown/SecondaryLabel.tsx b/site/src/examples/dropdown/SecondaryLabel.tsx new file mode 100644 index 00000000000..f7162fc2f36 --- /dev/null +++ b/site/src/examples/dropdown/SecondaryLabel.tsx @@ -0,0 +1,22 @@ +import { ReactElement } from "react"; +import { Dropdown, Option, StackLayout, Text } from "@salt-ds/core"; +import { largestCities, LargeCity } from "./exampleData"; + +export const SecondaryLabel = (): ReactElement => { + return ( + city.name} + placeholder="Secondary label example" + > + {largestCities.slice(0, 5).map((city) => ( + + ))} + + ); +}; diff --git a/site/src/examples/dropdown/index.ts b/site/src/examples/dropdown/index.ts index f0e4186ecd3..a7516153d52 100644 --- a/site/src/examples/dropdown/index.ts +++ b/site/src/examples/dropdown/index.ts @@ -12,3 +12,4 @@ export * from "./Validation"; export * from "./Variants"; export * from "./WithFormField"; export * from "./ObjectValues"; +export * from "./SecondaryLabel"; From 3b5a25dd21bbed640bb268ff796f47512e869b97 Mon Sep 17 00:00:00 2001 From: thushara joseph Date: Tue, 21 May 2024 13:58:12 +0530 Subject: [PATCH 06/13] design review comment to change examples to city data --- .../stories/combo-box/combo-box.stories.tsx | 60 ++++++++++++------- .../stories/dropdown/dropdown.stories.tsx | 48 ++++++++++----- .../src/examples/combo-box/SecondaryLabel.tsx | 27 ++++----- site/src/examples/combo-box/exampleData.ts | 4 ++ site/src/examples/dropdown/SecondaryLabel.tsx | 12 ++-- site/src/examples/dropdown/exampleData.ts | 4 ++ 6 files changed, 99 insertions(+), 56 deletions(-) diff --git a/packages/core/stories/combo-box/combo-box.stories.tsx b/packages/core/stories/combo-box/combo-box.stories.tsx index 2dda5c0dca3..03d69e96570 100644 --- a/packages/core/stories/combo-box/combo-box.stories.tsx +++ b/packages/core/stories/combo-box/combo-box.stories.tsx @@ -654,21 +654,41 @@ MultiplePillsTruncated.args = { truncate: true, }; -export type LargeCity = { - name: string; - countryCode: CountryCode; +export type CityWithCountry = { + value: string; + country: string; }; -export const largestCities: LargeCity[] = [ - { name: "Tokyo", countryCode: "JP" }, - { name: "Delhi", countryCode: "IN" }, - { name: "Shanghai", countryCode: "CN" }, - { name: "São Paulo", countryCode: "BR" }, - { name: "Mexico City", countryCode: "MX" }, - { name: "Cairo", countryCode: "EG" }, +export const citiesWithCountries = [ + { + value: "Chicago", + country: "US", + }, + { + value: "Miami", + country: "US", + }, + { + value: "New York", + country: "US", + }, + { + value: "Liverpool", + country: "GB", + }, + { + value: "London", + country: "GB", + }, + { + value: "Manchester", + country: "GB", + }, ]; -export const SecondaryLabel: StoryFn> = (args) => { +export const SecondaryLabel: StoryFn> = ( + args +) => { const [value, setValue] = useState(args.defaultValue?.toString() ?? ""); const handleChange = (event: ChangeEvent) => { @@ -681,19 +701,19 @@ export const SecondaryLabel: StoryFn> = (args) => { const handleSelectionChange = ( event: SyntheticEvent, - newSelected: LargeCity[] + newSelected: CityWithCountry[] ) => { if (newSelected.length === 1) { - setValue(newSelected[0].name); + setValue(newSelected[0].value); } else { setValue(""); } }; - const options = largestCities.filter( + const options = citiesWithCountries.filter( (city) => - city.name.toLowerCase().includes(value.trim().toLowerCase()) || - city.countryCode.toLowerCase().includes(value.trim().toLowerCase()) + city.value.toLowerCase().includes(value.trim().toLowerCase()) || + city.country.toLowerCase().includes(value.trim().toLowerCase()) ); return ( @@ -702,13 +722,13 @@ export const SecondaryLabel: StoryFn> = (args) => { onSelectionChange={handleSelectionChange} value={value} style={{ width: "266px" }} - valueToString={(city) => city.name} + valueToString={(city) => city.value} > {options.map((city) => ( - ))} diff --git a/packages/core/stories/dropdown/dropdown.stories.tsx b/packages/core/stories/dropdown/dropdown.stories.tsx index 1b6e61c955f..12f4c5b1f12 100644 --- a/packages/core/stories/dropdown/dropdown.stories.tsx +++ b/packages/core/stories/dropdown/dropdown.stories.tsx @@ -315,32 +315,50 @@ export const ObjectValue: StoryFn> = (args) => { ); }; -export type LargeCity = { - name: string; - countryCode: CountryCode; +export type CityWithCountry = { + value: string; + country: string; }; -export const largestCities: LargeCity[] = [ - { name: "Tokyo", countryCode: "JP" }, - { name: "Delhi", countryCode: "IN" }, - { name: "Shanghai", countryCode: "CN" }, - { name: "São Paulo", countryCode: "BR" }, - { name: "Mexico City", countryCode: "MX" }, - { name: "Cairo", countryCode: "EG" }, +export const citiesWithCountries = [ + { + value: "Chicago", + country: "US", + }, + { + value: "Miami", + country: "US", + }, + { + value: "New York", + country: "US", + }, + { + value: "Liverpool", + country: "GB", + }, + { + value: "London", + country: "GB", + }, + { + value: "Manchester", + country: "GB", + }, ]; export const SecondaryLabel: StoryFn = (args) => { return ( city.name} + valueToString={(city: CityWithCountry) => city.value} placeholder="Secondary label example" > - {largestCities.map((city) => ( - ))} diff --git a/site/src/examples/combo-box/SecondaryLabel.tsx b/site/src/examples/combo-box/SecondaryLabel.tsx index eb729a68bea..b4b825acb87 100644 --- a/site/src/examples/combo-box/SecondaryLabel.tsx +++ b/site/src/examples/combo-box/SecondaryLabel.tsx @@ -1,7 +1,6 @@ import { ChangeEvent, ReactElement, SyntheticEvent, useState } from "react"; import { ComboBox, Option, StackLayout, Text } from "@salt-ds/core"; -import { largestCities, LargeCity } from "./exampleData"; - +import { citiesWithCountries, CityWithCountry } from "./exampleData"; export const SecondaryLabel = (): ReactElement => { const [value, setValue] = useState(""); @@ -12,22 +11,20 @@ export const SecondaryLabel = (): ReactElement => { const handleSelectionChange = ( event: SyntheticEvent, - newSelected: LargeCity[] + newSelected: CityWithCountry[] ) => { if (newSelected.length === 1) { - setValue(newSelected[0].name); + setValue(newSelected[0].value); } else { setValue(""); } }; - const options = largestCities - .slice(0, 5) - .filter( - (city) => - city.name.toLowerCase().includes(value.trim().toLowerCase()) || - city.countryCode.toLowerCase().includes(value.trim().toLowerCase()) - ); + const options = citiesWithCountries.filter( + (city) => + city.value.toLowerCase().includes(value.trim().toLowerCase()) || + city.country.toLowerCase().includes(value.trim().toLowerCase()) + ); return ( { onSelectionChange={handleSelectionChange} value={value} style={{ width: "266px" }} - valueToString={(value) => value.name} + valueToString={(value) => value.value} > {options.map((city) => ( - ))} diff --git a/site/src/examples/combo-box/exampleData.ts b/site/src/examples/combo-box/exampleData.ts index fa984b0c532..a73a92cf7d1 100644 --- a/site/src/examples/combo-box/exampleData.ts +++ b/site/src/examples/combo-box/exampleData.ts @@ -13,6 +13,10 @@ export const shortColorData = [ "White", "Yellow", ]; +export type CityWithCountry = { + value: string; + country: string; +}; export const citiesWithCountries = [ { diff --git a/site/src/examples/dropdown/SecondaryLabel.tsx b/site/src/examples/dropdown/SecondaryLabel.tsx index f7162fc2f36..d81d054b1a6 100644 --- a/site/src/examples/dropdown/SecondaryLabel.tsx +++ b/site/src/examples/dropdown/SecondaryLabel.tsx @@ -1,19 +1,19 @@ import { ReactElement } from "react"; import { Dropdown, Option, StackLayout, Text } from "@salt-ds/core"; -import { largestCities, LargeCity } from "./exampleData"; +import { citiesWithCountries, CityWithCountry } from "./exampleData"; export const SecondaryLabel = (): ReactElement => { return ( city.name} + valueToString={(city: CityWithCountry) => city.value} placeholder="Secondary label example" > - {largestCities.slice(0, 5).map((city) => ( - ))} diff --git a/site/src/examples/dropdown/exampleData.ts b/site/src/examples/dropdown/exampleData.ts index fa984b0c532..a73a92cf7d1 100644 --- a/site/src/examples/dropdown/exampleData.ts +++ b/site/src/examples/dropdown/exampleData.ts @@ -13,6 +13,10 @@ export const shortColorData = [ "White", "Yellow", ]; +export type CityWithCountry = { + value: string; + country: string; +}; export const citiesWithCountries = [ { From 1c8126ff51bbd7b4fd11de63131b3dd2f765ddb4 Mon Sep 17 00:00:00 2001 From: thushara joseph Date: Tue, 21 May 2024 14:50:51 +0530 Subject: [PATCH 07/13] review comment changed the gap to 0.5 --- packages/core/stories/combo-box/combo-box.stories.tsx | 2 +- packages/core/stories/dropdown/dropdown.stories.tsx | 2 +- site/src/examples/combo-box/SecondaryLabel.tsx | 2 +- site/src/examples/dropdown/SecondaryLabel.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/stories/combo-box/combo-box.stories.tsx b/packages/core/stories/combo-box/combo-box.stories.tsx index 03d69e96570..b76e584f194 100644 --- a/packages/core/stories/combo-box/combo-box.stories.tsx +++ b/packages/core/stories/combo-box/combo-box.stories.tsx @@ -726,7 +726,7 @@ export const SecondaryLabel: StoryFn> = ( > {options.map((city) => ( @@ -442,18 +589,18 @@ export const LongList: StoryFn> = (args) => { value={value} valueToString={(countryCode) => countryMetaMap[countryCode].countryName} > - - {Object.entries(groupedOptions).map(([firstLetter, options]) => ( - - {options.map((country) => ( - + + {country.countryName} + + ))} + + ))} ); }; @@ -654,89 +801,6 @@ MultiplePillsTruncated.args = { truncate: true, }; -export type CityWithCountry = { - value: string; - country: string; -}; - -export const citiesWithCountries = [ - { - value: "Chicago", - country: "US", - }, - { - value: "Miami", - country: "US", - }, - { - value: "New York", - country: "US", - }, - { - value: "Liverpool", - country: "GB", - }, - { - value: "London", - country: "GB", - }, - { - value: "Manchester", - country: "GB", - }, -]; - -export const SecondaryLabel: StoryFn> = ( - args -) => { - const [value, setValue] = useState(args.defaultValue?.toString() ?? ""); - - const handleChange = (event: ChangeEvent) => { - // React 16 backwards compatibility - event.persist(); - - const value = event.target.value; - setValue(value); - }; - - const handleSelectionChange = ( - event: SyntheticEvent, - newSelected: CityWithCountry[] - ) => { - if (newSelected.length === 1) { - setValue(newSelected[0].value); - } else { - setValue(""); - } - }; - - const options = citiesWithCountries.filter( - (city) => - city.value.toLowerCase().includes(value.trim().toLowerCase()) || - city.country.toLowerCase().includes(value.trim().toLowerCase()) - ); - - return ( - city.value} - placeholder="City" - > - {options.map((city) => ( - - ))} - - ); -}; - export const FreeText: StoryFn = (args) => { const [value, setValue] = useState(getTemplateDefaultValue(args)); const [selectedValues, setSelectedValues] = useState([]); @@ -780,7 +844,7 @@ export const FreeText: StoryFn = (args) => { ))} {value && !usStates.includes(value) && ( )} diff --git a/packages/core/stories/dropdown/dropdown.stories.tsx b/packages/core/stories/dropdown/dropdown.stories.tsx index 8074994919e..461b38980a1 100644 --- a/packages/core/stories/dropdown/dropdown.stories.tsx +++ b/packages/core/stories/dropdown/dropdown.stories.tsx @@ -11,9 +11,13 @@ import { } from "@salt-ds/core"; import { Meta, StoryFn } from "@storybook/react"; -import { CountryCode, GB, US } from "@salt-ds/countries"; import { SyntheticEvent, useState } from "react"; -import { LocationIcon } from "@salt-ds/icons"; +import { + LocationIcon, + UserAdminIcon, + GuideClosedIcon, + EditIcon, +} from "@salt-ds/icons"; export default { title: "Core/Dropdown", @@ -146,8 +150,8 @@ export const Variants: StoryFn = () => { ); }; -export const MultiSelect = Template.bind({}); -MultiSelect.args = { +export const Multiselect = Template.bind({}); +Multiselect.args = { multiselect: true, }; @@ -182,14 +186,24 @@ export const Grouped: StoryFn = (args) => { ); }; -const countries: Record = { - GB: { - icon: , - name: "United Kingdom of Great Britain and Northern Ireland", +const permissions: Record< + string, + { icon: JSX.Element; name: string; description: string } +> = { + read: { + icon: , + name: "Read", + description: "Read only", + }, + write: { + icon: , + name: "Write", + description: "Read and write only", }, - US: { - icon: , - name: "United States of America", + admin: { + icon: , + name: "Admin", + description: "Full access", }, }; @@ -204,7 +218,7 @@ export const ComplexOption: StoryFn = (args) => { args.onSelectionChange?.(event, newSelected); }; - const adornment = countries[selected[0] ?? ""]?.icon || null; + const adornment = permissions[selected[0] ?? ""]?.icon || null; return ( = (args) => { selected={selected} startAdornment={adornment} onSelectionChange={handleSelectionChange} - valueToString={(item) => countries[item].name} + valueToString={(item) => permissions[item].name} > - - + {Object.values(permissions).map(({ name, icon, description }) => ( + + ))} ); }; @@ -292,7 +319,7 @@ const people: Person[] = [ { id: 4, firstName: "Jane", lastName: "Smith", displayName: "Jane Smith" }, ]; -export const ObjectValue: StoryFn> = (args) => { +export const ObjectValue: StoryFn> = () => { const [selected, setSelected] = useState([]); const handleSelectionChange = ( event: SyntheticEvent, @@ -314,54 +341,3 @@ export const ObjectValue: StoryFn> = (args) => { ); }; - -export type CityWithCountry = { - value: string; - country: string; -}; - -export const citiesWithCountries = [ - { - value: "Chicago", - country: "US", - }, - { - value: "Miami", - country: "US", - }, - { - value: "New York", - country: "US", - }, - { - value: "Liverpool", - country: "GB", - }, - { - value: "London", - country: "GB", - }, - { - value: "Manchester", - country: "GB", - }, -]; - -export const SecondaryLabel: StoryFn = (args) => { - return ( - city.value} - placeholder="City" - > - {citiesWithCountries.map((city) => ( - - ))} - - ); -}; diff --git a/site/docs/components/combo-box/examples.mdx b/site/docs/components/combo-box/examples.mdx index a0e6b10ae2d..81980acefa0 100644 --- a/site/docs/components/combo-box/examples.mdx +++ b/site/docs/components/combo-box/examples.mdx @@ -120,7 +120,7 @@ Use the `Option` component to display a message when there are no options availa ## Complex options -You can supply static content as children to the `Option` component. This can be used to visually assist users, for example rendering the flag icons next to the name of countries as seen in this example. +You can supply static content as children to the `Option` component. This can be used to visually assist users, for example rendering the flag icons next to the name of countries or to provide descriptions for additional context as seen in this example. ### Best practices @@ -176,12 +176,5 @@ When the `value` prop is an object, the `valueToString` prop must be provided to Use the `truncate` prop when you are limited on space and want to prevent the multi-select combobox from spanning multiple lines. - - - -## Secondary Label - -You can pass secondary label's content as children to the `Option` component to achieve this use-case. - diff --git a/site/docs/components/dropdown/examples.mdx b/site/docs/components/dropdown/examples.mdx index 9bc880ed38c..efbdac98488 100644 --- a/site/docs/components/dropdown/examples.mdx +++ b/site/docs/components/dropdown/examples.mdx @@ -118,7 +118,7 @@ Wrap options in a `OptionGroup` component to group them together. Use the `label ## Complex options -Static content can be supplied as children to the `Option` component. This can be used to visually assist users, for example rendering the flag icons next to the name of countries as seen in this example. +Static content can be supplied as children to the `Option` component. This can be used to visually assist users, for example rendering the flag icons next to the name of countries or to provide descriptions for additional context as seen in this example. _Note_: The `valueToString` prop is needed here because the `value` prop is different to the displayed option text. ### Best practices @@ -159,12 +159,5 @@ Start adornments are typically used to describe the purpose of the field (e.g., In order to support advanced use-cases, like where the options are fetched from a server. The `value` prop passed to `Option` can be an object. When the `value` prop is an object, the `valueToString` prop must be provided to `ComboBox` to specify how to display the option in the button. - - - -## Secondary Label - -You can pass secondary label's content as children to the `Option` component to achieve this use-case. - diff --git a/site/src/examples/combo-box/ComplexOptions.tsx b/site/src/examples/combo-box/ComplexOptions.tsx index 12d8d5c8af4..343cc51cc96 100644 --- a/site/src/examples/combo-box/ComplexOptions.tsx +++ b/site/src/examples/combo-box/ComplexOptions.tsx @@ -1,23 +1,177 @@ -import { ChangeEvent, ReactElement, Suspense, useState } from "react"; -import { ComboBox, Option } from "@salt-ds/core"; -import { LargeCity, largestCities } from "./exampleData"; -import { LazyCountrySymbol } from "@salt-ds/countries"; +import { ChangeEvent, ReactElement, useState } from "react"; +import { Avatar, ComboBox, Option, StackLayout, Text } from "@salt-ds/core"; -const customMatchPattern = ( - input: { name: string; countryCode: string }, - filterValue: string -) => { - return ( - input.name.toLowerCase().includes(filterValue.toLowerCase()) || - filterValue === input.countryCode - ); +type Contact = { + firstName: string; + lastName: string; + email: string; + displayName: string; + id: string; }; -function OptionWithCountrySymbol({ value }: { value: LargeCity }) { +const contacts: Contact[] = [ + { + firstName: "Kamron", + lastName: "Marisa", + displayName: "Kamron Marisa", + email: "Kamron_Marisa20@example.net", + id: "26c1ff5f-78a9-4b2b-bbaf-cb5285ed4d79", + }, + { + firstName: "Matilda", + lastName: "Cathrine", + displayName: "Matilda Cathrine", + email: "Matilda_Cathrine32@example.com", + id: "45b8b157-2ab1-479f-9289-fe4a12e899c7", + }, + { + firstName: "Neva", + lastName: "Reuben", + displayName: "Neva Reuben", + email: "Neva.Reuben45@example.net", + id: "bbe8230e-0559-4fc0-8575-10329b56b3c5", + }, + { + firstName: "Laney", + lastName: "Hilton", + displayName: "Laney Hilton", + email: "Laney.Hilton65@example.com", + id: "f6b9885e-16e8-4fd5-a658-4207e168cdbb", + }, + { + firstName: "Madison", + lastName: "Rosa", + displayName: "Madison Rosa", + email: "Madison.Rosa34@example.org", + id: "405fe991-84e6-4d45-85a6-feb0c010106b", + }, + { + firstName: "Consuelo", + lastName: "Elijah", + displayName: "Consuelo Elijah", + email: "Consuelo.Elijah87@example.org", + id: "688fdb00-9f31-4a33-90cd-e82b71b51f4c", + }, + { + firstName: "Taurean", + lastName: "Blaise", + displayName: "Taurean Blaise", + email: "Taurean_Blaise@example.net", + id: "335662e3-0802-4f39-aaeb-78720aa75ca2", + }, + { + firstName: "Therese", + lastName: "Irma", + displayName: "Therese Irma", + email: "Therese.Irma20@example.com", + id: "c64c65d7-c193-48b1-b586-6ae4586d9d85", + }, + { + firstName: "Terry", + lastName: "Alaina", + displayName: "Terry Alaina", + email: "Terry_Alaina@example.com", + id: "f2f30596-f35e-4ef4-96ce-1d168a9c2db7", + }, + { + firstName: "Mike", + lastName: "Shanny", + displayName: "Mike Shanny", + email: "Mike_Shanny5@example.org", + id: "66162734-165a-4fb6-8701-b59497b799aa", + }, + { + firstName: "Adolf", + lastName: "Gerda", + displayName: "Adolf Gerda", + email: "Adolf.Gerda77@example.org", + id: "93d62117-07a6-4615-95ed-4591af3205eb", + }, + { + firstName: "Magali", + lastName: "Donna", + displayName: "Magali Donna", + email: "Magali_Donna@example.net", + id: "96b5c1a5-443b-44d3-bc39-f8ed746aa7b3", + }, + { + firstName: "Rhiannon", + lastName: "Emerald", + displayName: "Rhiannon Emerald", + email: "Rhiannon.Emerald36@example.com", + id: "53c6ac6a-56f3-4740-874f-57dceba46451", + }, + { + firstName: "William", + lastName: "Rowan", + displayName: "William Rowan", + email: "William.Rowan93@example.org", + id: "27def2df-eb35-4229-8492-e80171abbe3a", + }, + { + firstName: "Santiago", + lastName: "Maida", + displayName: "Santiago Maida", + email: "Santiago.Maida@example.net", + id: "455be580-8f45-4ff3-9437-78dd60c03279", + }, + { + firstName: "Marilyne", + lastName: "Candice", + displayName: "Marilyne Candice", + email: "Marilyne.Candice@example.net", + id: "539c291c-3c4a-4b47-b38d-1e946aa18a4e", + }, + { + firstName: "Norbert", + lastName: "Nikita", + displayName: "Norbert Nikita", + email: "Norbert.Nikita@example.org", + id: "b01bf4fb-33ca-4ebf-befc-1ba27b06833a", + }, + { + firstName: "Maximo", + lastName: "Carmel", + displayName: "Maximo Carmel", + email: "Maximo.Carmel@example.org", + id: "cf2de37d-9e72-4a8e-bfb4-db78e5a0b239", + }, + { + firstName: "Edward", + lastName: "Kyler", + displayName: "Edward Kyler", + email: "Edward.Kyler74@example.net", + id: "50ec7911-f90a-4f3b-b49d-33bbb7a012dd", + }, + { + firstName: "Judson", + lastName: "Carey", + displayName: "Judson Carey", + email: "Judson_Carey15@example.com", + id: "4c6513a6-c08a-40a7-a54c-414b4d29db9f", + }, +]; + +function ContactOption({ value }: { value: Contact }) { return ( -