Add Select.tsx
This commit is contained in:
parent
c289d807e5
commit
7c36b72541
3 changed files with 109 additions and 17 deletions
6
twenty-frontend/src/css/vendor/_tail.scss
vendored
6
twenty-frontend/src/css/vendor/_tail.scss
vendored
|
@ -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; }
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
82
twenty-frontend/src/js/components/Select.tsx
Normal file
82
twenty-frontend/src/js/components/Select.tsx
Normal 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>
|
||||
);
|
||||
};
|
Loading…
Reference in a new issue