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 (
|
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 base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
|
|
||||||
|
input {
|
||||||
|
@apply shadow-none placeholder-gray-300 dark:placeholder-gray-700 border-none outline-none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
*:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
* {
|
||||||
|
@apply dark:text-gray-100
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
@apply h-screen dark:bg-black
|
||||||
|
}
|
@ -3,7 +3,7 @@ module.exports = {
|
|||||||
purge: {
|
purge: {
|
||||||
content: ["./src/**/*.{ts,tsx}"],
|
content: ["./src/**/*.{ts,tsx}"],
|
||||||
},
|
},
|
||||||
darkMode: false, // or 'media' or 'class'
|
darkMode: "media", // or 'media' or 'class'
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user