Add todo frontend
This commit is contained in:
parent
b7a57461a9
commit
f2e9fc67b7
@ -0,0 +1,9 @@
|
||||
import { FC } from "react";
|
||||
|
||||
interface PageHeadingProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const PageHeading: FC<PageHeadingProps> = ({ title }) => (
|
||||
<h3 className="font-bold text-xl tracking-wide">{title}</h3>
|
||||
);
|
@ -0,0 +1,3 @@
|
||||
export const ProjectsHeading = (props: { title: string }) => (
|
||||
<h5 className="text-sm font-bold">{props.title}</h5>
|
||||
);
|
25
src/client/src/components/todos/addTodo.tsx
Normal file
25
src/client/src/components/todos/addTodo.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { useState } from "react";
|
||||
import { CollapsedAddTodo } from "@src/components/todos/collapsed/collapsedAddTodo";
|
||||
import { AddTodoForm } from "@src/components/todos/collapsed/addTodoForm";
|
||||
import { CollapsedState } from "@src/components/todos/collapsed/collapsedState";
|
||||
|
||||
export function AddTodo() {
|
||||
const [collapsed, setCollapsed] = useState<CollapsedState>(
|
||||
CollapsedState.collapsed
|
||||
);
|
||||
|
||||
if (collapsed === CollapsedState.collapsed) {
|
||||
return (
|
||||
<CollapsedAddTodo
|
||||
onClick={() => setCollapsed(CollapsedState.notCollapsed)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AddTodoForm
|
||||
onAdd={(todoName) => {}}
|
||||
onClose={() => setCollapsed(CollapsedState.collapsed)}
|
||||
/>
|
||||
);
|
||||
}
|
35
src/client/src/components/todos/collapsed/addTodoForm.tsx
Normal file
35
src/client/src/components/todos/collapsed/addTodoForm.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { FC, useState } from "react";
|
||||
|
||||
export const AddTodoForm: FC<{
|
||||
onAdd: (todoName: string) => void;
|
||||
onClose: () => void;
|
||||
}> = ({ onAdd, onClose }) => {
|
||||
const [todoName, setTodoName] = useState("");
|
||||
|
||||
return (
|
||||
<div className="p-2 space-y-1">
|
||||
<div className="todo-input-form py-2 px-4 bg-gray-900 border border-gray-600">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Todo name"
|
||||
className="text-sm"
|
||||
value={todoName}
|
||||
onChange={(e) => setTodoName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-x-2">
|
||||
<button
|
||||
disabled={!todoName}
|
||||
onClick={() => {
|
||||
onAdd(todoName);
|
||||
setTodoName("");
|
||||
}}
|
||||
className="disabled:text-gray-800 transition"
|
||||
>
|
||||
Add todo
|
||||
</button>
|
||||
<button onClick={onClose}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,5 @@
|
||||
import { FC } from "react";
|
||||
|
||||
export const CollapsedAddTodo: FC<{ onClick: () => void }> = ({ onClick }) => {
|
||||
return <div onClick={onClick}>Add todo</div>;
|
||||
};
|
12
src/client/src/components/todos/collapsed/collapsedState.tsx
Normal file
12
src/client/src/components/todos/collapsed/collapsedState.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
type Collapsed = true;
|
||||
type NotCollapsed = false;
|
||||
|
||||
export type CollapsedState = Collapsed | NotCollapsed;
|
||||
|
||||
export const CollapsedState: {
|
||||
collapsed: Collapsed;
|
||||
notCollapsed: NotCollapsed;
|
||||
} = {
|
||||
collapsed: true,
|
||||
notCollapsed: false,
|
||||
};
|
2
src/client/src/components/todos/index.ts
Normal file
2
src/client/src/components/todos/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./todoItem";
|
||||
export * from "./todoList";
|
20
src/client/src/components/todos/todoCheckmark.tsx
Normal file
20
src/client/src/components/todos/todoCheckmark.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { StatusState, Todo } from "@src/core/entities/todo";
|
||||
import { FC } from "react";
|
||||
|
||||
interface TodoCheckmarkProps {
|
||||
updateTodo: (todo: Todo) => void;
|
||||
todo: Todo;
|
||||
}
|
||||
|
||||
export const TodoCheckmark: FC<TodoCheckmarkProps> = (props) => (
|
||||
<div
|
||||
onClick={() =>
|
||||
props.updateTodo({ ...props.todo, status: !props.todo.status })
|
||||
}
|
||||
className={`h-5 w-5 rounded-full border dark:border-gray-700 ${
|
||||
props.todo.status === StatusState.done
|
||||
? "dark:bg-gray-700"
|
||||
: "hover:dark:bg-gray-600"
|
||||
}`}
|
||||
/>
|
||||
);
|
17
src/client/src/components/todos/todoItem.tsx
Normal file
17
src/client/src/components/todos/todoItem.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { Todo } from "@src/core/entities/todo";
|
||||
import { FC } from "react";
|
||||
import { TodoCheckmark } from "@src/components/todos/todoCheckmark";
|
||||
|
||||
interface TodoItemProps {
|
||||
todo: Todo;
|
||||
updateTodo: (todo: Todo) => void;
|
||||
}
|
||||
|
||||
export const TodoItem: FC<TodoItemProps> = (props) => (
|
||||
<div className="py-3 border-b border-gray-300 dark:border-gray-700">
|
||||
<div className="flex items-center space-x-4">
|
||||
<TodoCheckmark {...props} />
|
||||
<span className="pb-1">{props.todo.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
21
src/client/src/components/todos/todoList.tsx
Normal file
21
src/client/src/components/todos/todoList.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { Todo } from "@src/core/entities/todo";
|
||||
import { TodoItem } from "@src/components/todos/todoItem";
|
||||
import { AddTodo } from "@src/components/todos/addTodo";
|
||||
|
||||
export const TodoList = (props: { todos: Todo[] }) => (
|
||||
<>
|
||||
<ul id="inbox">
|
||||
{props.todos.map((t, i) => (
|
||||
<li key={i}>
|
||||
<TodoItem
|
||||
todo={t}
|
||||
updateTodo={(todo) => {
|
||||
console.log(todo);
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<AddTodo />
|
||||
</>
|
||||
);
|
18
src/client/src/core/actions/todos.ts
Normal file
18
src/client/src/core/actions/todos.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { StatusState, Todo } from "@src/core/entities/todo";
|
||||
|
||||
export const createTodo = (name: string): Todo => ({
|
||||
name,
|
||||
status: StatusState.notDone,
|
||||
});
|
||||
|
||||
const createDoneTodo = (name: string) => ({
|
||||
...createTodo(name),
|
||||
status: true,
|
||||
});
|
||||
|
||||
const todos: Todo[] = [
|
||||
createTodo("go to dentist"),
|
||||
createTodo("Check e-Boks"),
|
||||
createDoneTodo("Check and clean mail"),
|
||||
];
|
||||
export const getInboxTodos = () => todos;
|
12
src/client/src/core/entities/todo.tsx
Normal file
12
src/client/src/core/entities/todo.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
type NotDone = false;
|
||||
type Done = true;
|
||||
type StatusState = NotDone | Done;
|
||||
export const StatusState: { done: Done; notDone: NotDone } = {
|
||||
done: true,
|
||||
notDone: false,
|
||||
};
|
||||
|
||||
export interface Todo {
|
||||
name: string;
|
||||
status: StatusState;
|
||||
}
|
@ -1,5 +1,18 @@
|
||||
export default function Home() {
|
||||
import { useMemo } from "react";
|
||||
import { getInboxTodos } from "@src/core/actions/todos";
|
||||
import { PageHeading } from "@src/components/common/headings/pageHeading";
|
||||
import { TodoList } from "@src/components/todos";
|
||||
|
||||
const HomePage = () => {
|
||||
const inboxTodos = useMemo(() => getInboxTodos(), []);
|
||||
|
||||
return (
|
||||
<div>Hello</div>
|
||||
)
|
||||
}
|
||||
<main className="py-8 px-14 space-y-6">
|
||||
<section className="space-y-2">
|
||||
<PageHeading title="Inbox" />
|
||||
<TodoList todos={inboxTodos} />
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
export default HomePage;
|
||||
|
@ -1,3 +1,21 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
|
||||
input {
|
||||
@apply shadow-none placeholder-gray-300 dark:placeholder-gray-700 border-none outline-none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
*:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@tailwind utilities;
|
||||
|
||||
* {
|
||||
@apply dark:text-gray-100
|
||||
}
|
||||
|
||||
body {
|
||||
@apply h-screen dark:bg-black
|
||||
}
|
@ -3,7 +3,7 @@ module.exports = {
|
||||
purge: {
|
||||
content: ["./src/**/*.{ts,tsx}"],
|
||||
},
|
||||
darkMode: false, // or 'media' or 'class'
|
||||
darkMode: "media", // or 'media' or 'class'
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user