From a3547ba328d29f5a0ff22ea6c1f28fefc82bc8af Mon Sep 17 00:00:00 2001 From: Juan Salvador Zorrilla Date: Fri, 23 May 2025 15:10:08 +0200 Subject: [PATCH 01/16] set up all necessary dependencies and configure project to use TypeScript, Tailwind, Shadcn --- components.json | 21 +++++++++++ eslint.config.js | 33 ----------------- eslint.config.ts | 55 ++++++++++++++++++++++++++++ package.json | 17 +++++++-- src/{App.jsx => App.tsx} | 0 src/index.css | 73 +++++++++++++++++++++++++++++++++++++- src/lib/utils.ts | 6 ++++ src/{main.jsx => main.tsx} | 4 +-- tsconfig.json | 18 ++++++++++ vite.config.js | 7 ---- vite.config.ts | 14 ++++++++ 11 files changed, 202 insertions(+), 46 deletions(-) create mode 100644 components.json delete mode 100644 eslint.config.js create mode 100644 eslint.config.ts rename src/{App.jsx => App.tsx} (100%) create mode 100644 src/lib/utils.ts rename src/{main.jsx => main.tsx} (61%) create mode 100644 tsconfig.json delete mode 100644 vite.config.js create mode 100644 vite.config.ts diff --git a/components.json b/components.json new file mode 100644 index 0000000..7dfce35 --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js deleted file mode 100644 index 2fd24fd..0000000 --- a/eslint.config.js +++ /dev/null @@ -1,33 +0,0 @@ -import js from '@eslint/js' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import globals from 'globals' - -export default [ - { ignores: ['dist'] }, - { - files: ['**/*.{js,jsx}'], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - parserOptions: { - ecmaVersion: 'latest', - ecmaFeatures: { jsx: true }, - sourceType: 'module' - } - }, - plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh - }, - rules: { - ...js.configs.recommended.rules, - ...reactHooks.configs.recommended.rules, - 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true } - ] - } - } -] diff --git a/eslint.config.ts b/eslint.config.ts new file mode 100644 index 0000000..2909050 --- /dev/null +++ b/eslint.config.ts @@ -0,0 +1,55 @@ +import js from '@eslint/js' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import globals from 'globals' +import * as tseslint from '@typescript-eslint/eslint-plugin' +import parser from '@typescript-eslint/parser' + +export default [ + { ignores: ['dist'] }, + { + files: ['**/*.{ts,tsx}'], + languageOptions: { + parser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { jsx: true } + }, + globals: globals.browser + }, + plugins: { + '@typescript-eslint': tseslint + }, + rules: { + ...js.configs.recommended.rules, + ...tseslint.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }] + } + }, + { + files: ['**/*.{js,jsx}'], + languageOptions: { + ecmaVersion: 'latest', + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { jsx: true } + }, + globals: globals.browser + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh + }, + rules: { + ...js.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }] + } + } +] diff --git a/package.json b/package.json index caf6289..4593f30 100644 --- a/package.json +++ b/package.json @@ -10,18 +10,29 @@ "preview": "vite preview" }, "dependencies": { + "@tailwindcss/vite": "^4.1.7", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.511.0", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "tailwind-merge": "^3.3.0", + "tailwindcss": "^4.1.7" }, "devDependencies": { "@eslint/js": "^9.21.0", - "@types/react": "^19.0.10", - "@types/react-dom": "^19.0.4", + "@types/node": "^22.15.21", + "@types/react": "^19.1.5", + "@types/react-dom": "^19.1.5", + "@typescript-eslint/eslint-plugin": "^8.32.1", + "@typescript-eslint/parser": "^8.32.1", "@vitejs/plugin-react": "^4.3.4", "eslint": "^9.21.0", "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^15.15.0", + "tw-animate-css": "^1.3.0", + "typescript": "^5.8.3", "vite": "^6.2.0" } } diff --git a/src/App.jsx b/src/App.tsx similarity index 100% rename from src/App.jsx rename to src/App.tsx diff --git a/src/index.css b/src/index.css index f7c0aef..44c80d1 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,74 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + :root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + --background: oklch(93.46% 0.0305 255.11); + --secondary-background: oklch(100% 0 0); + --foreground: oklch(0% 0 0); + --main-foreground: oklch(0% 0 0); + --main: oklch(67.47% 0.1726 259.49); + --border: oklch(0% 0 0); + --ring: oklch(0% 0 0); + --overlay: oklch(0% 0 0 / 0.8); + --shadow: 4px 4px 0px 0px var(--border); + --chart-1: #5294FF; + --chart-2: #FF4D50; + --chart-3: #FACC00; + --chart-4: #05E17A; + --chart-5: #7A83FF; + --chart-active-dot: #000; } + +.dark { + --background: oklch(29.23% 0.0626 270.49); + --secondary-background: oklch(23.93% 0 0); + --foreground: oklch(92.49% 0 0); + --main-foreground: oklch(0% 0 0); + --main: oklch(67.47% 0.1726 259.49); + --border: oklch(0% 0 0); + --ring: oklch(100% 0 0); + --shadow: 4px 4px 0px 0px var(--border); + --chart-1: #5294FF; + --chart-2: #FF6669; + --chart-3: #E0B700; + --chart-4: #04C86D; + --chart-5: #7A83FF; + --chart-active-dot: #fff; +} + +@theme inline { + --color-main: var(--main); + --color-background: var(--background); + --color-secondary-background: var(--secondary-background); + --color-foreground: var(--foreground); + --color-main-foreground: var(--main-foreground); + --color-border: var(--border); + --color-overlay: var(--overlay); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + + --spacing-boxShadowX: 4px; + --spacing-boxShadowY: 4px; + --spacing-reverseBoxShadowX: -4px; + --spacing-reverseBoxShadowY: -4px; + --radius-base: 5px; + --shadow-shadow: var(--shadow); + --font-weight-base: 500; + --font-weight-heading: 700; +} + +@layer base { + body { + @apply text-foreground font-base bg-background; + } + + h1, h2, h3, h4, h5, h6{ + @apply font-heading; + } +} \ No newline at end of file diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..bd0c391 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/src/main.jsx b/src/main.tsx similarity index 61% rename from src/main.jsx rename to src/main.tsx index 1b8ffe9..82c5feb 100644 --- a/src/main.jsx +++ b/src/main.tsx @@ -1,11 +1,11 @@ import React from 'react' import ReactDOM from 'react-dom/client' -import { App } from './App.jsx' +import { App } from './App' import './index.css' -ReactDOM.createRoot(document.getElementById('root')).render( +ReactDOM.createRoot(document.getElementById('root')!).render( diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5b5311f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "react-jsx", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src"] +} diff --git a/vite.config.js b/vite.config.js deleted file mode 100644 index ba24244..0000000 --- a/vite.config.js +++ /dev/null @@ -1,7 +0,0 @@ -import react from '@vitejs/plugin-react' -import { defineConfig } from 'vite' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react()] -}) diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..8d7e074 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,14 @@ +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' +import tailwindcss from '@tailwindcss/vite' +import path from "path" + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react(), tailwindcss()], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, +}) \ No newline at end of file From 0edff44b5e241983e0cdb550ea8c8458769e78c4 Mon Sep 17 00:00:00 2001 From: Juan Salvador Zorrilla Date: Fri, 23 May 2025 15:59:47 +0200 Subject: [PATCH 02/16] add tailwind config file --- src/App.tsx | 2 +- src/index.css | 6 ++++++ tailwind.config.js | 11 +++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 tailwind.config.js diff --git a/src/App.tsx b/src/App.tsx index 5427540..b1b7076 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ export const App = () => { return ( -

