Add Select.tsx

This commit is contained in:
0x1eef 2024-01-24 13:29:43 -03:00
parent c289d807e5
commit 7c36b72541
3 changed files with 109 additions and 17 deletions

View file

@ -2,7 +2,7 @@
@import "tail/width";
@import "tail/margin";
@import "tail/padding";
@import "tail/borders";
@import "tail/border";
@import "tail/text";
@import "tail/font";
@import "tail/cursor";
@ -42,3 +42,7 @@
.text-left { text-align: left; }
.text-center { text-align: center; }
.text-right { text-align: right; }
.border-primary { border-color: $primary-color; }
.border-secondary { border-color: $secondary-color; }
.border-accent { border-color: $accent-color; }

View file

@ -1,6 +1,7 @@
import React, { useState, useEffect } from "react";
import React from "react";
import { useProjects } from "/hooks/queries/useProjects";
import { Project, Maybe } from "/types/schema";
import { Select, Option } from "/components/Select";
type Props = {
selected: Maybe<string>;
@ -10,28 +11,33 @@ type Props = {
export function ProjectSelect({ onChange, selected }: Props) {
const { data, loading } = useProjects();
const projects: Project[] = data?.projects || [];
const options = [
<option>Any project</option>,
...projects.map(project => {
return <option value={project.id}>{project.name}</option>;
}),
];
const options: Option[] = projects.map(project => ({
label: (
<div className="flex">
<span
style={{ backgroundColor: project.color }}
className="flex w-2/8 rounded w-8 h-8 mr-3 cursor-pointer"
></span>
<span className="flex align-items-center">{project.name}</span>
</div>
),
value: String(project.id),
}));
if (loading) {
return null;
}
return (
<select
className="flex p-3 w-3/4 bg-primary text-medium"
defaultValue={selected}
onChange={event => {
const { target } = event;
const project = projects.find(p => p.id === Number(target.value));
<Select
onChange={(option: Option) => {
const project = projects.find(p => String(p.id) == option.value);
onChange(project);
}}
>
{...options}
</select>
options={options}
selected={String(selected)}
placeholder="Any project"
/>
);
1;
}

View file

@ -0,0 +1,82 @@
import React, { ReactNode, useState, useEffect } from "react";
const LI_CLASSNAME = [
"flex",
"align-items-center",
"w-3/4",
"hover-bg-secondary",
"p-3",
"mt-2",
"cursor-pointer",
].join(" ");
const ACTIVE_CLASSNAME = [
LI_CLASSNAME,
"rounded",
"border",
"border-solid",
"border-secondary",
"text-primary",
"bg-secondary"
].join(" ");
type Props = {
options: Option[];
selected: string;
onChange: (o: Option) => void;
placeholder: string;
};
export type Option = {
label: string | ReactNode;
value: string;
};
export const Select = ({ onChange, options, selected, placeholder }: Props) => {
const selectOptions = [{ label: placeholder, value: "" }, ...options];
const [isOpen, setIsOpen] = useState<boolean>(false);
const [option, setOption] = useState<Option>(
options.find(o => o.value === selected) || selectOptions[0]
);
const onClick = (option: Option) => {
if (isOpen) {
setOption(option);
setIsOpen(false);
onChange(option);
} else {
setIsOpen(true);
}
};
const getClassName = (o1: Option, o2: Option) => {
if (o1.value === o2.value) {
return ACTIVE_CLASSNAME
} else {
return (!isOpen && o1.value !== o2.value) ? "hidden" : LI_CLASSNAME;
}
}
useEffect(() => {
const onDocumentClick = () => {
if (isOpen) {
setIsOpen(false)
}
}
document.body.addEventListener('click', onDocumentClick)
return () => document.body.removeEventListener('click', onDocumentClick)
}, [isOpen])
return (
<ul>
{selectOptions.map((o, i) => (
<li
key={i}
data-value={o.value}
onClick={(e) => [e.stopPropagation(), onClick(o)]}
className={getClassName(o, option)}
>
{o.label}
</li>
))}
</ul>
);
};