Refactor task list example application
This commit is contained in:
14
examples/task-list/src/components/application.tsx
Normal file
14
examples/task-list/src/components/application.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { CreateTask } from './create-task';
|
||||
import { TaskProvider } from '../contexts/task-context';
|
||||
import { Tasks } from './tasks';
|
||||
|
||||
export const Application = () => {
|
||||
return (
|
||||
<TaskProvider>
|
||||
<main className="container max-w-xl my-10 space-y-8">
|
||||
<CreateTask onSubmit={(title) => console.log(title)} />
|
||||
<Tasks />
|
||||
</main>
|
||||
</TaskProvider>
|
||||
);
|
||||
};
|
||||
43
examples/task-list/src/components/create-task.tsx
Normal file
43
examples/task-list/src/components/create-task.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { useState } from 'react';
|
||||
import { useTaskActions } from '../contexts/task-context';
|
||||
|
||||
type CreateTaskProps = {
|
||||
onSubmit: (title: string) => void;
|
||||
};
|
||||
|
||||
export const CreateTask = ({ onSubmit }: CreateTaskProps) => {
|
||||
const { addTask } = useTaskActions();
|
||||
const [title, setTitle] = useState('');
|
||||
|
||||
return (
|
||||
<form
|
||||
method="POST"
|
||||
action="/api/tasks"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
addTask(title);
|
||||
setTitle('');
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<label htmlFor="new-task-title" className="sr-only">
|
||||
Title
|
||||
</label>
|
||||
<div className="flex flex-col sm:flex-row">
|
||||
<input
|
||||
type="text"
|
||||
name="title"
|
||||
id="new-task-title"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="What do you need to get done?"
|
||||
required
|
||||
/>
|
||||
<button type="submit" disabled={!title}>
|
||||
Create Task
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
26
examples/task-list/src/components/date-time.tsx
Normal file
26
examples/task-list/src/components/date-time.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { ComponentPropsWithoutRef } from 'react';
|
||||
import { twMerge as merge } from 'tailwind-merge';
|
||||
|
||||
const formatDate = new Intl.DateTimeFormat(navigator.language, {
|
||||
dateStyle: 'short',
|
||||
timeStyle: 'short',
|
||||
});
|
||||
|
||||
export const DateTime = ({
|
||||
date,
|
||||
title,
|
||||
className,
|
||||
}: ComponentPropsWithoutRef<'div'> & {
|
||||
date: Date | string;
|
||||
title: string;
|
||||
}) => {
|
||||
if (typeof date === 'string') date = new Date(date);
|
||||
return (
|
||||
<div className={merge('overflow-x-hidden space-x-2 text-xs', className)}>
|
||||
<span className="font-semibold after:content-[':'] after:text-slate-700 text-primary-800 dark:text-primary-200 dark:after:text-slate-300">
|
||||
{title}
|
||||
</span>
|
||||
<span className="whitespace-pre">{formatDate.format(date)}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
45
examples/task-list/src/components/task.tsx
Normal file
45
examples/task-list/src/components/task.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { memo } from 'react';
|
||||
import { ChevronRightCircle } from 'lucide-react';
|
||||
import { DateTime } from './date-time';
|
||||
import { useTaskActions } from '../contexts/task-context';
|
||||
|
||||
type TaskProps = {
|
||||
task: import('../types').Task;
|
||||
};
|
||||
|
||||
export const Task = memo(({ task }: TaskProps) => {
|
||||
const { updateTask, removeTask } = useTaskActions();
|
||||
|
||||
return (
|
||||
<li className="block p-4 space-y-2 border-t first:rounded-t-md border-x last:border-b last:rounded-b-md dark:border-slate-700">
|
||||
<header className="flex flex-row items-center gap-4">
|
||||
<label htmlFor={`toggle-${task.id}`} className="sr-only">
|
||||
Mark Task as {task.completed ? 'Incomplete' : 'Complete'}
|
||||
</label>
|
||||
<input
|
||||
id={`toggle-${task.id}`}
|
||||
type="checkbox"
|
||||
className="block w-6 h-6"
|
||||
checked={task.completed}
|
||||
onChange={() => updateTask(task.id, { completed: !task.completed })}
|
||||
/>
|
||||
<h2 className="w-full font-semibold">{task.title}</h2>
|
||||
<button
|
||||
className="button-small button-destructive button-ghost"
|
||||
onClick={() => removeTask(task.id)}
|
||||
>
|
||||
❌
|
||||
</button>
|
||||
</header>
|
||||
<label className="text-xs grid grid-cols-[1.25rem_1fr] gap-1 cursor-pointer rounded-md bg-primary-50 dark:bg-primary-950 border-2 border-primary-100 dark:border-primary-900 p-2">
|
||||
<input type="checkbox" className="sr-only peer" />
|
||||
<ChevronRightCircle className="block w-4 h-4 transition-transform peer-checked:rotate-90" />
|
||||
<h3 className="select-none">Metadata</h3>
|
||||
<div className="hidden col-span-2 gap-2 peer-checked:flex">
|
||||
<DateTime date={task.createdAt} title="Created" />
|
||||
<DateTime date={task.lastModified} title="Modified" />
|
||||
</div>
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
14
examples/task-list/src/components/tasks.tsx
Normal file
14
examples/task-list/src/components/tasks.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Task } from './task';
|
||||
import { useTaskState } from '../contexts/task-context';
|
||||
|
||||
export const Tasks = () => {
|
||||
const tasks = useTaskState();
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{tasks.map((task) => (
|
||||
<Task key={task.id} task={task} />
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user