React Boilerplate

+

To-Do List (Neobrutalism Edition)

) } diff --git a/src/index.css b/src/index.css index 44c80d1..3d455dc 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,4 @@ +@import url('https://fonts.googleapis.com/css2?family=Lexend+Mega:wght@100..900&display=swap'); @import "tailwindcss"; @import "tw-animate-css"; @@ -66,6 +67,11 @@ @layer base { body { @apply text-foreground font-base bg-background; + background-color: var(--background); + + background-image: + radial-gradient(circle, #ccc 1px, transparent 1px); + background-size: 20px 20px; } h1, h2, h3, h4, h5, h6{ diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..c163e8e --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,11 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + theme: { + extend: { + fontFamily: { + heading: ['Lexend Mega', 'sans-serif'], + base: ['Lexend Mega', 'sans-serif'], + } + }, + }, +} \ No newline at end of file From 15a4ea4c0bda6c8ccb3cb8753427f167288cd065 Mon Sep 17 00:00:00 2001 From: Juan Salvador Zorrilla Date: Fri, 23 May 2025 17:11:43 +0200 Subject: [PATCH 03/16] add fonts --- index.html | 3 ++- src/App.tsx | 5 ++++- src/index.css | 1 - tailwind.config.js | 15 +++++++++++---- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/index.html b/index.html index f7ac4e4..9e5489a 100644 --- a/index.html +++ b/index.html @@ -3,8 +3,9 @@ + - Todo + To Do List
diff --git a/src/App.tsx b/src/App.tsx index b1b7076..776e1ba 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,8 @@ export const App = () => { return ( -

To-Do List (Neobrutalism Edition)

+ <> +

To-Do List (Neobrutalism Edition)

+
Is this blue?
+ ) } diff --git a/src/index.css b/src/index.css index 3d455dc..ce85e30 100644 --- a/src/index.css +++ b/src/index.css @@ -1,4 +1,3 @@ -@import url('https://fonts.googleapis.com/css2?family=Lexend+Mega:wght@100..900&display=swap'); @import "tailwindcss"; @import "tw-animate-css"; diff --git a/tailwind.config.js b/tailwind.config.js index c163e8e..2d367c8 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,11 +1,18 @@ /** @type {import('tailwindcss').Config} */ module.exports = { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], theme: { extend: { + colors: { + testblue: "#00f", + }, fontFamily: { - heading: ['Lexend Mega', 'sans-serif'], - base: ['Lexend Mega', 'sans-serif'], - } + heading: ['"Lexend Mega"', 'sans-serif'], + }, }, }, -} \ No newline at end of file + plugins: [], +}; From 15712ef5fa7b988bd9122d4485ed2b1b2985e000 Mon Sep 17 00:00:00 2001 From: Juan Salvador Zorrilla Date: Sat, 24 May 2025 00:04:57 +0200 Subject: [PATCH 04/16] work on the UI --- index.html | 1 - package.json | 2 + src/App.tsx | 7 ++- src/components/header/Header.tsx | 9 ++++ src/components/main/Main.tsx | 24 +++++++++ src/components/ui/button.tsx | 56 +++++++++++++++++++ src/components/ui/card.tsx | 92 ++++++++++++++++++++++++++++++++ src/components/ui/checkbox.tsx | 31 +++++++++++ src/components/ui/input.tsx | 19 +++++++ src/index.css | 21 +++++--- tailwind.config.js | 18 ------- 11 files changed, 253 insertions(+), 27 deletions(-) create mode 100644 src/components/header/Header.tsx create mode 100644 src/components/main/Main.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/card.tsx create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/input.tsx delete mode 100644 tailwind.config.js diff --git a/index.html b/index.html index 9e5489a..9a8c545 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,6 @@ - To Do List diff --git a/package.json b/package.json index 4593f30..7f45b2f 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "preview": "vite preview" }, "dependencies": { + "@radix-ui/react-checkbox": "^1.3.2", + "@radix-ui/react-slot": "^1.2.3", "@tailwindcss/vite": "^4.1.7", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/src/App.tsx b/src/App.tsx index 776e1ba..90438fc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,11 @@ +import Header from "./components/header/Header" +import Main from "./components/main/Main" + export const App = () => { return ( <> -

To-Do List (Neobrutalism Edition)

-
Is this blue?
+
+
) } diff --git a/src/components/header/Header.tsx b/src/components/header/Header.tsx new file mode 100644 index 0000000..8345733 --- /dev/null +++ b/src/components/header/Header.tsx @@ -0,0 +1,9 @@ +const Header = () => { + return ( +
+

To-Do List

+
+ ) +} + +export default Header \ No newline at end of file diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx new file mode 100644 index 0000000..e72b40a --- /dev/null +++ b/src/components/main/Main.tsx @@ -0,0 +1,24 @@ +import { Card } from "@/components/ui/card" +import { Button }from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Checkbox } from "@/components/ui/checkbox" + +const Main = () => { + return ( +
+ +

Tasks:

+ +
+ +

Mark as done

+
+ +
+
+ ) +} + +export default Main \ No newline at end of file diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..f91affd --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,56 @@ +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import * as React from "react" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-base text-sm font-base ring-offset-white transition-all gap-2 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-black focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: + "text-main-foreground bg-main border-2 border-border shadow-shadow hover:translate-x-boxShadowX hover:translate-y-boxShadowY hover:shadow-none", + noShadow: "text-main-foreground bg-main border-2 border-border", + neutral: + "bg-secondary-background text-foreground border-2 border-border shadow-shadow hover:translate-x-boxShadowX hover:translate-y-boxShadowY hover:shadow-none", + reverse: + "text-main-foreground bg-main border-2 border-border hover:translate-x-reverseBoxShadowX hover:translate-y-reverseBoxShadowY hover:shadow-shadow", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 px-3", + lg: "h-11 px-8", + icon: "size-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +) + +function Button({ + className, + variant, + size, + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot : "button" + + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..d5a3851 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,92 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Card({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, + CardAction, +} diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..9c11cde --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,31 @@ +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Check } from "lucide-react" + +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Checkbox({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + + ) +} + +export { Checkbox } diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..08ca29a --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,19 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ) +} + +export { Input } diff --git a/src/index.css b/src/index.css index ce85e30..3b92d23 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,4 @@ +@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap'); @import "tailwindcss"; @import "tw-animate-css"; @@ -61,19 +62,27 @@ --shadow-shadow: var(--shadow); --font-weight-base: 500; --font-weight-heading: 700; + --font-base: DM Sans, "sans-serif"; + --font-heading: DM Sans, "sans-serif"; } @layer base { body { - @apply text-foreground font-base bg-background; - background-color: var(--background); - - background-image: - radial-gradient(circle, #ccc 1px, transparent 1px); - background-size: 20px 20px; + @apply text-foreground font-base bg-secondary-background; } h1, h2, h3, h4, h5, h6{ @apply font-heading; + font-weight: var(--font-weight-heading) + } +} + +@layer utilities { + .bg-grid-light { + background-image: + repeating-linear-gradient(0deg, #e5e7eb 0, #e5e7eb 1px, transparent 1px, transparent 40px), + repeating-linear-gradient(90deg, #e5e7eb 0, #e5e7eb 1px, transparent 1px, transparent 40px); + background-size: 40px 40px; + background-position: 0 0; } } \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js deleted file mode 100644 index 2d367c8..0000000 --- a/tailwind.config.js +++ /dev/null @@ -1,18 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: [ - "./index.html", - "./src/**/*.{js,ts,jsx,tsx}", - ], - theme: { - extend: { - colors: { - testblue: "#00f", - }, - fontFamily: { - heading: ['"Lexend Mega"', 'sans-serif'], - }, - }, - }, - plugins: [], -}; From be3189ab879680b49fc9d257dda2e7d70b561265 Mon Sep 17 00:00:00 2001 From: Juan Salvador Zorrilla Date: Sat, 24 May 2025 17:58:27 +0200 Subject: [PATCH 05/16] basic ui finished --- src/App.tsx | 6 +++--- src/components/main/EmptyState.tsx | 9 +++++++++ src/components/main/ListItem.tsx | 12 ++++++++++++ src/components/main/Main.tsx | 16 +++++++--------- 4 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 src/components/main/EmptyState.tsx create mode 100644 src/components/main/ListItem.tsx diff --git a/src/App.tsx b/src/App.tsx index 90438fc..c9d08d3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,9 +3,9 @@ import Main from "./components/main/Main" export const App = () => { return ( - <> +
-
- +
+
) } diff --git a/src/components/main/EmptyState.tsx b/src/components/main/EmptyState.tsx new file mode 100644 index 0000000..7f38123 --- /dev/null +++ b/src/components/main/EmptyState.tsx @@ -0,0 +1,9 @@ +const EmptyState = () => { + return ( +
+

Hooray, no active tasks!

+
+ ) +} + +export default EmptyState \ No newline at end of file diff --git a/src/components/main/ListItem.tsx b/src/components/main/ListItem.tsx new file mode 100644 index 0000000..3b06a9e --- /dev/null +++ b/src/components/main/ListItem.tsx @@ -0,0 +1,12 @@ +import { Checkbox } from "@/components/ui/checkbox" + +const ListItem = () => { + return ( +
+ +

Mark as done or whatever you think is good for example.

+
+) +} + +export default ListItem \ No newline at end of file diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index e72b40a..0ae816d 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -1,21 +1,19 @@ import { Card } from "@/components/ui/card" import { Button }from "@/components/ui/button" import { Input } from "@/components/ui/input" -import { Checkbox } from "@/components/ui/checkbox" +import ListItem from "./ListItem" +import EmptyState from "./EmptyState" const Main = () => { return (
- -

Tasks:

- -
- -

Mark as done

-
- +
) From 71a8019f3c8b6b5e7607227ad7974c9a3cea4769 Mon Sep 17 00:00:00 2001 From: Juan Salvador Zorrilla Date: Sat, 24 May 2025 22:38:46 +0200 Subject: [PATCH 06/16] added zustand to dependencies --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 7f45b2f..c412e7b 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "tailwind-merge": "^3.3.0", - "tailwindcss": "^4.1.7" + "tailwindcss": "^4.1.7", + "zustand": "^5.0.5" }, "devDependencies": { "@eslint/js": "^9.21.0", From 4ffc7a7375fc7d4821f52e044f7f3d3ae289ebdb Mon Sep 17 00:00:00 2001 From: Juan Salvador Zorrilla Date: Sat, 24 May 2025 23:07:48 +0200 Subject: [PATCH 07/16] create todo store --- src/stores/useTodoStore.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/stores/useTodoStore.ts diff --git a/src/stores/useTodoStore.ts b/src/stores/useTodoStore.ts new file mode 100644 index 0000000..5df44f6 --- /dev/null +++ b/src/stores/useTodoStore.ts @@ -0,0 +1,32 @@ +import { create } from 'zustand' + +type Todo = { + id: number; + text: string; + completed: boolean; +}; + +type TodoState = { + todos: Todo[]; + addTodo: (text: string) => void; + removeTodo: (id: number) => void; + toggleTodo: (id: number) => void; +}; + +const useTodoStore = create((set) => ({ + todos: [], + addTodo: (text) => + set((state) => ({ + todos: [...state.todos, {id: Date.now(), text, completed: false}], + })), + removeTodo: (id) => + set((state) => ({ + todos: state.todos.filter((todo) => todo.id !== id), + })), + toggleTodo: (id) => + set((state) => ({ + todos: state.todos.map((todo) => + todo.id === id ? { ...todo, completed: !todo.completed } : todo + ), + })), +})) \ No newline at end of file From 771d3b8aede1f36617904bfb063756ee127ba2f7 Mon Sep 17 00:00:00 2001 From: Juan Salvador Zorrilla Date: Sun, 25 May 2025 00:36:49 +0200 Subject: [PATCH 08/16] add task insertion and completion functionality using global store with zustand --- src/components/main/ListItem.tsx | 19 +++++++++++--- src/components/main/Main.tsx | 44 ++++++++++++++++++++++++++++---- src/stores/useTodoStore.ts | 4 ++- 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/components/main/ListItem.tsx b/src/components/main/ListItem.tsx index 3b06a9e..a8a2b66 100644 --- a/src/components/main/ListItem.tsx +++ b/src/components/main/ListItem.tsx @@ -1,10 +1,23 @@ import { Checkbox } from "@/components/ui/checkbox" -const ListItem = () => { +type ListItemProps = { + id: number; + text: string; + completed: boolean; + onToggle: (id: number) => void; +}; + +const ListItem = ({ id, text, completed, onToggle }: ListItemProps) => { return (
- -

Mark as done or whatever you think is good for example.

+ onToggle(id)} + /> +

+ {text} +

) } diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index 0ae816d..a2af345 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -1,19 +1,53 @@ +import { useState } from "react" import { Card } from "@/components/ui/card" import { Button }from "@/components/ui/button" import { Input } from "@/components/ui/input" import ListItem from "./ListItem" import EmptyState from "./EmptyState" +import useTodoStore from "../../stores/useToDoStore" const Main = () => { + + const [text, setText] = useState(""); + const todos = useTodoStore((state) => state.todos); + const addTodo = useTodoStore((state) => state.addTodo); + const toggleTodo = useTodoStore((state) => state.toggleTodo); + + const handleAdd = () => { + if (text.trim()) { + addTodo(text); + setText(""); + } + }; + return (

Tasks:

- - - + + setText(e.target.value)} + /> + + + {todos.length === 0 ? ( + + ) : ( +
+ {todos.map((todo) => ( + + ))} +
+ )}
) diff --git a/src/stores/useTodoStore.ts b/src/stores/useTodoStore.ts index 5df44f6..c0ed7c3 100644 --- a/src/stores/useTodoStore.ts +++ b/src/stores/useTodoStore.ts @@ -29,4 +29,6 @@ const useTodoStore = create((set) => ({ todo.id === id ? { ...todo, completed: !todo.completed } : todo ), })), -})) \ No newline at end of file +})) + +export default useTodoStore \ No newline at end of file From f9c2900f2932140e5f5449168607801f7903beb1 Mon Sep 17 00:00:00 2001 From: Juan Salvador Zorrilla Date: Sun, 25 May 2025 23:17:11 +0200 Subject: [PATCH 09/16] finish adding all required functionality --- src/components/main/ListItem.tsx | 24 ++++++++++++++++++------ src/components/main/Main.tsx | 20 ++++++++++++++------ src/stores/useTodoStore.ts | 20 ++++++++++++++++---- 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/src/components/main/ListItem.tsx b/src/components/main/ListItem.tsx index a8a2b66..63a67ec 100644 --- a/src/components/main/ListItem.tsx +++ b/src/components/main/ListItem.tsx @@ -5,16 +5,28 @@ type ListItemProps = { text: string; completed: boolean; onToggle: (id: number) => void; + onDelete: (id: number) => void; + deleteMode: boolean; }; -const ListItem = ({ id, text, completed, onToggle }: ListItemProps) => { +const ListItem = ({ id, text, completed, onToggle, onDelete, deleteMode }: ListItemProps) => { return (
- onToggle(id)} - /> +
+ {!deleteMode ? ( + onToggle(id)} + /> + ) : ( + + )} +

{text}

diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index a2af345..37f9640 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -1,10 +1,10 @@ -import { useState } from "react" +import { Button } from "@/components/ui/button" import { Card } from "@/components/ui/card" -import { Button }from "@/components/ui/button" import { Input } from "@/components/ui/input" -import ListItem from "./ListItem" +import { useState } from "react" +import useTodoStore from "../../stores/useTodoStore" import EmptyState from "./EmptyState" -import useTodoStore from "../../stores/useToDoStore" +import ListItem from "./ListItem" const Main = () => { @@ -12,6 +12,9 @@ const Main = () => { const todos = useTodoStore((state) => state.todos); const addTodo = useTodoStore((state) => state.addTodo); const toggleTodo = useTodoStore((state) => state.toggleTodo); + const toggleDeleteMode = useTodoStore((state) => state.toggleDeleteMode); + const deleteMode = useTodoStore((state) => state.deleteMode); + const removeTodo = useTodoStore((state) => state.removeTodo); const handleAdd = () => { if (text.trim()) { @@ -23,7 +26,7 @@ const Main = () => { return (
-

Tasks:

+

Tasks ({todos.length})

{ value={text} onChange={(e) => setText(e.target.value)} /> - +
+ + +
{todos.length === 0 ? ( @@ -44,6 +50,8 @@ const Main = () => { text={todo.text} completed={todo.completed} onToggle={toggleTodo} + onDelete={removeTodo} + deleteMode={deleteMode} /> ))}
diff --git a/src/stores/useTodoStore.ts b/src/stores/useTodoStore.ts index c0ed7c3..05cf649 100644 --- a/src/stores/useTodoStore.ts +++ b/src/stores/useTodoStore.ts @@ -8,21 +8,33 @@ type Todo = { type TodoState = { todos: Todo[]; + deleteMode: boolean; addTodo: (text: string) => void; removeTodo: (id: number) => void; toggleTodo: (id: number) => void; + toggleDeleteMode: () => void; }; const useTodoStore = create((set) => ({ todos: [], + deleteMode: false, + toggleDeleteMode: () => + set((state: TodoState): Partial => ({ + deleteMode: !state.deleteMode + })), addTodo: (text) => set((state) => ({ - todos: [...state.todos, {id: Date.now(), text, completed: false}], + todos: [{id: Date.now(), text, completed: false}, ...state.todos], + deleteMode: false, })), removeTodo: (id) => - set((state) => ({ - todos: state.todos.filter((todo) => todo.id !== id), - })), + set((state) => { + const updatedTodos = state.todos.filter((todo) => todo.id !== id); + return { + todos: updatedTodos, + deleteMode: updatedTodos.length === 0 ? false : state.deleteMode, + }; + }), toggleTodo: (id) => set((state) => ({ todos: state.todos.map((todo) => From d4cc2a3f2dae577ff3a5211c7d691e9696308268 Mon Sep 17 00:00:00 2001 From: Juan Salvador Zorrilla Date: Sun, 25 May 2025 23:49:15 +0200 Subject: [PATCH 10/16] add growth constraints to elements in task list --- src/components/main/EmptyState.tsx | 2 +- src/components/main/ListItem.tsx | 3 ++- src/components/main/Main.tsx | 40 ++++++++++++++++++++++-------- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/components/main/EmptyState.tsx b/src/components/main/EmptyState.tsx index 7f38123..b8f3794 100644 --- a/src/components/main/EmptyState.tsx +++ b/src/components/main/EmptyState.tsx @@ -1,6 +1,6 @@ const EmptyState = () => { return ( -
+

Hooray, no active tasks!

) diff --git a/src/components/main/ListItem.tsx b/src/components/main/ListItem.tsx index 63a67ec..6f43f72 100644 --- a/src/components/main/ListItem.tsx +++ b/src/components/main/ListItem.tsx @@ -15,13 +15,14 @@ const ListItem = ({ id, text, completed, onToggle, onDelete, deleteMode }: ListI
{!deleteMode ? ( onToggle(id)} /> ) : ( diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index 37f9640..05c8a7f 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -27,22 +27,42 @@ const Main = () => {

Tasks ({todos.length})

+ +
+ + setText(e.target.value)} + /> + +
+ + + + + +
- setText(e.target.value)} - /> -
- -
+ {todos.length === 0 ? ( ) : ( -
+
{todos.map((todo) => ( Date: Sun, 25 May 2025 23:53:49 +0200 Subject: [PATCH 11/16] limit card size to 600px --- src/components/main/Main.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index 05c8a7f..9d37330 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -25,7 +25,7 @@ const Main = () => { return (
- +

Tasks ({todos.length})

From ade658efb5bcd3c8e83eb3b8fa0b70210508499c Mon Sep 17 00:00:00 2001 From: Juan Salvador Zorrilla Date: Mon, 26 May 2025 00:22:45 +0200 Subject: [PATCH 12/16] add read me --- README.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d1c68b5..6eb24d2 100644 --- a/README.md +++ b/README.md @@ -1 +1,31 @@ -# Todo \ No newline at end of file +# 📝 To-Do List App + +This is a simple and responsive to-do list web application built with **React**, **TypeScript**, **Zustand**, and **Tailwind CSS**. It makes use of the component library **neobrutalism.dev**, which is built on top of **shadcn/ui** for a minimalistic, stylish design. The app allows users to manage tasks with features like adding, completing, and deleting tasks. + +--- + +## ✨ Features + +- ✅ **Add tasks** to your task list +- 🔁 **Toggle task completion** +- ❌ **Delete individual tasks** in a dedicated delete mode +- 📊 **Task counter** shows how many tasks you have on your list +- 📱 **Responsive design** that works across all screen sizes + +--- + +## 🛠️ Tech Stack + +- **React** (with Vite) +- **TypeScript** +- **Zustand** for global state management +- **Tailwind CSS** for styling +- **shadcn/ui** and [neobrutalism.dev](https://neobrutalism.dev) components + +--- + +## 🔗 Link + +https://neobrutalist-todo.netlify.app/ + +--- From c99114518e428cd3b6d9d7215e2c5945906515f6 Mon Sep 17 00:00:00 2001 From: Juan Salvador Zorrilla Date: Mon, 26 May 2025 11:22:03 +0200 Subject: [PATCH 13/16] change empty state colors for better a11y --- src/components/main/EmptyState.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/main/EmptyState.tsx b/src/components/main/EmptyState.tsx index b8f3794..ceb2026 100644 --- a/src/components/main/EmptyState.tsx +++ b/src/components/main/EmptyState.tsx @@ -1,7 +1,7 @@ const EmptyState = () => { return ( -
-

Hooray, no active tasks!

+
+

Hooray, no active tasks!

) } From b8ac152c79524eca47ab8376544b85da3f8af00a Mon Sep 17 00:00:00 2001 From: Juan Salvador Zorrilla Date: Mon, 26 May 2025 11:53:07 +0200 Subject: [PATCH 14/16] remove extra space and unnecessary scrolling --- src/App.tsx | 2 +- src/components/main/Main.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index c9d08d3..d0a1600 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,7 +3,7 @@ import Main from "./components/main/Main" export const App = () => { return ( -
+
diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index 9d37330..9e9422e 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -24,7 +24,7 @@ const Main = () => { }; return ( -
+

Tasks ({todos.length})

From 87eb050e23ddac25d00699d91e6fe4d5edfb51bf Mon Sep 17 00:00:00 2001 From: Juan Salvador Zorrilla Date: Tue, 27 May 2025 14:31:54 +0200 Subject: [PATCH 15/16] add media queries for button sizes --- src/components/main/Main.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index 9e9422e..da8d5ad 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -40,18 +40,18 @@ const Main = () => {
From c2a95c176760fcfc7a01612fd4f7066665469c9c Mon Sep 17 00:00:00 2001 From: Juan Salvador Zorrilla Date: Tue, 27 May 2025 16:19:12 +0200 Subject: [PATCH 16/16] add custom utility class to make sure long words don't break the style --- src/components/main/ListItem.tsx | 2 +- src/index.css | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/main/ListItem.tsx b/src/components/main/ListItem.tsx index 6f43f72..b42671c 100644 --- a/src/components/main/ListItem.tsx +++ b/src/components/main/ListItem.tsx @@ -28,7 +28,7 @@ const ListItem = ({ id, text, completed, onToggle, onDelete, deleteMode }: ListI )}
-

+

{text}

diff --git a/src/index.css b/src/index.css index 3b92d23..fe75a64 100644 --- a/src/index.css +++ b/src/index.css @@ -85,4 +85,8 @@ background-size: 40px 40px; background-position: 0 0; } + .break-word { + overflow-wrap: break-word; + word-break: break-word; + } } \ No newline at end of file