refactor: migrate dropdowns from reach-ui (#2315)

This commit is contained in:
Aman Harwara
2023-04-21 16:59:45 +05:30
committed by GitHub
parent e5b1c32730
commit 3d68ae0923
42 changed files with 86 additions and 471 deletions

View File

@@ -1,15 +1,18 @@
import { ListboxArrow, ListboxInput, ListboxList, ListboxPopover } from '@reach/listbox'
import '@reach/listbox/styles.css'
import { FunctionComponent } from 'react'
import { useEffect } from 'react'
import Icon from '@/Components/Icon/Icon'
import { DropdownItem } from './DropdownItem'
import StyledListboxButton from './StyledListboxButton'
import StyledListboxOption from './StyledListboxOption'
import { classNames } from '@standardnotes/snjs'
import { VisuallyHidden } from '@ariakit/react'
import {
Select,
SelectArrow,
SelectItem,
SelectLabel,
SelectPopover,
useSelectStore,
VisuallyHidden,
} from '@ariakit/react'
type DropdownProps = {
id: string
label: string
items: DropdownItem[]
value: string
@@ -21,92 +24,77 @@ type DropdownProps = {
popover?: string
}
fullWidth?: boolean
portal?: boolean
}
type ListboxButtonProps = DropdownItem & {
isExpanded: boolean
}
const Dropdown = ({ label, value, onChange, items, disabled, fullWidth, classNameOverride = {} }: DropdownProps) => {
const select = useSelectStore({
defaultValue: value,
})
const CustomDropdownButton: FunctionComponent<ListboxButtonProps> = ({
label,
isExpanded,
icon,
iconClassName = '',
}) => (
<>
<div className="flex items-center">
{icon ? (
<div className="mr-2 flex">
<Icon type={icon} className={iconClassName} size="small" />
</div>
) : null}
<div className="text-base lg:text-sm">{label}</div>
</div>
<ListboxArrow className={`flex ${isExpanded ? 'rotate-180' : ''}`}>
<Icon type="menu-arrow-down" className="text-passive-1" size="small" />
</ListboxArrow>
</>
)
const selectedValue = select.useState('value')
const isExpanded = select.useState('open')
const Dropdown: FunctionComponent<DropdownProps> = ({
id,
label,
items,
value,
onChange,
disabled,
classNameOverride = {},
fullWidth,
portal = true,
}) => {
const labelId = `${id}-label`
const currentItem = items.find((item) => item.value === selectedValue)
const handleChange = (value: string) => {
const selectedItem = items.find((item) => item.value === value) as DropdownItem
onChange(value, selectedItem)
}
useEffect(() => {
return select.subscribe(
(state) => {
if (state.value !== value) {
onChange(state.value, items.find((item) => item.value === state.value) as DropdownItem)
}
},
['value'],
)
}, [items, onChange, select, value])
return (
<div className={classNameOverride.wrapper}>
<VisuallyHidden id={labelId}>{label}</VisuallyHidden>
<ListboxInput value={value} onChange={handleChange} aria-labelledby={labelId} disabled={disabled}>
<StyledListboxButton
className={classNames('w-full', !fullWidth && 'md:w-fit', classNameOverride.button)}
children={({ value, label, isExpanded }) => {
const current = items.find((item) => item.value === value)
const icon = current ? current?.icon : null
const iconClassName = current ? current?.iconClassName : null
return CustomDropdownButton({
value: value ? value : label.toLowerCase(),
label,
isExpanded,
...(icon ? { icon } : null),
...(iconClassName ? { iconClassName } : null),
})
}}
/>
<ListboxPopover
portal={portal}
className={classNames('sn-dropdown sn-dropdown-popover', classNameOverride.popover)}
>
<div className="sn-component">
<ListboxList>
{items.map((item) => (
<StyledListboxOption key={item.value} value={item.value} label={item.label} disabled={item.disabled}>
{item.icon ? (
<div className="mr-3 flex">
<Icon type={item.icon} className={item.iconClassName ?? ''} size="small" />
</div>
) : null}
<div className="text-base lg:text-sm">{item.label}</div>
</StyledListboxOption>
))}
</ListboxList>
</div>
</ListboxPopover>
</ListboxInput>
<VisuallyHidden>
<SelectLabel store={select}>{label}</SelectLabel>
</VisuallyHidden>
<Select
className={classNames(
'flex w-full min-w-55 items-center justify-between rounded border border-border bg-default py-1.5 px-3.5 text-sm text-foreground',
disabled && 'opacity-50',
classNameOverride.button,
!fullWidth && 'md:w-fit',
)}
store={select}
disabled={disabled}
>
<div className="flex items-center">
{currentItem?.icon ? (
<div className="mr-2 flex">
<Icon type={currentItem.icon} className={currentItem.iconClassName ?? ''} size="small" />
</div>
) : null}
<div className="text-base lg:text-sm">{currentItem?.label}</div>
</div>
<SelectArrow className={classNames('text-passive-1', isExpanded && 'rotate-180')} />
</Select>
<SelectPopover
store={select}
className={classNames(
'max-h-[var(--popover-available-height)] w-[var(--popover-anchor-width)] overflow-y-auto rounded border border-border bg-default py-1',
classNameOverride.popover,
)}
>
{items.map((item) => (
<SelectItem
className="flex cursor-pointer items-center bg-transparent py-1.5 px-3 text-sm text-text hover:bg-contrast hover:text-foreground [&[data-active-item]]:bg-info [&[data-active-item]]:text-info-contrast"
key={item.value}
value={item.value}
disabled={item.disabled}
>
{item.icon ? (
<div className="mr-3 flex">
<Icon type={item.icon} className={item.iconClassName ?? ''} size="small" />
</div>
) : null}
<div className="text-base lg:text-sm">{item.label}</div>
</SelectItem>
))}
</SelectPopover>
</div>
)
}