Browse Source
Co-authored-by: William Oldham <wegg7250@gmail.com> Co-authored-by: James Hawkins <jhawki2005@gmail.com>pull/60/head
2 changed files with 146 additions and 17 deletions
@ -0,0 +1,129 @@ |
|||||||
|
import { Icon, Icons } from "components/Icon"; |
||||||
|
import React, { |
||||||
|
MouseEventHandler, |
||||||
|
SyntheticEvent, |
||||||
|
useEffect, |
||||||
|
useState, |
||||||
|
} from "react"; |
||||||
|
|
||||||
|
import { Backdrop, useBackdrop } from "components/layout/Backdrop"; |
||||||
|
import { ButtonControl } from "./buttons/ButtonControl"; |
||||||
|
|
||||||
|
export interface OptionItem { |
||||||
|
id: string; |
||||||
|
name: string; |
||||||
|
} |
||||||
|
|
||||||
|
interface DropdownProps { |
||||||
|
open: boolean; |
||||||
|
setOpen: React.Dispatch<React.SetStateAction<boolean>>; |
||||||
|
selectedItem: string; |
||||||
|
setSelectedItem: (value: string) => void; |
||||||
|
options: Array<OptionItem>; |
||||||
|
} |
||||||
|
|
||||||
|
export interface OptionProps { |
||||||
|
option: OptionItem; |
||||||
|
onClick: MouseEventHandler<HTMLDivElement>; |
||||||
|
tabIndex?: number; |
||||||
|
} |
||||||
|
|
||||||
|
function Option({ option, onClick, tabIndex }: OptionProps) { |
||||||
|
return ( |
||||||
|
<div |
||||||
|
className="text-denim-700 flex h-10 cursor-pointer items-center space-x-2 px-4 py-2 text-left transition-colors hover:text-white" |
||||||
|
onClick={onClick} |
||||||
|
tabIndex={tabIndex} |
||||||
|
> |
||||||
|
<input type="radio" className="hidden" id={option.id} /> |
||||||
|
<label htmlFor={option.id} className="cursor-pointer "> |
||||||
|
<div className="item">{option.name}</div> |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export const Dropdown = React.forwardRef<HTMLDivElement, DropdownProps>( |
||||||
|
(props: DropdownProps, ref) => { |
||||||
|
const [setBackdrop, backdropProps, highlightedProps] = useBackdrop(); |
||||||
|
const [delayedSelectedId, setDelayedSelectedId] = useState( |
||||||
|
props.selectedItem |
||||||
|
); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
let id: NodeJS.Timeout; |
||||||
|
|
||||||
|
if (props.open) { |
||||||
|
setDelayedSelectedId(props.selectedItem); |
||||||
|
} else { |
||||||
|
id = setTimeout(() => { |
||||||
|
setDelayedSelectedId(props.selectedItem); |
||||||
|
}, 200); |
||||||
|
} |
||||||
|
return () => { |
||||||
|
if (id) clearTimeout(id); |
||||||
|
}; |
||||||
|
/* eslint-disable-next-line */ |
||||||
|
}, [props.open]); |
||||||
|
|
||||||
|
const selectedItem: OptionItem = |
||||||
|
props.options.find((opt) => opt.id === props.selectedItem) || |
||||||
|
props.options[0]; |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
setBackdrop(props.open); |
||||||
|
/* eslint-disable-next-line */ |
||||||
|
}, [props.open]); |
||||||
|
|
||||||
|
const onOptionClick = (e: SyntheticEvent, option: OptionItem) => { |
||||||
|
e.stopPropagation(); |
||||||
|
props.setSelectedItem(option.id); |
||||||
|
props.setOpen(false); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div |
||||||
|
className="min-w-[140px]" |
||||||
|
onClick={() => props.setOpen((open) => !open)} |
||||||
|
> |
||||||
|
<div |
||||||
|
ref={ref} |
||||||
|
className="relative w-full sm:w-auto" |
||||||
|
{...highlightedProps} |
||||||
|
> |
||||||
|
<ButtonControl |
||||||
|
{...props} |
||||||
|
className="sm:justify-left bg-bink-200 hover:bg-bink-300 relative z-20 flex h-10 w-full items-center justify-center space-x-2 rounded-[20px] px-4 py-2 text-white" |
||||||
|
> |
||||||
|
<span className="flex-1">{selectedItem.name}</span> |
||||||
|
<Icon |
||||||
|
icon={Icons.CHEVRON_DOWN} |
||||||
|
className={`transition-transform ${ |
||||||
|
props.open ? "rotate-180" : "" |
||||||
|
}`}
|
||||||
|
/> |
||||||
|
</ButtonControl> |
||||||
|
<div |
||||||
|
className={`bg-denim-300 options-scrollbar absolute top-0 z-10 w-full overflow-y-auto rounded-[20px] pt-[40px] transition-all duration-200 ${ |
||||||
|
props.open |
||||||
|
? "block max-h-60 opacity-100" |
||||||
|
: "invisible max-h-0 opacity-0" |
||||||
|
}`}
|
||||||
|
> |
||||||
|
{props.options |
||||||
|
.filter((opt) => opt.id !== delayedSelectedId) |
||||||
|
.map((opt) => ( |
||||||
|
<Option |
||||||
|
option={opt} |
||||||
|
key={opt.id} |
||||||
|
onClick={(e) => onOptionClick(e, opt)} |
||||||
|
tabIndex={props.open ? 0 : undefined} |
||||||
|
/> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<Backdrop onClick={() => props.setOpen(false)} {...backdropProps} /> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
); |
Loading…
Reference in new issue