2023-08-04 18:45:47 +02:00
|
|
|
import React, { useEffect, useRef, useState } from "react";
|
|
|
|
import { theme } from "../../theme";
|
2023-08-04 19:19:15 +02:00
|
|
|
import { Icon } from "../Icon";
|
2023-08-04 18:45:47 +02:00
|
|
|
|
|
|
|
export interface DropdownProps {
|
|
|
|
onChange: (value: string) => void;
|
|
|
|
options: Array<{ label: string; value: string }>;
|
|
|
|
selected?: string;
|
|
|
|
open?: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
const DropdownOptions: React.FC<DropdownProps> = (props) => {
|
|
|
|
return (
|
2023-08-04 20:04:58 +02:00
|
|
|
<div
|
|
|
|
style={{
|
|
|
|
position: "absolute",
|
|
|
|
zIndex: 100,
|
|
|
|
...theme.background.secondary,
|
|
|
|
...theme.border.primary,
|
|
|
|
borderTop: 0,
|
|
|
|
top: 25,
|
|
|
|
width: "100%",
|
|
|
|
left: -1,
|
|
|
|
}}
|
|
|
|
>
|
2023-08-04 18:45:47 +02:00
|
|
|
{props.options.map(({ label, value }) => (
|
|
|
|
<div
|
|
|
|
style={{ display: "flex", padding: "4px 8px", cursor: "pointer" }}
|
|
|
|
onClick={() => {
|
|
|
|
props.onChange(value);
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<div style={{ marginRight: 10, width: 10 }}>
|
2023-08-04 19:19:15 +02:00
|
|
|
{props.selected === value ? <Icon name="check" /> : ""}
|
2023-08-04 18:45:47 +02:00
|
|
|
</div>
|
|
|
|
<div>{label}</div>
|
|
|
|
</div>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Temp comp until @nocodelytics/components is ready
|
|
|
|
* We'll use <Timeframe /> which will also include types
|
|
|
|
*/
|
|
|
|
export const Dropdown: React.FC<DropdownProps> = (props) => {
|
|
|
|
if (!props.options.length) {
|
|
|
|
throw new Error(`Dropdown requires options`);
|
|
|
|
}
|
|
|
|
const [isOpen, setIsOpen] = useState<boolean>(props.open || false);
|
|
|
|
const [selected, setSelected] = useState<string>(
|
|
|
|
props.selected || props.options[0].value
|
|
|
|
);
|
|
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
function handleClickOutside(event: Event) {
|
|
|
|
if (ref.current && !ref.current.contains(event.target as Node)) {
|
|
|
|
setIsOpen(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
document.addEventListener("mousedown", handleClickOutside);
|
|
|
|
return () => {
|
|
|
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
|
|
};
|
|
|
|
}, [ref]);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
ref={ref}
|
|
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
|
|
style={{
|
|
|
|
position: "relative",
|
|
|
|
...theme.text.primary,
|
|
|
|
...theme.background.secondary,
|
|
|
|
...theme.border.primary,
|
|
|
|
cursor: "pointer",
|
|
|
|
maxWidth: 150,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
style={{
|
|
|
|
padding: "0px 4px 0px 8px",
|
|
|
|
height: 24,
|
|
|
|
justifyContent: "unset",
|
|
|
|
gridAutoFlow: "column",
|
|
|
|
display: "grid",
|
|
|
|
alignItems: "center",
|
|
|
|
gap: 2,
|
2023-08-04 19:55:21 +02:00
|
|
|
lineHeight: 2.4,
|
2023-08-04 18:45:47 +02:00
|
|
|
}}
|
|
|
|
>
|
2023-08-04 19:19:15 +02:00
|
|
|
<span style={{ flex: 1 }}>
|
|
|
|
{props.options.find(({ value }) => selected === value)!.label}
|
|
|
|
</span>
|
|
|
|
<Icon
|
2023-08-04 19:55:21 +02:00
|
|
|
styles={{ textAlign: "right", marginRight: 12, marginLeft: 12 }}
|
2023-08-04 19:19:15 +02:00
|
|
|
name="chevron-down"
|
|
|
|
/>
|
2023-08-04 18:45:47 +02:00
|
|
|
</div>
|
|
|
|
{isOpen && (
|
|
|
|
<DropdownOptions
|
|
|
|
{...props}
|
|
|
|
selected={selected}
|
|
|
|
onChange={(value) => {
|
|
|
|
setIsOpen(false);
|
|
|
|
setSelected(value);
|
|
|
|
props.onChange(value);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|