From 6da9fb447f6c59679c6fa087b8ecfb11fadc59af Mon Sep 17 00:00:00 2001 From: Steven Countryman Date: Mon, 22 Apr 2024 17:42:43 -0700 Subject: [PATCH 01/12] Adding storybook entries for Listbox --- .../Listbox/ListboxDefault.stories.tsx | 31 +++++ .../stories/Listbox/ListboxDescription.md | 1 + .../Listbox/ListboxFiltering.stories.tsx | 63 ++++++++++ .../Listbox/ListboxInDialog.stories.tsx | 117 ++++++++++++++++++ .../stories/Listbox/index.stories.tsx | 23 ++++ 5 files changed, 235 insertions(+) create mode 100644 packages/react-components/react-combobox/stories/Listbox/ListboxDefault.stories.tsx create mode 100644 packages/react-components/react-combobox/stories/Listbox/ListboxDescription.md create mode 100644 packages/react-components/react-combobox/stories/Listbox/ListboxFiltering.stories.tsx create mode 100644 packages/react-components/react-combobox/stories/Listbox/ListboxInDialog.stories.tsx create mode 100644 packages/react-components/react-combobox/stories/Listbox/index.stories.tsx diff --git a/packages/react-components/react-combobox/stories/Listbox/ListboxDefault.stories.tsx b/packages/react-components/react-combobox/stories/Listbox/ListboxDefault.stories.tsx new file mode 100644 index 0000000000000..809b365fe5568 --- /dev/null +++ b/packages/react-components/react-combobox/stories/Listbox/ListboxDefault.stories.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; +import { Listbox, makeStyles, Option, shorthands } from '@fluentui/react-components'; +import type { ListboxProps } from '@fluentui/react-components'; + +const useStyles = makeStyles({ + root: { + // Stack the label above the field with a gap + display: 'grid', + gridTemplateRows: 'repeat(1fr)', + justifyItems: 'start', + ...shorthands.gap('2px'), + maxWidth: '400px', + }, +}); + +export const Default = (props: Partial) => { + const options = ['Cat', 'Dog', 'Ferret', 'Fish', 'Hamster', 'Snake']; + + const styles = useStyles(); + return ( +
+ + {options.map(option => ( + + ))} + +
+ ); +}; diff --git a/packages/react-components/react-combobox/stories/Listbox/ListboxDescription.md b/packages/react-components/react-combobox/stories/Listbox/ListboxDescription.md new file mode 100644 index 0000000000000..db39dca2c9a77 --- /dev/null +++ b/packages/react-components/react-combobox/stories/Listbox/ListboxDescription.md @@ -0,0 +1 @@ +A listbox (`Listbox`) provides people a way to select an option from an inline list. diff --git a/packages/react-components/react-combobox/stories/Listbox/ListboxFiltering.stories.tsx b/packages/react-components/react-combobox/stories/Listbox/ListboxFiltering.stories.tsx new file mode 100644 index 0000000000000..2e6ce145a868e --- /dev/null +++ b/packages/react-components/react-combobox/stories/Listbox/ListboxFiltering.stories.tsx @@ -0,0 +1,63 @@ +import * as React from 'react'; +import { Button, Input, Listbox, makeStyles, mergeClasses, Option, shorthands } from '@fluentui/react-components'; +import type { ListboxProps } from '@fluentui/react-components'; +import { DismissRegular } from '@fluentui/react-icons'; + +const useStyles = makeStyles({ + root: { + // Stack the label above the field with a gap + display: 'grid', + gridTemplateRows: 'repeat(1fr)', + justifyItems: 'start', + ...shorthands.gap('2px'), + width: '200px', + }, + input: { + width: '100%', + marginBottom: '8px', + }, + listbox: { + width: '100%', + }, +}); + +export const Filtering = (props: Partial) => { + const options = ['Cat', 'Dog', 'Ferret', 'Fish', 'Hamster', 'Snake']; + + const [filter, setFilter] = React.useState(''); + + const filteredOptions = React.useMemo(() => { + return options.filter( + o => o.toLowerCase().includes(filter.toLowerCase()) || filter.toLowerCase().includes(o.toLowerCase()), + ); + }, [filter]); + + const styles = useStyles(); + return ( +
+ setFilter(data.value)} + contentAfter={ + filter.length > 0 ? ( +
+ ); +}; diff --git a/packages/react-components/react-combobox/stories/Listbox/ListboxInDialog.stories.tsx b/packages/react-components/react-combobox/stories/Listbox/ListboxInDialog.stories.tsx new file mode 100644 index 0000000000000..697015e467826 --- /dev/null +++ b/packages/react-components/react-combobox/stories/Listbox/ListboxInDialog.stories.tsx @@ -0,0 +1,117 @@ +import * as React from 'react'; +import { + Button, + Dialog, + DialogBody, + DialogContent, + DialogSurface, + DialogTrigger, + Input, + Listbox, + makeStyles, + MenuButton, + mergeClasses, + Option, + shorthands, +} from '@fluentui/react-components'; +import type { ListboxProps } from '@fluentui/react-components'; +import { DismissRegular } from '@fluentui/react-icons'; + +const useStyles = makeStyles({ + root: { + // Stack the label above the field with a gap + display: 'grid', + gridTemplateRows: 'repeat(1fr)', + justifyItems: 'start', + ...shorthands.gap('2px'), + maxWidth: '400px', + }, + menuButton: { + justifyContent: 'space-between', + }, + input: { + width: '100%', + marginBottom: '8px', + }, + listbox: { + width: '100%', + }, +}); + +export const InDialog = (props: Partial) => { + const inputRef = React.useRef(null); + const options = ['Cat', 'Dog', 'Ferret', 'Fish', 'Hamster', 'Snake']; + const [selectedOption, setSelectedOption] = React.useState(props.selectedOptions?.[0]); + + const [dialogOpen, setDialogOpen] = React.useState(false); + const [filter, setFilter] = React.useState(''); + + const filteredOptions = React.useMemo(() => { + return options.filter( + o => o.toLowerCase().includes(filter.toLowerCase()) || filter.toLowerCase().includes(o.toLowerCase()), + ); + }, [filter]); + + React.useEffect(() => { + setSelectedOption(props.selectedOptions?.[0]); + }, [props.selectedOptions]); + + React.useEffect(() => { + if (dialogOpen) { + inputRef.current?.focus(); + } + }, [dialogOpen]); + + const styles = useStyles(); + return ( +
+ setDialogOpen(data.open)}> + + setDialogOpen(!dialogOpen)}> + {selectedOption ?? 'Select a pet'} + + + + + + setFilter(data.value)} + contentAfter={ + filter.length > 0 ? ( + +
+ ); +}; diff --git a/packages/react-components/react-combobox/stories/Listbox/index.stories.tsx b/packages/react-components/react-combobox/stories/Listbox/index.stories.tsx new file mode 100644 index 0000000000000..3a26b24a37922 --- /dev/null +++ b/packages/react-components/react-combobox/stories/Listbox/index.stories.tsx @@ -0,0 +1,23 @@ +import { Meta } from '@storybook/react'; +import { Listbox, Option } from '@fluentui/react-components'; + +import descriptionMd from './ListboxDescription.md'; + +export { Default } from './ListboxDefault.stories'; +export { Filtering } from './ListboxFiltering.stories'; +export { InDialog } from './ListboxInDialog.stories'; + +export default { + title: 'Components/Listbox', + component: Listbox, + subcomponents: { + Option, + }, + parameters: { + docs: { + description: { + component: [descriptionMd].join('\n'), + }, + }, + }, +} as Meta; From d89851352eb0674a00275a64e2a878ba5c2838e8 Mon Sep 17 00:00:00 2001 From: Steven Countryman Date: Mon, 22 Apr 2024 17:44:23 -0700 Subject: [PATCH 02/12] simplifying filtered options --- .../stories/Listbox/ListboxFiltering.stories.tsx | 4 +--- .../stories/Listbox/ListboxInDialog.stories.tsx | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/react-components/react-combobox/stories/Listbox/ListboxFiltering.stories.tsx b/packages/react-components/react-combobox/stories/Listbox/ListboxFiltering.stories.tsx index 2e6ce145a868e..bb4898971b135 100644 --- a/packages/react-components/react-combobox/stories/Listbox/ListboxFiltering.stories.tsx +++ b/packages/react-components/react-combobox/stories/Listbox/ListboxFiltering.stories.tsx @@ -22,12 +22,10 @@ const useStyles = makeStyles({ }); export const Filtering = (props: Partial) => { - const options = ['Cat', 'Dog', 'Ferret', 'Fish', 'Hamster', 'Snake']; - const [filter, setFilter] = React.useState(''); const filteredOptions = React.useMemo(() => { - return options.filter( + return ['Cat', 'Dog', 'Ferret', 'Fish', 'Hamster', 'Snake'].filter( o => o.toLowerCase().includes(filter.toLowerCase()) || filter.toLowerCase().includes(o.toLowerCase()), ); }, [filter]); diff --git a/packages/react-components/react-combobox/stories/Listbox/ListboxInDialog.stories.tsx b/packages/react-components/react-combobox/stories/Listbox/ListboxInDialog.stories.tsx index 697015e467826..e18471d692848 100644 --- a/packages/react-components/react-combobox/stories/Listbox/ListboxInDialog.stories.tsx +++ b/packages/react-components/react-combobox/stories/Listbox/ListboxInDialog.stories.tsx @@ -40,14 +40,13 @@ const useStyles = makeStyles({ export const InDialog = (props: Partial) => { const inputRef = React.useRef(null); - const options = ['Cat', 'Dog', 'Ferret', 'Fish', 'Hamster', 'Snake']; const [selectedOption, setSelectedOption] = React.useState(props.selectedOptions?.[0]); const [dialogOpen, setDialogOpen] = React.useState(false); const [filter, setFilter] = React.useState(''); const filteredOptions = React.useMemo(() => { - return options.filter( + return ['Cat', 'Dog', 'Ferret', 'Fish', 'Hamster', 'Snake'].filter( o => o.toLowerCase().includes(filter.toLowerCase()) || filter.toLowerCase().includes(o.toLowerCase()), ); }, [filter]); From 01bd938820257bd4e21a7a78999bfcee2c7b630d Mon Sep 17 00:00:00 2001 From: Steven Countryman Date: Mon, 22 Apr 2024 18:04:00 -0700 Subject: [PATCH 03/12] Improving stories --- .../Listbox/ListboxDefault.stories.tsx | 2 +- .../Listbox/ListboxFiltering.stories.tsx | 3 +- .../Listbox/ListboxInDialog.stories.tsx | 46 +++++++++++++++---- .../Listbox/ListboxMultiselect.stories.tsx | 31 +++++++++++++ .../stories/Listbox/index.stories.tsx | 1 + 5 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 packages/react-components/react-combobox/stories/Listbox/ListboxMultiselect.stories.tsx diff --git a/packages/react-components/react-combobox/stories/Listbox/ListboxDefault.stories.tsx b/packages/react-components/react-combobox/stories/Listbox/ListboxDefault.stories.tsx index 809b365fe5568..01fd9f5b6f808 100644 --- a/packages/react-components/react-combobox/stories/Listbox/ListboxDefault.stories.tsx +++ b/packages/react-components/react-combobox/stories/Listbox/ListboxDefault.stories.tsx @@ -19,7 +19,7 @@ export const Default = (props: Partial) => { const styles = useStyles(); return (
- + {options.map(option => (