Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | import { Story } from '@storybook/react'; import React, { useState } from 'react'; import MultiSelect, { MultiSelectProps } from '../components/MultiSelect'; import Button from '../components/Button/Button'; import { Popover, Typography } from '@mui/material'; export default { title: 'MultiSelect', component: MultiSelect, }; const Template: Story<MultiSelectProps<number, string>> = (args) => { const [selectedOptions, setSelectedOptions] = useState<number[]>([]); const handleAdd = (id: number) => { const newOptions = [...selectedOptions]; newOptions.push(id); setSelectedOptions(newOptions); }; const handleRemove = (id: number) => { const newOptions = [...selectedOptions]; const index = newOptions.findIndex((value) => id === value); if (index >= 0) { newOptions.splice(index, 1); setSelectedOptions(newOptions); } }; const onPillClick = (id: number) => { window.alert(`You clicked the ${defaultOptions.get(id)} pill!`); }; const renderString = (v: string) => v; return ( <MultiSelect {...args} selectedOptions={selectedOptions} onAdd={handleAdd} onRemove={handleRemove} onPillClick={onPillClick} valueRenderer={renderString} /> ); }; const WithParentOptionsVisibilityControlTemplate: Story<MultiSelectProps<number, string>> = (args) => { const [selectedOptions, setSelectedOptions] = useState<number[]>([]); const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); const [optionsVisible, setOptionsVisible] = useState(false); const [shouldClose, setShouldClose] = useState(false); const toggleMultiSelectVisible = (event?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => { if (event) { setAnchorEl(event.currentTarget); } }; const hideOptionsOrClose = () => { if (shouldClose) { setAnchorEl(null); } }; const onMultiSelectBlur = () => { /* I admit this is less than ideal. I was unable to find another solution (and I spent way to long on it already). Because there are two events that fire in immediate succession (and in this order), the MultiSelect onBlur and the Popover onClick, we need to setOptionsVisible(false) and setShouldClose(false) _after_ the Popover's onClickCapture executes. I chose 100 ms arbitrarily based on the desire to still be able to double click away from the popover to close the options and subsequently close the popover. Choosing a number like 1000ms, for example, makes the double click not work unless 1000ms has elapsed between the two clicks. */ const delaySet = async () => { await new Promise((resolve) => { setTimeout(resolve, 100); }); setShouldClose(false); setOptionsVisible(false); }; void delaySet(); }; const onMultiSelectFocus = () => { setOptionsVisible(true); setShouldClose(false); }; const handleAdd = (id: number) => { const newOptions = [...selectedOptions]; newOptions.push(id); setSelectedOptions(newOptions); }; const handleRemove = (id: number) => { const newOptions = [...selectedOptions]; const index = newOptions.findIndex((value) => id === value); if (index >= 0) { newOptions.splice(index, 1); setSelectedOptions(newOptions); } }; const onPillClick = (id: number) => { window.alert(`You clicked the ${defaultOptions.get(id)} pill!`); }; const renderString = (v: string) => v; const renderMultiSelect = () => ( <MultiSelect {...args} selectedOptions={selectedOptions} onAdd={handleAdd} onRemove={handleRemove} onPillClick={onPillClick} valueRenderer={renderString} optionsVisible={optionsVisible} onBlur={onMultiSelectBlur} onFocus={onMultiSelectFocus} /> ); return ( <> <Typography sx={{ marginBottom: '20px' }}> The purpose of this story is to show that clicking away from the card will hide the options first (if they are open), and a second click away will hide the multi select </Typography> <Button onClick={toggleMultiSelectVisible} label={Boolean(anchorEl) ? 'Hide MultiSelect' : 'Show MultiSelect'} /> <Popover id='filter-popover' open={Boolean(anchorEl)} onClose={hideOptionsOrClose} anchorEl={anchorEl} onClickCapture={(event) => { // If the captured event is not for the backdrop, do nothing const eventIsBackdropClick = Array.from((event.target as HTMLElement).classList.values()).some( (targetClass: string) => targetClass.toLowerCase().includes('backdrop') ); if (!eventIsBackdropClick) { return; } // Since two events are fired when the options are opened, the MultiSelect onBlur and // the Popover onClick (of the backdrop, which prompts the onClose event), we need to stop event propagation // if the options are visible. Otherwise, we setShouldClose(true) so that when the onClose handler fires // it knows that it should close if (optionsVisible) { event.stopPropagation(); } else { setShouldClose(true); } }} anchorOrigin={{ vertical: 'bottom', horizontal: 'left', }} transformOrigin={{ vertical: 'top', horizontal: 'left', }} sx={{ '& .MuiPaper-root': { borderRadius: '8px', padding: '10px', overflow: 'visible', width: '480px', }, }} > {renderMultiSelect()} </Popover> </> ); }; export const Default = Template.bind({}); export const WithParentOptionsVisibilityControl = WithParentOptionsVisibilityControlTemplate.bind({}); const defaultOptions = new Map<number, string>([ [1, 'eggs'], [2, 'potatoes'], [3, 'spam'], [4, 'bacon'], [5, 'beans'], [6, 'spam also'], [7, 'more spam'], ]); Default.args = { label: 'Breakfast order:', tooltipTitle: 'Please make your selection below...', helperText: 'Maybe some nice spam?', missingValuePlaceholder: '???', placeHolder: 'Select...', options: defaultOptions, disabled: false, }; WithParentOptionsVisibilityControl.args = { label: 'Breakfast order:', tooltipTitle: 'Please make your selection below...', helperText: 'Maybe some nice spam?', missingValuePlaceholder: '???', placeHolder: 'Select...', options: defaultOptions, disabled: false, }; |