diff --git a/README.md b/README.md index d1c68b5..ef7491d 100644 --- a/README.md +++ b/README.md @@ -1 +1,132 @@ -# Todo \ No newline at end of file +# 🦊 FocusDen + +> _Let your hands create what your eyes fear to imagine._ + +A calm, minimalist productivity app built with **React**, **Zustand**, and **styled-components**. +FocusDen helps you stay organized and present β€” without noise or clutter. + +🌐 **Live demo:** [https://focusden.netlify.app](https://focusden.netlify.app) + +--- + +## πŸ–ΌοΈ Preview + +![FocusDen app screenshot](./src/assets/focusden-preview.jpg) + +--- + +## ✨ Features + +- βœ… Add, complete, and delete tasks +- βœ… Filter by **All / Active / Completed** +- βœ… Optional **due date** and automatic β€œOverdue” indicator +- βœ… Character counter on new tasks +- βœ… Persistent data with `localStorage` +- βœ… Light & dark mode toggle +- βœ… Clean, responsive layout (320px–1600px) +- βœ… 95+ Lighthouse accessibility score +- 🦊 Minimalist, focus-friendly design + +--- + +## 🧠 Tech Stack + +| Technology | Purpose | +|-------------|----------| +| βš›οΈ React (Vite) | Core UI framework | +| πŸͺ£ Zustand | Global state management (no prop drilling) | +| πŸ’… styled-components | Component-scoped styling | +| πŸ•’ date-fns | Date formatting & overdue logic | +| πŸŒ— localStorage | Persistent task storage | +| πŸ§ͺ ESLint + Vite | Clean, fast developer setup | + +--- + +## πŸ—‚οΈ Folder Structure + + src/ + β”œβ”€ assets/ + β”‚ β”œβ”€ favicon.jpg + β”‚ └─ focusden-preview.png + β”œβ”€ components/ + β”‚ β”œβ”€ TodoForm.jsx + β”‚ β”œβ”€ TodoList.jsx + β”‚ β”œβ”€ TodoItem.jsx + β”‚ β”œβ”€ EmptyState.jsx + β”‚ └─ Footer.jsx + β”œβ”€ store/ + β”‚ β”œβ”€ useTodoStore.js + β”‚ └─ useThemeStore.js + β”œβ”€ styles/ + β”‚ β”œβ”€ GlobalStyles.js + β”‚ └─ media.js + β”œβ”€ App.jsx + └─ main.jsx + +--- + +## πŸͺ„ Getting Started + +1️⃣ Install dependencies +β†’ Run: `npm install` + +2️⃣ Start the app locally +β†’ Run: `npm run dev` + +3️⃣ Build for production +β†’ Run: `npm run build` + +Then open the generated `/dist` folder in your browser. + +--- + +## πŸš€ Stretch Goals + +πŸ•“ Filter tasks by **due date** or **overdue** +🏷️ Add **tags / categories** +πŸ”” Add **reminders or notifications** +☁️ Sync tasks with a backend or cloud API + +--- + +## πŸ“± Responsiveness + +| Device | Example width | Behavior | +|---------|----------------|-----------| +| πŸ“± Mobile | up to 480px | Stacked layout, larger tap areas | +| πŸ’» Tablet | β‰₯ 768px | Balanced grid, adaptive text | +| πŸ–₯️ Desktop | β‰₯ 1024px | Fixed-width centered container | +| πŸ–₯️ XL screens | β‰₯ 1440px | Fluid, maximum readability | + +--- + +## β™Ώ Accessibility + +βœ” Visible focus states and proper labels +βœ” `aria-live` announcements for task counts +βœ” Sufficient color contrast (WCAG AA) +βœ” Keyboard-friendly navigation +βœ” Semantic HTML structure + +--- + +## πŸ‘©β€πŸ’» Author + +Made with 🍡, 🎧, curiosity, and a generous dose of AI magic by Ulrika Einebrant. +Frontend developer passionate about clean design, accessibility, and calm user experiences. +β€œLet your hands create what your eyes fear to imagine.” + +--- + +## πŸͺΆ License + +This project is open source and available under the **MIT License**. + +--- + +## πŸ’« Connect + +πŸ”— **Live app:** [focusden.netlify.app](https://focusden.netlify.app) +πŸ’» **GitHub repo:** [github.com/yourusername/focusden](https://github.com/yourusername/focusden) +🧭 **Portfolio:** [ulrikasportfolio.netlify.app](https://ulrikasportfolio.netlify.app/) +πŸ’Ό **LinkedIn:** [ulrika-einebrant](https://www.linkedin.com/in/ulrika-einebrant/) diff --git a/index.html b/index.html index f7ac4e4..6c85279 100644 --- a/index.html +++ b/index.html @@ -1,16 +1,27 @@ - - - - - Todo - - -
- - - + + + + + + FocusDen + + + +
+ + + + \ No newline at end of file diff --git a/package.json b/package.json index caf6289..15239cc 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,12 @@ "preview": "vite preview" }, "dependencies": { + "date-fns": "^4.1.0", + "nanoid": "^5.1.6", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "styled-components": "^6.1.19", + "zustand": "^5.0.8" }, "devDependencies": { "@eslint/js": "^9.21.0", diff --git a/public/favicon.jpg b/public/favicon.jpg new file mode 100644 index 0000000..3dac53c Binary files /dev/null and b/public/favicon.jpg differ diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index 5427540..48e0215 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,132 @@ -export const App = () => { +// src/App.jsx +import { useState } from 'react' +import styled from 'styled-components' +import GlobalStyles from './styles/GlobalStyles.js' +import { useTodoStore } from './store/useTodoStore.js' +import { useThemeStore } from './store/useThemeStore.js' // ⬅️ NEW +import TodoForm from './components/TodoForm.jsx' +import TodoList from './components/TodoList.jsx' +import EmptyState from './components/EmptyState.jsx' +import Footer from './components/Footer.jsx' +import foxIcon from './assets/favicon.jpg' + +const Shell = styled.main` + max-width: 720px; margin: 0 auto; padding: 24px; min-height: 100dvh; display: grid; align-content: start; gap: 18px; +` +const H1 = styled.h1` + margin: 8px 0 4px; + font-size: clamp(28px, 3vw, 40px); + display: flex; + align-items: center; + gap: 10px; + + img { + width: 40px; + height: 40px; + border-radius: 20%; + user-select: none; + } +` + +const Card = styled.section` + display: grid; gap: 12px; +` + +const TopBar = styled.header` + display: flex; align-items: center; justify-content: space-between; gap: 12px; +` + +const ThemeBtn = styled.button` + border: 1px solid #2a3650; + background: transparent; + color: var(--text); + padding: 8px 12px; + border-radius: 12px; + font-weight: 600; +` + +const FilterBar = styled.div` + display: flex; gap: 8px; align-items: center; flex-wrap: wrap; +` +const FilterButton = styled.button` + border: 1px solid #2a3650; + background: ${({ $active }) => ($active ? 'var(--primary)' : 'transparent')}; + color: ${({ $active }) => ($active ? '#fff' : 'var(--text)')}; + padding: 8px 12px; + border-radius: 10px; + font-weight: 600; +` + +export default function App() { + const tasks = useTodoStore(s => s.tasks) + + // THEME: light | dark | auto + const { theme, setTheme } = useThemeStore() + function toggleTheme() { + setTheme(theme === 'dark' ? 'light' : 'dark') + } + + // FILTERS + const [filter, setFilter] = useState('all') + const total = tasks.length + const remaining = tasks.filter(t => !t.completed).length + const filteredTasks = tasks.filter(t => + filter === 'all' ? true : filter === 'active' ? !t.completed : t.completed + ) + return ( -

React Boilerplate

+ <> + + + +
+

+ FocusDen + Fox logo +

+ +

+ Let your hands create what your eyes fear to imagine. +

+
+ + {theme === 'dark' ? 'πŸŒ™ Dark' : 'β˜€οΈ Light'} + + +
+ + + + {/* Filter controls */} + + setFilter('all')} + aria-pressed={filter === 'all'} + $active={filter === 'all'} + >All + + setFilter('active')} + aria-pressed={filter === 'active'} + $active={filter === 'active'} + >Active + + setFilter('completed')} + aria-pressed={filter === 'completed'} + $active={filter === 'completed'} + >Completed + + + Showing {filteredTasks.length} of {total} + + + + + {total === 0 ? : } +