UNPKG

monday-ui-react-core

Version:

Official monday.com UI resources for application development in React.js

1,043 lines (982 loc) 23.4 kB
import React, { useCallback, useMemo, useRef, useState } from "react"; import { StoryDescription } from "vibe-storybook-components"; import { createStoryMetaSettingsDecorator } from "../../../storybook"; import { multiInteractionTests, overviewPlaySuite } from "../__tests__/Dropdown.interactions"; import person1 from "./assets/person1.png"; import person3 from "./assets/person3.png"; import person2 from "./assets/person2.png"; import { OptionRenderer } from "./OptionRenderer.js"; import { Attach, Email } from "../../Icon/Icons"; import { Avatar, Box, Button, DialogContentContainer, Dropdown, Flex, Label, Modal, ModalContent } from "../../index"; import ModalExampleContent from "../../../storybook/patterns/dropdown-in-modals/ModalExampleContent"; import "./Dropdown.stories.scss"; import { fakeFetchUsers, generateItems } from "./Dropdown.stories.helpers"; const metaSettings = createStoryMetaSettingsDecorator({ component: Dropdown, enumPropNamesArray: ["size", "menuPosition", "menuPlacement"], actionPropsArray: [ "onMenuOpen", "onMenuClose", "onFocus", "onBlur", "onChange", "openMenuOnFocus", "onOptionRemove", "onOptionSelect", "onClear", "onInputChange", "onKeyDown" ] }); export default { title: "Inputs/Dropdown", component: Dropdown, argTypes: metaSettings.argTypes, decorators: metaSettings.decorators }; const dropdownTemplate = props => { const options = useMemo( () => [ { value: 1, label: "Option 1" }, { value: 2, label: "Option 2" }, { value: 3, label: "Option 3" } ], [] ); return ( <div style={{ height: "150px" }}> <Dropdown options={options} {...props} /> </div> ); }; export const Overview = { render: dropdownTemplate.bind({}), args: { placeholder: "Placeholder text here", className: "dropdown-stories-styles_spacing" }, parameters: { controls: { // TODO: remove exclusion when prop is removed in next major exclude: ["withReadOnlyStyle"] }, docs: { liveEdit: { isEnabled: false } } }, play: overviewPlaySuite }; export const Sizes = { render: () => ( <> <Dropdown placeholder="Small" size={Dropdown.sizes.SMALL} className="dropdown-stories-styles_spacing" /> <Dropdown placeholder="Medium" size={Dropdown.sizes.MEDIUM} className="dropdown-stories-styles_spacing" /> <Dropdown placeholder="Large" size={Dropdown.sizes.LARGE} className="dropdown-stories-styles_spacing" /> </> ) }; export const Disabled = { render: () => { const options = useMemo( () => [ { value: "1", label: "Option 1" }, { value: "2", label: "Option 2" }, { value: "3", label: "Option 3" } ], [] ); return ( <Flex direction={Flex.directions.ROW}> <Dropdown defaultValue={[options[0]]} options={options} disabled className="dropdown-stories-styles_spacing" /> <Dropdown multi defaultValue={[options[0], options[1]]} options={options} disabled className="dropdown-stories-styles_spacing" /> </Flex> ); } }; export const Readonly = { render: () => { const options = useMemo( () => [ { value: "1", label: "Option 1" }, { value: "2", label: "Option 2" }, { value: "3", label: "Option 3" } ], [] ); return ( <Flex direction={Flex.directions.ROW}> <Dropdown defaultValue={[options[0]]} options={options} readOnly className="dropdown-stories-styles_spacing" /> <Dropdown multi defaultValue={[options[0], options[1]]} options={options} readOnly className="dropdown-stories-styles_spacing" /> </Flex> ); } }; export const Rtl = { render: () => ( <> <Dropdown placeholder="Left to right (default)" className="dropdown-stories-styles_spacing" /> <Dropdown placeholder="מימין לשמאל" className="dropdown-stories-styles_spacing" rtl /> </> ), name: "RTL" }; export const MultiChoiceWithDifferentStates = { render: () => { const options = useMemo( () => [ { value: "Rotem", label: "Rotem Dekel" }, { value: "Hadas", label: "Hadas Farhi" }, { value: "Netta", label: "Netta Muller" }, { value: "Dor", label: "Dor Yehuda" } ], [] ); return ( <Flex wrap gap={Flex.gaps.MEDIUM}> <StoryDescription description="Single line" vertical> <div style={{ width: "400px" }} > <Dropdown placeholder="Single line multi state" defaultValue={[options[0]]} options={options} multi className="dropdown-stories-styles_with-chips" /> </div> </StoryDescription> <StoryDescription description="Multiple lines" vertical> <div style={{ width: "400px" }} > <Dropdown placeholder="Multiple line multi state" defaultValue={[options[0]]} options={options} multi multiline className="dropdown-stories-styles_with-chips" /> </div> </StoryDescription> <StoryDescription description="Mandatory default values" vertical headerStyle={{ width: 190 }} > <div style={{ width: "400px" }} > <Dropdown defaultValue={[options[0]]} options={options} multi multiline className="dropdown-stories-styles_with-chips" withMandatoryDefaultOptions /> </div> </StoryDescription> <StoryDescription description="Hidden options list" vertical headerStyle={{ width: 190 }} > <div style={{ width: "400px" }} > <Dropdown defaultValue={[...options]} options={options} multi className="dropdown-stories-styles_with-chips" /> </div> </StoryDescription> </Flex> ); }, play: multiInteractionTests, parameters: { docs: { liveEdit: { scope: { StoryDescription } } } }, name: "Multi-choice with different states" }; export const AsyncDropdown = { render: () => { const fetchUserOptions = async searchTerm => { try { const response = await fakeFetchUsers(); const users = await response.json(); return users .filter(user => user.name.toLowerCase().includes(searchTerm.toLowerCase())) .map(user => ({ label: user.name, value: user.id })); } catch (error) { console.error("Error fetching user data:", error); } return []; }; return ( <div style={{ width: "400px" }} > <Dropdown asyncOptions={fetchUserOptions} placeholder="Async options" cacheOptions defaultOptions /> </div> ); }, parameters: { docs: { liveEdit: { scope: { fakeFetchUsers } } } } }; export const DropdownWithAvatar = { render: () => { const optionsAvatar = useMemo( () => [ { value: "Rotem", label: "Rotem Dekel", leftAvatar: person1 }, { value: "Hadas", label: "Hadas Farhi", leftAvatar: person2 }, { value: "Netta", label: "Netta Muller", leftAvatar: person3 } ], [] ); return ( <Flex gap={Flex.gaps.SMALL}> <StoryDescription vertical description="Single value"> <div> <Dropdown defaultValue={[optionsAvatar[0]]} options={optionsAvatar} className="dropdown-stories-styles_with-chips" /> </div> </StoryDescription> <StoryDescription vertical description="Multiple values"> <div> <Dropdown defaultValue={[optionsAvatar[0]]} options={optionsAvatar} multi multiline className="dropdown-stories-styles_with-chips" /> </div> </StoryDescription> </Flex> ); }, parameters: { docs: { liveEdit: { scope: { person1, person2, person3, StoryDescription } } } }, name: "Dropdown with avatar" }; export const DropdownWithIcon = { render: () => { const optionsIcons = useMemo( () => [ { value: "email", label: "Email", leftIcon: Email }, { value: "attach", label: "Attach", leftIcon: Attach } ], [] ); return ( <Flex gap={Flex.gaps.SMALL}> <StoryDescription vertical description="Single value"> <div> <Dropdown defaultValue={[optionsIcons[0]]} options={optionsIcons} className="dropdown-stories-styles_with-chips" /> </div> </StoryDescription> <StoryDescription vertical description="Multiple values"> <div> <Dropdown defaultValue={[optionsIcons[0]]} options={optionsIcons} multi multiline className="dropdown-stories-styles_with-chips" /> </div> </StoryDescription> </Flex> ); }, parameters: { docs: { liveEdit: { scope: { Email, Attach, StoryDescription } } } }, name: "Dropdown with icon" }; export const DropdownWithChipColors = { render: () => { const optionsWithChipColors = useMemo( () => [ { value: "Rotem", label: "Rotem Dekel", chipColor: Dropdown.chipColors.NEGATIVE }, { value: "Hadas", label: "Hadas Farhi", chipColor: Dropdown.chipColors.POSITIVE }, { value: "Netta", label: "Netta Muller", chipColor: Dropdown.chipColors.PRIMARY } ], [] ); return ( <StoryDescription vertical> <div> <Dropdown defaultValue={[optionsWithChipColors[0]]} options={optionsWithChipColors} multi multiline className="dropdown-stories-styles_with-chips" /> </div> </StoryDescription> ); }, parameters: { docs: { liveEdit: { scope: { StoryDescription } } } }, name: "Dropdown with chip colors" }; export const DropdownWithTooltipsOnItems = { render: () => { const optionsWithTooltips = useMemo( () => [ { value: "Option 1", label: "Option 1", tooltipProps: { content: "Description for option 1" } }, { value: "Option 2", label: "Option 2", tooltipProps: { content: "Description for option 2" } } ], [] ); return ( <StoryDescription vertical> <div> <Dropdown placeholder={"Placeholder text here"} options={optionsWithTooltips} className="dropdown-stories-styles_with-chips" /> </div> </StoryDescription> ); }, parameters: { docs: { liveEdit: { scope: { StoryDescription } } } }, name: "Dropdown with tooltips on items" }; export const DropdownWithChips = { render: () => { const options = useMemo( () => [ { value: "Dor Yehuda", label: "Hadas Farhi", src: person1, type: Avatar.types.IMG, size: Avatar.sizes.SMALL, name: "Dor Yehuda", position: "(Full Stack Developer)" }, { value: "No", label: "Rotem Dekel", src: person3, type: Avatar.types.IMG, size: Avatar.sizes.SMALL, name: "Rotem Dekel", position: "(Product Designer)" }, { value: "Yes", label: "Netta Muller", src: person2, type: Avatar.types.IMG, size: Avatar.sizes.SMALL, name: "Netta Muller", position: "(Brand Designer)" } ], [] ); return ( <Dropdown defaultValue={[options[0]]} options={options} multi placeholder="Dropdown with chips" optionRenderer={OptionRenderer} className="dropdown-stories-styles_with-chips" /> ); }, parameters: { docs: { liveEdit: { scope: { person1, person2, person3, OptionRenderer } } } }, name: "Dropdown with chips" }; export const SearchableDropdown = { render: () => { const [searchValue, setSearchValue] = useState(""); const allOptions = useMemo( () => [ { value: "Red", label: "Red" }, { value: "Orange", label: "Orange" }, { value: "Yellow", label: "Yellow" }, { value: "Green", label: "Green" }, { value: "Blue", label: "Blue" }, { value: "Indigo", label: "Indigo" }, { value: "Violet", label: "Violet" } ], [] ); const options = useMemo(() => { if (!searchValue) return allOptions; return allOptions.filter(option => option.label.toLowerCase().includes(searchValue.toLowerCase())); }, [allOptions, searchValue]); const onInputChange = value => setSearchValue(value); return ( <Dropdown options={options} multi placeholder="Select colors" className="dropdown-stories-styles_with-chips" onInputChange={onInputChange} /> ); }, name: "Searchable dropdown" }; export const DropdownWithLabels = { render: () => { const labelRenderer = useCallback(({ label, color }) => { return <Label text={label} color={color} isAnimationDisabled />; }, []); const options = useMemo( () => [ { value: "success", label: "Success", color: Label.colors.POSITIVE }, { value: "failed", label: "Failed", color: Label.colors.NEGATIVE }, { value: "in progress", label: "In progress" } ], [] ); return ( <Dropdown placeholder="Placeholder text here" options={options} defaultValue={[options[0]]} className="dropdown-stories-styles_big-spacing" optionRenderer={labelRenderer} valueRenderer={labelRenderer} /> ); }, name: "Dropdown with labels" }; export const VirtualizedDropdown = { render: () => { const options = useMemo(() => generateItems(300), []); return <Dropdown options={options} isVirtualized className="dropdown-stories-styles_with-chips" />; }, name: "Virtualized dropdown" }; export const DropdownInsideAForm = { render: () => { const options = useMemo( () => [ { value: "Sometimes", label: "Sometimes" }, { value: "No", label: "No" }, { value: "Yes", label: "Yes" } ], [] ); return ( <div> <h5 className="dropdown-stories-styles_title">Are you usually a Dark mode person?</h5> <Dropdown defaultValue={[options[0]]} placeholder="Placeholder text here" options={options} className="dropdown-stories-styles_big-spacing" /> </div> ); }, name: "Dropdown inside a form" }; export const DropdownWithGroups = { render: () => { const options = useMemo( () => [ { label: "Group 1", options: [ { value: "1", label: "Option 1" }, { value: "2", label: "Option 2" } ] }, { label: "Group 2", options: [ { value: "3", label: "Option 3" }, { value: "4", label: "Option 4" } ] } ], [] ); const optionsWithoutGroupLabel = useMemo( () => [ { options: [ { value: "1", label: "Option 1" }, { value: "2", label: "Option 2" } ] }, { options: [ { value: "3", label: "Option 3" }, { value: "4", label: "Option 4" } ] } ], [] ); return ( <Flex gap={Flex.gaps.LARGE}> <div> <Dropdown placeholder="Groups with group title" options={options} className="dropdown-stories-styles_big-spacing" /> </div> <div> <Dropdown placeholder="Groups with group divider" options={optionsWithoutGroupLabel} withGroupDivider className="dropdown-stories-styles_big-spacing" /> </div> </Flex> ); }, name: "Dropdown with groups" }; export const DropdownInsidePopover = { render: () => { const options = useMemo( () => [ { value: "1", label: "Option 1" }, { value: "2", label: "Option 2" }, { value: "3", label: "Option 3" }, { value: "4", label: "Option 4" }, { value: "5", label: "Option 5" }, { value: "6", label: "Option 6" }, { value: "7", label: "Option 7" }, { value: "8", label: "Option 8" }, { value: "9", label: "Option 9" }, { value: "10", label: "Option 10" }, { value: "11", label: "Option 11" }, { value: "12", label: "Option 12" }, { value: "13", label: "Option 13" }, { value: "14", label: "Option 14" }, { value: "15", label: "Option 15" } ], [] ); const [show, setShow] = useState(false); const closeModal = useCallback(() => { setShow(false); }, [setShow]); const dialogStyle = { width: "350px", height: "200px", overflow: "auto" }; return ( <Flex gap={Flex.gaps.LARGE}> <DialogContentContainer style={dialogStyle}> <ModalExampleContent /> <Box marginTop={Box.marginTops.MEDIUM} marginBottom={Box.marginBottoms.XXL}> <Dropdown placeholder="Dropdown inside DialogContentContainer" options={options} menuPosition={Dropdown.menuPositions.FIXED} /> </Box> </DialogContentContainer> <div> <Button onClick={() => setShow(true)}>Open Modal</Button> <Modal title="Modal with dropdown" show={show} onClose={closeModal}> <ModalContent> <Dropdown placeholder="Dropdown" options={options} menuPosition={Dropdown.menuPositions.FIXED} /> </ModalContent> </Modal> </div> </Flex> ); }, parameters: { docs: { liveEdit: { scope: { ModalExampleContent } } } }, name: "Dropdown inside popover" }; export const DropdownWithLoading = { render: () => { const [isLoading, setIsLoading] = useState(false); const options = useMemo( () => [ { value: "1", label: "Option 1" }, { value: "2", label: "Option 2" }, { value: "3", label: "Option 3" } ], [] ); const loadingOnInputChange = useCallback(() => { setIsLoading(true); setTimeout(() => { setIsLoading(false); }, 1000); }, []); return ( <Dropdown placeholder={"Type to start loading"} options={options} isLoading={isLoading} loadingMessage={() => "Loading options..."} className="dropdown-stories-styles_big-spacing" onInputChange={loadingOnInputChange} /> ); }, name: "Dropdown with loading" }; export const DropdownWithRef = { render: () => { const ref = useRef(); const options = useMemo( () => [ { value: "1", label: "Option 1" }, { value: "2", label: "Option 2" }, { value: "3", label: "Option 3" } ], [] ); const focusDropdownInput = useCallback(() => { console.log("Dropdown ref.current = ", ref.current); ref.current.select.focus(); }, []); return ( <Flex direction={Flex.directions.ROW}> <Dropdown placeholder="Dropdown with ref" options={options} ref={ref} className="dropdown-stories-styles_spacing" /> <Button onClick={focusDropdownInput} className="dropdown-stories-styles_button"> Focus dropdown input </Button> </Flex> ); }, name: "Dropdown with ref" }; export const DropdownValueSelection = { render: () => { const options = useMemo( () => [ { value: "1", label: "Option 1" }, { value: "2", label: "Option 2" }, { value: "3", label: "Option 3" } ], [] ); return ( <Flex gap={Flex.gaps.LARGE}> <Dropdown placeholder="Tab selects value" options={options} className="dropdown-stories-styles_big-spacing" /> <Dropdown placeholder="Tab does not select value" options={options} tabSelectsValue={false} className="dropdown-stories-styles_big-spacing" /> </Flex> ); }, name: "Dropdown value selection" };