Add FilterInput

Easier to quickly set focus on project.
This commit is contained in:
0x1eef 2024-04-24 03:16:06 -03:00
parent 93af35c490
commit c3b5c2377c
4 changed files with 126 additions and 74 deletions

View file

@ -11,6 +11,7 @@ export function ProjectSelect({ onChange, selected }: Props) {
const { data, loading } = useProjects();
const projects: Project[] = data?.projects || [];
const options: Option[] = projects.map(project => ({
id: project.id,
label: (
<div className="flex items-center">
<span
@ -20,7 +21,7 @@ export function ProjectSelect({ onChange, selected }: Props) {
<span className="flex">{project.name}</span>
</div>
),
value: String(project.id),
value: project.name,
}));
if (loading) {
@ -30,7 +31,7 @@ export function ProjectSelect({ onChange, selected }: Props) {
return (
<Select
onChange={(option: Option) => {
const project = projects.find(p => String(p.id) == option.value);
const project = projects.find(p => p.id == option.id);
onChange(project);
}}
options={options}

View file

@ -25,18 +25,31 @@ type Props = {
onChange: (o: Option) => void;
placeholder: string;
className?: string;
isFilterable?: boolean;
};
export type Option = {
id: number;
label: string | ReactNode;
value: string;
};
export const Select = ({ onChange, options, selected, placeholder, className }: Props) => {
const selectOptions = [{ label: placeholder, value: "" }, ...options];
export const Select = ({
onChange,
options,
selected,
placeholder,
className,
isFilterable = true,
}: Props) => {
const selectOptions = [
{ id: null, label: placeholder, value: "" },
...options,
];
const [isOpen, setIsOpen] = useState<boolean>(false);
const [filterText, setFilterText] = useState<string>(null);
const [option, setOption] = useState<Option>(
options.find(o => o.value === selected) || selectOptions[0],
options.find(o => String(o.id) === selected) || selectOptions[0],
);
const onClick = (option: Option) => {
if (isOpen) {
@ -67,7 +80,21 @@ export const Select = ({ onChange, options, selected, placeholder, className }:
return (
<ul className={className}>
{selectOptions.map((o, i) => (
{isFilterable && isOpen && (
<li className="flex">
<FilterInput onTextChange={text => setFilterText(text)} />
</li>
)}
{selectOptions
.filter(o => {
if (filterText === null) {
return true;
} else {
const regexp = new RegExp(filterText);
return regexp.test(o.value);
}
})
.map((o, i) => (
<li
key={i}
data-value={o.value}
@ -80,3 +107,21 @@ export const Select = ({ onChange, options, selected, placeholder, className }:
</ul>
);
};
type FilterInputProps = { onTextChange: (text: string) => void };
function FilterInput({ onTextChange }: FilterInputProps) {
return (
<input
className="p-3 rounded border-secondary border-solid outline-none"
onClick={e => e.stopPropagation()}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const {
target: { value: text },
} = e;
onTextChange(text);
}}
type="text"
placeholder="Filter by text"
/>
);
}

View file

@ -1,43 +1,56 @@
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
export type Exact<T extends { [key: string]: unknown }> = {
[K in keyof T]: T[K];
};
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & {
[SubKey in K]?: Maybe<T[SubKey]>;
};
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & {
[SubKey in K]: Maybe<T[SubKey]>;
};
export type MakeEmpty<
T extends { [key: string]: unknown },
K extends keyof T,
> = { [_ in K]?: never };
export type Incremental<T> =
| T
| {
[P in keyof T]?: P extends " $fragmentName" | "__typename" ? T[P] : never;
};
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: { input: string; output: string; }
String: { input: string; output: string; }
Boolean: { input: boolean; output: boolean; }
Int: { input: number; output: number; }
Float: { input: number; output: number; }
ID: { input: string; output: string };
String: { input: string; output: string };
Boolean: { input: boolean; output: boolean };
Int: { input: number; output: number };
Float: { input: number; output: number };
/** An ISO 8601-encoded datetime */
ISO8601DateTime: { input: any; output: any; }
ISO8601DateTime: { input: any; output: any };
};
/** Autogenerated return type of CompleteTask. */
export type CompleteTaskPayload = {
__typename?: 'CompleteTaskPayload';
errors?: Maybe<Array<Scalars['String']['output']>>;
ok?: Maybe<Scalars['Boolean']['output']>;
__typename?: "CompleteTaskPayload";
errors?: Maybe<Array<Scalars["String"]["output"]>>;
ok?: Maybe<Scalars["Boolean"]["output"]>;
};
/** Autogenerated return type of CreateTask. */
export type CreateTaskPayload = {
__typename?: 'CreateTaskPayload';
errors: Array<Scalars['String']['output']>;
__typename?: "CreateTaskPayload";
errors: Array<Scalars["String"]["output"]>;
};
/** Autogenerated return type of DestroyTask. */
export type DestroyTaskPayload = {
__typename?: 'DestroyTaskPayload';
errors?: Maybe<Array<Scalars['String']['output']>>;
ok?: Maybe<Scalars['Boolean']['output']>;
__typename?: "DestroyTaskPayload";
errors?: Maybe<Array<Scalars["String"]["output"]>>;
ok?: Maybe<Scalars["Boolean"]["output"]>;
};
export type Mutation = {
__typename?: 'Mutation';
__typename?: "Mutation";
completeTask?: Maybe<CompleteTaskPayload>;
createTask?: Maybe<CreateTaskPayload>;
destroyTask?: Maybe<DestroyTaskPayload>;
@ -45,96 +58,89 @@ export type Mutation = {
updateTask?: Maybe<UpdateTaskPayload>;
};
export type MutationCompleteTaskArgs = {
taskId: Scalars['Int']['input'];
taskId: Scalars["Int"]["input"];
};
export type MutationCreateTaskArgs = {
input: TaskInput;
};
export type MutationDestroyTaskArgs = {
taskId: Scalars['Int']['input'];
taskId: Scalars["Int"]["input"];
};
export type MutationSetRandomProjectColorArgs = {
projectId: Scalars['Int']['input'];
projectId: Scalars["Int"]["input"];
};
export type MutationUpdateTaskArgs = {
input: TaskInput;
taskId: Scalars['Int']['input'];
taskId: Scalars["Int"]["input"];
};
export type Project = {
__typename?: 'Project';
color: Scalars['String']['output'];
id: Scalars['Int']['output'];
name: Scalars['String']['output'];
path: Scalars['String']['output'];
__typename?: "Project";
color: Scalars["String"]["output"];
id: Scalars["Int"]["output"];
name: Scalars["String"]["output"];
path: Scalars["String"]["output"];
tasks: Array<Task>;
};
export type Query = {
__typename?: 'Query';
__typename?: "Query";
findTask?: Maybe<Task>;
projects: Array<Project>;
tasks: Array<Task>;
};
export type QueryFindTaskArgs = {
taskId: Scalars['Int']['input'];
taskId: Scalars["Int"]["input"];
};
export type QueryTasksArgs = {
projectId?: InputMaybe<Scalars['Int']['input']>;
projectId?: InputMaybe<Scalars["Int"]["input"]>;
status: TaskStatus;
};
/** Autogenerated return type of SetRandomProjectColor. */
export type SetRandomProjectColorPayload = {
__typename?: 'SetRandomProjectColorPayload';
errors: Array<Scalars['String']['output']>;
__typename?: "SetRandomProjectColorPayload";
errors: Array<Scalars["String"]["output"]>;
project?: Maybe<Project>;
};
export type Task = {
__typename?: 'Task';
content: Scalars['String']['output'];
id: Scalars['Int']['output'];
inProgress: Scalars['Boolean']['output'];
isBacklogged: Scalars['Boolean']['output'];
isComplete: Scalars['Boolean']['output'];
isReady: Scalars['Boolean']['output'];
__typename?: "Task";
content: Scalars["String"]["output"];
id: Scalars["Int"]["output"];
inProgress: Scalars["Boolean"]["output"];
isBacklogged: Scalars["Boolean"]["output"];
isComplete: Scalars["Boolean"]["output"];
isReady: Scalars["Boolean"]["output"];
project: Project;
status: TaskStatus;
title: Scalars['String']['output'];
updatedAt: Scalars['ISO8601DateTime']['output'];
title: Scalars["String"]["output"];
updatedAt: Scalars["ISO8601DateTime"]["output"];
};
export type TaskInput = {
content?: InputMaybe<Scalars['String']['input']>;
projectId?: InputMaybe<Scalars['Int']['input']>;
content?: InputMaybe<Scalars["String"]["input"]>;
projectId?: InputMaybe<Scalars["Int"]["input"]>;
status?: InputMaybe<TaskStatus>;
title?: InputMaybe<Scalars['String']['input']>;
title?: InputMaybe<Scalars["String"]["input"]>;
};
export enum TaskStatus {
Backlog = 'backlog',
Complete = 'complete',
InProgress = 'in_progress',
Ready = 'ready'
Backlog = "backlog",
Complete = "complete",
InProgress = "in_progress",
Ready = "ready",
}
/** Autogenerated return type of UpdateTask. */
export type UpdateTaskPayload = {
__typename?: 'UpdateTaskPayload';
errors: Array<Scalars['String']['output']>;
__typename?: "UpdateTaskPayload";
errors: Array<Scalars["String"]["output"]>;
};