Add FilterInput
Easier to quickly set focus on project.
This commit is contained in:
parent
93af35c490
commit
c3b5c2377c
4 changed files with 126 additions and 74 deletions
|
@ -1,3 +1,3 @@
|
|||
import { createContext } from "react";
|
||||
type TContext = Record<"params" | "cookies", Record<string, string>>;
|
||||
export const AppContext = createContext<TContext>({params: {}, cookies: {}});
|
||||
export const AppContext = createContext<TContext>({ params: {}, cookies: {} });
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,16 +80,48 @@ export const Select = ({ onChange, options, selected, placeholder, className }:
|
|||
|
||||
return (
|
||||
<ul className={className}>
|
||||
{selectOptions.map((o, i) => (
|
||||
<li
|
||||
key={i}
|
||||
data-value={o.value}
|
||||
onClick={e => [e.stopPropagation(), onClick(o)]}
|
||||
className={getClassName(o, option)}
|
||||
>
|
||||
{o.label}
|
||||
{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}
|
||||
onClick={e => [e.stopPropagation(), onClick(o)]}
|
||||
className={getClassName(o, option)}
|
||||
>
|
||||
{o.label}
|
||||
</li>
|
||||
))}
|
||||
</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"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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"]>;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue