-
Notifications
You must be signed in to change notification settings - Fork 157
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Forwarding ref in Select components (#492)
* Split Select components * Forward refs of Select and MultiSelect
- Loading branch information
Showing
50 changed files
with
493 additions
and
385 deletions.
There are no files selected for viewing
Binary file removed
BIN
-2.95 KB
.../.loki/reference/chrome_iphone7_Containers_Accordion_Accordion_Item_Default.png
Binary file not shown.
Binary file removed
BIN
-3.5 KB
....loki/reference/chrome_iphone7_Containers_Accordion_Accordion_Item_Disabled.png
Binary file not shown.
Binary file removed
BIN
-4.39 KB
.../.loki/reference/chrome_iphone7_Containers_Accordion_Accordion_Item_Enabled.png
Binary file not shown.
Binary file removed
BIN
-6.44 KB
....loki/reference/chrome_iphone7_Containers_Accordion_Accordion_Item_Expanded.png
Binary file not shown.
Binary file removed
BIN
-6.08 KB
...reference/chrome_iphone7_Containers_Accordion_Accordion_Item_Noncollapsible.png
Binary file not shown.
Binary file removed
BIN
-3.69 KB
.../reference/chrome_iphone7_Containers_Accordion_Accordion_Item_Without_Title.png
Binary file not shown.
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
Binary file removed
BIN
-177 KB
packages/fuselage/.loki/reference/chrome_iphone7_Message_Default.png
Binary file not shown.
Binary file removed
BIN
-4.4 KB
packages/fuselage/.loki/reference/chrome_iphone7_Message_Toolbox_Default.png
Binary file not shown.
Binary file removed
BIN
-4.4 KB
...es/fuselage/.loki/reference/chrome_iphone7_Messages_Message_Toolbox_Default.png
Binary file not shown.
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
Binary file removed
BIN
-71.9 KB
packages/fuselage/.loki/reference/chrome_laptop_Message_Default.png
Diff not rendered.
Binary file removed
BIN
-2.29 KB
packages/fuselage/.loki/reference/chrome_laptop_Message_Toolbox_Default.png
Diff not rendered.
Binary file removed
BIN
-2.29 KB
...ges/fuselage/.loki/reference/chrome_laptop_Messages_Message_Toolbox_Default.png
Diff not rendered.
199 changes: 199 additions & 0 deletions
199
packages/fuselage/src/components/MultiSelect/MultiSelect.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
import { | ||
useMergedRefs, | ||
useMutableCallback, | ||
useResizeObserver, | ||
} from '@rocket.chat/fuselage-hooks'; | ||
import React, { | ||
useState, | ||
useRef, | ||
useEffect, | ||
useCallback, | ||
memo, | ||
forwardRef, | ||
} from 'react'; | ||
|
||
import { AnimatedVisibility, Box, Flex, Position } from '../Box'; | ||
import Chip from '../Chip'; | ||
import { Icon } from '../Icon'; | ||
import { InputBox } from '../InputBox'; | ||
import Margins from '../Margins'; | ||
import { Options, CheckOption, useCursor } from '../Options'; | ||
import { Focus, Addon } from '../Select/Select'; | ||
|
||
const SelectedOptions = memo((props) => <Chip {...props} />); | ||
|
||
const prevent = (e) => { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
e.nativeEvent.stopImmediatePropagation(); | ||
}; | ||
|
||
export const MultiSelect = forwardRef( | ||
( | ||
{ | ||
value, | ||
filter, | ||
options = [], | ||
error, | ||
disabled, | ||
anchor: Anchor = Focus, | ||
onChange = () => {}, | ||
getLabel = ([, label] = []) => label, | ||
getValue = ([value]) => value, | ||
placeholder, | ||
renderOptions: _Options = Options, | ||
...props | ||
}, | ||
ref | ||
) => { | ||
const [internalValue, setInternalValue] = useState(value || []); | ||
|
||
const currentValue = value !== undefined ? value : internalValue; | ||
const option = options.find((option) => getValue(option) === currentValue); | ||
const index = options.indexOf(option); | ||
|
||
const internalChanged = ([value]) => { | ||
if (currentValue.includes(value)) { | ||
const newValue = currentValue.filter((item) => item !== value); | ||
setInternalValue(newValue); | ||
return onChange(newValue); | ||
} | ||
const newValue = [...currentValue, value]; | ||
setInternalValue(newValue); | ||
return onChange(newValue); | ||
}; | ||
|
||
const mapOptions = ([value, label]) => { | ||
if (currentValue.includes(value)) { | ||
return [value, label, true]; | ||
} | ||
return [value, label]; | ||
}; | ||
const applyFilter = ([, option]) => | ||
!filter || ~option.toLowerCase().indexOf(filter.toLowerCase()); | ||
const filteredOptions = options.filter(applyFilter).map(mapOptions); | ||
const [cursor, handleKeyDown, handleKeyUp, reset, [visible, hide, show]] = | ||
useCursor(index, filteredOptions, internalChanged); | ||
|
||
useEffect(reset, [filter]); | ||
|
||
const innerRef = useRef(); | ||
const anchorRef = useMergedRefs(ref, innerRef); | ||
|
||
const { ref: containerRef, borderBoxSize } = useResizeObserver(); | ||
|
||
return ( | ||
<Box | ||
is='div' | ||
rcx-select | ||
className={[error && 'invalid', disabled && 'disabled']} | ||
ref={containerRef} | ||
onClick={useMutableCallback(() => | ||
visible === AnimatedVisibility.VISIBLE | ||
? hide() | ||
: innerRef.current.focus() & show() | ||
)} | ||
disabled={disabled} | ||
{...props} | ||
> | ||
<Flex.Item grow={1}> | ||
<Margins inline='x4'> | ||
<Flex.Container> | ||
<Box is='div'> | ||
<Box | ||
is='div' | ||
display='flex' | ||
alignItems='center' | ||
flexWrap='wrap' | ||
margin='-x8' | ||
role='listbox' | ||
> | ||
<Margins all='x4'> | ||
<Anchor | ||
disabled={disabled} | ||
ref={anchorRef} | ||
aria-haspopup='listbox' | ||
onClick={show} | ||
onBlur={hide} | ||
onKeyUp={handleKeyUp} | ||
onKeyDown={handleKeyDown} | ||
order={1} | ||
rcx-input-box--undecorated | ||
children={!value ? option || placeholder : null} | ||
/> | ||
{currentValue.map((value) => ( | ||
<SelectedOptions | ||
tabIndex={-1} | ||
role='option' | ||
key={value} | ||
onMouseDown={(e) => | ||
prevent(e) & internalChanged([value]) && false | ||
} | ||
children={getLabel( | ||
options.find(([val]) => val === value) | ||
)} | ||
/> | ||
))} | ||
</Margins> | ||
</Box> | ||
</Box> | ||
</Flex.Container> | ||
</Margins> | ||
</Flex.Item> | ||
<Flex.Item grow={0} shrink={0}> | ||
<Margins inline='x4'> | ||
<Addon | ||
children={ | ||
<Icon | ||
name={ | ||
visible === AnimatedVisibility.VISIBLE | ||
? 'cross' | ||
: 'chevron-down' | ||
} | ||
size='x20' | ||
/> | ||
} | ||
/> | ||
</Margins> | ||
</Flex.Item> | ||
<AnimatedVisibility visibility={visible}> | ||
<Position anchor={containerRef}> | ||
<_Options | ||
width={borderBoxSize.inlineSize} | ||
onMouseDown={prevent} | ||
multiple | ||
filter={filter} | ||
renderItem={CheckOption} | ||
role='listbox' | ||
options={filteredOptions} | ||
onSelect={internalChanged} | ||
cursor={cursor} | ||
/> | ||
</Position> | ||
</AnimatedVisibility> | ||
</Box> | ||
); | ||
} | ||
); | ||
|
||
export const MultiSelectFiltered = ({ options, placeholder, ...props }) => { | ||
const [filter, setFilter] = useState(''); | ||
const anchor = useCallback( | ||
forwardRef(({ children, filter, ...props }, ref) => ( | ||
<Flex.Item grow={1}> | ||
<InputBox.Input | ||
ref={ref} | ||
placeholder={placeholder} | ||
value={filter} | ||
onInput={(e) => setFilter(e.currentTarget.value)} | ||
{...props} | ||
rcx-input-box--undecorated | ||
/> | ||
</Flex.Item> | ||
)), | ||
[] | ||
); | ||
return ( | ||
<MultiSelect filter={filter} options={options} {...props} anchor={anchor} /> | ||
); | ||
}; |
91 changes: 91 additions & 0 deletions
91
packages/fuselage/src/components/MultiSelect/MultiSelect.stories.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { action } from '@storybook/addon-actions'; | ||
import { Meta, Canvas, ArgsTable, Story } from '@storybook/addon-docs/blocks'; | ||
|
||
import { MultiSelect, MultiSelectFiltered } from '../..'; | ||
|
||
export const thumb = | ||
'data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=='; | ||
export const options = [ | ||
[1, 'a teste 1'], | ||
[2, 'b teste 2', true], | ||
[3, 'c teste 3'], | ||
[4, 'd teste 4'], | ||
[5, 'd teste 5'], | ||
[6, 'd teste 6'], | ||
[7, 'd teste 7'], | ||
[8, 'd teste 8'], | ||
[9, 'd teste 9'], | ||
[10, 'd teste 10'], | ||
]; | ||
export const optionsEllipses = [ | ||
[11, 'Very very very very very very very very very large text'], | ||
...options, | ||
]; | ||
|
||
<Meta | ||
title='Forms/MultiSelect' | ||
component={MultiSelect} | ||
parameters={{ jest: ['Select/spec'] }} | ||
/> | ||
|
||
# MultiSelect | ||
|
||
An input for selection of options. | ||
|
||
<Canvas> | ||
<Story name='MultiSelect'> | ||
<MultiSelect placeholder='Placeholder here...' options={options} /> | ||
</Story> | ||
</Canvas> | ||
|
||
<Canvas> | ||
<Story name='MultiSelect Error'> | ||
<MultiSelect error placeholder='Placeholder here...' options={options} /> | ||
</Story> | ||
</Canvas> | ||
|
||
<Canvas> | ||
<Story name='MultiSelect Disabled'> | ||
<MultiSelect disabled placeholder='Placeholder here...' options={options} /> | ||
</Story> | ||
</Canvas> | ||
|
||
<ArgsTable of={MultiSelect} /> | ||
|
||
# MultiSelectFiltered | ||
|
||
An input for selection of options. | ||
|
||
<Canvas> | ||
<Story name='MultiSelectFiltered'> | ||
<MultiSelectFiltered | ||
placeholder='Placeholder here...' | ||
onChange={action('change')} | ||
options={options} | ||
/> | ||
</Story> | ||
</Canvas> | ||
|
||
<Canvas> | ||
<Story name='MultiSelectFiltered error'> | ||
<MultiSelectFiltered | ||
error | ||
placeholder='Placeholder here...' | ||
onChange={action('change')} | ||
options={options} | ||
/> | ||
</Story> | ||
</Canvas> | ||
|
||
<Canvas> | ||
<Story name='MultiSelectFiltered disabled'> | ||
<MultiSelectFiltered | ||
disabled | ||
placeholder='Placeholder here...' | ||
onChange={action('change')} | ||
options={options} | ||
/> | ||
</Story> | ||
</Canvas> | ||
|
||
<ArgsTable of={MultiSelectFiltered} /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { ComponentProps, ForwardRefExoticComponent } from 'react'; | ||
|
||
import { Box } from '../Box'; | ||
|
||
type MultiSelectOptions = readonly (readonly [string, string])[]; | ||
|
||
type MultiSelectProps = Omit<ComponentProps<typeof Box>, 'onChange'> & { | ||
error?: string; | ||
options: MultiSelectOptions; | ||
onChange: (value: MultiSelectOptions[number][0]) => void; | ||
}; | ||
|
||
export const MultiSelect: ForwardRefExoticComponent<MultiSelectProps>; | ||
|
||
export const MultiSelectFiltered: ForwardRefExoticComponent<MultiSelectProps>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './MultiSelect'; |
Oops, something went wrong.