From 703fa7549346bddd9be3176fc7ee368fcaef5913 Mon Sep 17 00:00:00 2001 From: StarKnightt Date: Wed, 30 Apr 2025 13:27:15 +0530 Subject: [PATCH] feat(ui): enhance toast notifications with improved styling and interactions - Add consistent styling for success/error states - Improve toast animations and transitions - Add contextual icons for different actions - Optimize toast positioning and timing - Ensure proper theme integration for dark/light modes --- index.html | 3 + package-lock.json | 12 +- package.json | 3 +- src/App.module.css | 44 +- src/App.tsx | 4 +- src/components/ConfirmDialog.module.css | 44 +- src/components/FullscreenEditor.module.css | 131 +++++- src/components/FullscreenEditor.tsx | 8 +- src/components/HistoryPanel.module.css | 501 ++++++++++++++------- src/components/HistoryPanel.tsx | 190 ++++---- src/components/ThemeToggle.module.css | 25 + src/components/ThemeToggle.tsx | 20 + src/components/Timer.module.css | 474 ++++++++++++------- src/components/Timer.tsx | 173 ++++--- src/components/TodoPanel.module.css | 161 +++++++ src/components/TodoPanel.tsx | 92 ++++ src/styles/theme.css | 83 ++++ 17 files changed, 1373 insertions(+), 595 deletions(-) create mode 100644 src/components/ThemeToggle.module.css create mode 100644 src/components/ThemeToggle.tsx create mode 100644 src/components/TodoPanel.module.css create mode 100644 src/components/TodoPanel.tsx create mode 100644 src/styles/theme.css diff --git a/index.html b/index.html index 441eb09..d896dfc 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,9 @@ + + + CleanType - Modern Text Editor diff --git a/package-lock.json b/package-lock.json index 9dd8d16..e16c61f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,8 @@ "date-fns": "^4.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-hot-toast": "^2.4.1" + "react-hot-toast": "^2.4.1", + "react-icons": "^5.5.0" }, "devDependencies": { "@tauri-apps/cli": "^1.6.3", @@ -1741,6 +1742,15 @@ "react-dom": ">=16" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", diff --git a/package.json b/package.json index 89fb22c..c51abad 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "date-fns": "^4.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-hot-toast": "^2.4.1" + "react-hot-toast": "^2.4.1", + "react-icons": "^5.5.0" }, "devDependencies": { "@tauri-apps/cli": "^1.6.3", diff --git a/src/App.module.css b/src/App.module.css index 6f0cf6d..0471507 100644 --- a/src/App.module.css +++ b/src/App.module.css @@ -1,33 +1,33 @@ -.app { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; +.container { + min-height: 100vh; + width: 100%; + background: var(--bg-primary); + color: var(--color-primary); + transition: var(--transition-base); display: flex; flex-direction: column; - overflow: hidden; - transition: all 0.15s ease; } -.darkTheme { - background-color: #1a1a1a; - color: rgba(255, 255, 255, 0.9); - --bg-color: #1a1a1a; - --text-color: rgba(255, 255, 255, 0.9); - --nav-bg: rgba(0, 0, 0, 0.85); - --border-color: rgba(255, 255, 255, 0.1); +.nav { + position: sticky; + top: 0; + z-index: 10; + background: var(--nav-bg); + backdrop-filter: blur(8px); + border-bottom: 1px solid var(--border-color); + padding: var(--spacing-md); } -.lightTheme { - background-color: rgb(255, 252, 242); - color: rgba(0, 0, 0, 0.9); - --bg-color: rgb(255, 252, 242); - --text-color: rgba(0, 0, 0, 0.9); - --nav-bg: rgba(255, 252, 242, 0.95); - --border-color: rgba(0, 0, 0, 0.1); +.main { + flex: 1; + padding: var(--spacing-lg); + max-width: 1200px; + margin: 0 auto; + width: 100%; } +/* Remove theme-specific classes as we're using data-theme now */ + /* Mobile styles */ @media (max-width: 768px) { .app { diff --git a/src/App.tsx b/src/App.tsx index 8980d6a..8e8e3cf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,6 +3,7 @@ import { FullscreenEditor } from "./components/FullscreenEditor"; import { ErrorBoundary } from "./components/ErrorBoundary"; import KeyboardShortcuts from './components/KeyboardShortcuts'; import styles from './App.module.css'; +import './styles/theme.css'; function App() { const [isDarkTheme, setIsDarkTheme] = useState(() => { @@ -15,6 +16,7 @@ function App() { // Save theme changes to localStorage useEffect(() => { localStorage.setItem('cleantype-theme', isDarkTheme ? 'dark' : 'light'); + document.documentElement.setAttribute('data-theme', isDarkTheme ? 'dark' : 'light'); }, [isDarkTheme]); useEffect(() => { @@ -31,7 +33,7 @@ function App() { return ( -
+
setIsDarkTheme(prev => !prev)} diff --git a/src/components/ConfirmDialog.module.css b/src/components/ConfirmDialog.module.css index 8ae910f..ffbb31a 100644 --- a/src/components/ConfirmDialog.module.css +++ b/src/components/ConfirmDialog.module.css @@ -39,67 +39,65 @@ .actions { display: flex; justify-content: flex-end; - gap: 12px; + gap: 16px; + margin-top: 24px; } .cancelButton, .confirmButton { - all: unset; - cursor: pointer; + background: transparent; + border: none; padding: 8px 16px; - border-radius: 6px; + cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s ease; } .cancelButton { - background: rgba(255, 255, 255, 0.1); - color: rgba(255, 255, 255, 0.8); + color: rgba(255, 255, 255, 0.6); } .cancelButton:hover { - background: rgba(255, 255, 255, 0.15); + color: rgba(255, 255, 255, 0.9); } .confirmButton { - background: rgba(255, 59, 48, 0.15); - color: #ff3b30; + color: rgba(255, 59, 48, 0.8); } .confirmButton:hover { - background: rgba(255, 59, 48, 0.25); + color: rgba(255, 59, 48, 1); } -/* Light theme */ -.lightTheme { +/* Properly scoped light theme styling */ +[data-theme="light"] .dialog { background: rgba(255, 255, 255, 0.95); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); } -.lightTheme h3 { +[data-theme="light"] .dialog h3 { color: rgba(0, 0, 0, 0.9); } -.lightTheme p { +[data-theme="light"] .dialog p { color: rgba(0, 0, 0, 0.7); } -.lightTheme .cancelButton { - background: rgba(0, 0, 0, 0.05); - color: rgba(0, 0, 0, 0.8); +[data-theme="light"] .dialog .cancelButton { + color: rgba(0, 0, 0, 0.6); } -.lightTheme .cancelButton:hover { - background: rgba(0, 0, 0, 0.1); +[data-theme="light"] .dialog .cancelButton:hover { + color: rgba(0, 0, 0, 0.9); } -.lightTheme .confirmButton { - background: rgba(255, 59, 48, 0.1); +[data-theme="light"] .dialog .confirmButton { + color: rgba(255, 59, 48, 0.8); } -.lightTheme .confirmButton:hover { - background: rgba(255, 59, 48, 0.15); +[data-theme="light"] .dialog .confirmButton:hover { + color: rgba(255, 59, 48, 1); } @keyframes dialogAppear { diff --git a/src/components/FullscreenEditor.module.css b/src/components/FullscreenEditor.module.css index 2e47192..6ca494d 100644 --- a/src/components/FullscreenEditor.module.css +++ b/src/components/FullscreenEditor.module.css @@ -36,29 +36,103 @@ padding: 2rem; background: transparent; border: none; - outline: none; + outline: none !important; resize: none; line-height: 1.6; color: var(--text-color); font-family: Inter, Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + -webkit-tap-highlight-color: transparent; + user-select: text; +} + +.editor:focus { + outline: none !important; + box-shadow: none !important; +} + +.editor:focus-visible { + outline: none !important; + box-shadow: none !important; +} + +.editor::selection { + background: var(--color-accent); + color: white; + opacity: 0.3; +} + +.editor:focus { + outline: none !important; + box-shadow: none !important; +} + +.editor:focus-visible { + outline: none !important; + box-shadow: none !important; +} + +.darkTheme .editor::selection { + background: rgba(255, 255, 255, 0.2); + color: inherit; +} + +.lightTheme .editor::selection { + background: rgba(0, 0, 0, 0.1); + color: inherit; } .bottomNav { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); height: 60px; - padding: 0 2rem; + padding: 0 1.5rem; display: flex; align-items: center; - justify-content: space-between; - background: transparent; - font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - opacity: 0.85; - transition: opacity 0.2s ease; + gap: 1.5rem; + background: rgba(255, 255, 255, 0.08); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border-radius: 14px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); + font-family: -apple-system, BlinkMacSystemFont, "San Francisco", "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + font-size: 15px; + opacity: 0.9; + transition: opacity 0.2s ease, transform 0.3s ease; + z-index: 1000; } .bottomNav:hover { opacity: 1; + transform: translateX(-50%) scale(1.01); +} + +.bottomNav button { + background: transparent; + border: none; + outline: none; + width: 44px; + height: 44px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.2s ease, transform 0.2s ease, filter 0.2s ease; + cursor: pointer; + font-size: 16px; +} + +.bottomNav button:hover { + background: rgba(255, 255, 255, 0.12); + filter: brightness(1.15); +} + +.bottomNav button:active { + transform: scale(0.97); + filter: brightness(0.95); } .leftControls, @@ -552,11 +626,11 @@ background: rgba(0, 0, 0, 0.85); backdrop-filter: blur(8px); border-radius: 8px; - padding: 8px; + padding: 6px; display: none; flex-direction: column; gap: 4px; - min-width: 160px; + min-width: 140px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); z-index: 100; } @@ -584,9 +658,9 @@ .fontOption { all: unset; cursor: pointer; - padding: 8px 12px; + padding: 6px 10px; border-radius: 4px; - font-size: 15px; + font-size: 14px; transition: all 0.2s ease; display: flex; align-items: center; @@ -606,18 +680,6 @@ font-family: inherit; } -/* Light theme support */ -.lightTheme .fontMenu { - background: rgba(255, 255, 255, 0.95); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); -} - -.lightTheme .fontButton:hover, -.lightTheme .fontOption:hover, -.lightTheme .fontOption.active { - background: rgba(0, 0, 0, 0.05); -} - /* Theme styles */ .darkTheme { background-color: #1a1a1a; @@ -1025,4 +1087,27 @@ .timerWrapper :global(.customSubmit), .timerWrapper :global(.customCancel) { flex: 1; +} + +.lightTheme .fontMenu { + background: rgba(255, 255, 255, 0.95); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.lightTheme .fontDivider { + background: rgba(0, 0, 0, 0.1); +} + +.lightTheme .fontOption { + color: rgba(0, 0, 0, 0.8); +} + +.lightTheme .fontOption:hover, +.lightTheme .fontOption.active { + background: rgba(0, 0, 0, 0.06); + color: rgba(0, 0, 0, 0.95); +} + +.lightTheme .fontPreview { + opacity: 0.5; } \ No newline at end of file diff --git a/src/components/FullscreenEditor.tsx b/src/components/FullscreenEditor.tsx index 6e545b8..5cb36ac 100644 --- a/src/components/FullscreenEditor.tsx +++ b/src/components/FullscreenEditor.tsx @@ -552,12 +552,10 @@ export const FullscreenEditor: React.FC = ({ isDarkTheme, {FONT_CATEGORIES.basic.map(font => ( ))}
@@ -566,12 +564,10 @@ export const FullscreenEditor: React.FC = ({ isDarkTheme, {FONT_CATEGORIES.calligraphy.map(font => ( ))}
diff --git a/src/components/HistoryPanel.module.css b/src/components/HistoryPanel.module.css index 46a42a7..79d91d0 100644 --- a/src/components/HistoryPanel.module.css +++ b/src/components/HistoryPanel.module.css @@ -1,71 +1,134 @@ +:root { + --panel-bg: rgba(28, 28, 28, 0.95); + --panel-border: rgba(255, 255, 255, 0.08); + --panel-shadow: rgba(0, 0, 0, 0.25); + --text-primary: rgba(255, 255, 255, 0.9); + --text-secondary: rgba(255, 255, 255, 0.6); + --text-muted: rgba(255, 255, 255, 0.4); + --button-bg: rgba(255, 255, 255, 0.05); + --button-border: rgba(255, 255, 255, 0.1); + --button-hover-bg: rgba(255, 255, 255, 0.08); + --button-hover-border: rgba(255, 255, 255, 0.15); + --delete-hover: rgba(255, 59, 48, 0.1); + --delete-color: rgba(255, 59, 48, 0.9); +} + +[data-theme="light"] { + --panel-bg: rgba(255, 255, 255, 0.95); + --panel-border: rgba(0, 0, 0, 0.08); + --panel-shadow: rgba(0, 0, 0, 0.08); + --text-primary: rgba(0, 0, 0, 0.9); + --text-secondary: rgba(0, 0, 0, 0.6); + --text-muted: rgba(0, 0, 0, 0.4); + --button-bg: rgba(0, 0, 0, 0.06); + --button-border: rgba(0, 0, 0, 0.08); + --button-hover-bg: rgba(0, 0, 0, 0.08); + --button-hover-border: rgba(0, 0, 0, 0.15); + --delete-hover: rgba(255, 59, 48, 0.12); + --delete-color: rgba(255, 59, 48, 1); +} + .panel { position: fixed; - top: 0; right: 0; - width: 350px; - height: 100vh; - background: rgba(0, 0, 0, 0.85); - backdrop-filter: blur(20px); - box-shadow: -2px 0 10px rgba(0, 0, 0, 0.3); + top: 0; + bottom: 0; + width: 340px; + background: var(--panel-bg); + backdrop-filter: blur(20px) saturate(180%); + -webkit-backdrop-filter: blur(20px) saturate(180%); + transform: translateX(100%); + transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1); display: flex; flex-direction: column; z-index: 1000; + border-left: 1px solid var(--panel-border); + box-shadow: -8px 0 32px var(--panel-shadow); font-family: 'Poppins', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } +.panel.open { + transform: translateX(0); +} + .header { - padding: 16px; + padding: 20px; display: flex; - justify-content: space-between; align-items: center; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); + justify-content: space-between; + border-bottom: 1px solid var(--panel-border); + background: var(--panel-bg); + backdrop-filter: blur(32px) saturate(180%); + -webkit-backdrop-filter: blur(32px) saturate(180%); } .headerButtons { display: flex; + gap: 12px; align-items: center; - gap: 8px; } .header h2 { margin: 0; - font-size: 18px; + font-size: 15px; font-weight: 500; - color: rgba(255, 255, 255, 0.9); + color: var(--text-primary); } .closeButton, .newButton { - width: 28px; - height: 28px; - border-radius: 6px; - border: none; - background: transparent; - color: rgba(255, 255, 255, 0.6); - cursor: pointer; + background: var(--button-bg); + border: 1px solid var(--button-border); + border-radius: 8px; + width: 32px; + height: 32px; display: flex; align-items: center; justify-content: center; + color: var(--text-muted); transition: all 0.2s ease; - font-size: 20px; + cursor: pointer; } .closeButton:hover, .newButton:hover { - background: rgba(255, 255, 255, 0.1); - color: rgba(255, 255, 255, 0.9); + background: var(--button-hover-bg); + border-color: var(--button-hover-border); + color: var(--text-primary); } .newButton { - font-size: 24px; - font-weight: 400; + width: auto; + padding: 0 16px; + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + font-size: 14px; + font-weight: 500; + gap: 8px; + box-shadow: 0 1px 2px var(--panel-shadow); +} + +.newButton:hover { + transform: translateY(-1px); + box-shadow: 0 2px 4px var(--panel-shadow); +} + +.newButton:active { + transform: translateY(0); + box-shadow: 0 1px 2px var(--panel-shadow); } .searchContainer { - padding: 16px; + position: sticky; /* keeps search visible when scrolling */ + top: 0; + z-index: 10; + padding: 12px 16px; + background: var(--panel-bg); + backdrop-filter: blur(20px) saturate(160%); + -webkit-backdrop-filter: blur(20px) saturate(160%); + border-bottom: 1px solid var(--panel-border); + box-shadow: 0 2px 4px var(--panel-shadow); + display: flex; align-items: center; gap: 12px; - border-bottom: 1px solid var(--border-color); - background: rgba(0, 0, 0, 0.2); } .searchIcon { @@ -73,36 +136,45 @@ left: 28px; top: 50%; transform: translateY(-50%); - color: rgba(255, 255, 255, 0.4); + color: var(--text-muted); pointer-events: none; } .searchInput { - flex: 1; - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.1); + width: 100%; + background: var(--button-bg); + border: 1px solid var(--button-border); border-radius: 8px; - padding: 12px 16px; - color: inherit; + padding: 10px 12px 10px 36px; + color: var(--text-primary); font-size: 14px; - font-family: inherit; + font-family: 'Poppins', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; transition: all 0.2s ease; } -.searchInput:focus { - outline: none; - background: rgba(255, 255, 255, 0.08); - border-color: rgba(255, 255, 255, 0.2); +.searchInput::placeholder { + color: var(--text-muted); } -.searchInput::placeholder { - color: rgba(255, 255, 255, 0.4); +.searchInput:hover { + background: var(--button-hover-bg); + border-color: var(--button-hover-border); +} + +.searchInput:focus { + outline: none; + background: var(--button-hover-bg); + border-color: var(--button-hover-border); + box-shadow: 0 0 0 1px var(--button-hover-border); } .entriesList { flex: 1; overflow-y: auto; - padding: 12px; + padding: 16px; + display: flex; + flex-direction: column; + gap: 12px; } .entriesList::-webkit-scrollbar { @@ -114,38 +186,36 @@ } .entriesList::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.1); border-radius: 4px; } .entriesList::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.3); + background: rgba(255, 255, 255, 0.15); } .entryItem { - padding: 16px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 8px; - background: rgba(255, 255, 255, 0.05); - margin-bottom: 8px; - cursor: pointer; + padding: 12px; transition: all 0.2s ease; - border: 1px solid transparent; } .entryItem:hover { - background: rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.05); + border-color: rgba(255, 255, 255, 0.12); } .entryItem.active { - background: rgba(255, 255, 255, 0.1); - border: 1px solid rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.08); } .entryHeader { display: flex; - flex-direction: column; - gap: 4px; - margin-bottom: 8px; + align-items: center; + justify-content: space-between; + margin-bottom: 6px; } .titleWrapper { @@ -155,43 +225,22 @@ } .entryTitle { - font-size: 15px; - font-weight: 500; - color: rgba(255, 255, 255, 0.9); - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.titleInput { - width: 100%; - padding: 6px 10px; - border-radius: 4px; - border: 1px solid rgba(255, 255, 255, 0.2); - background: rgba(255, 255, 255, 0.1); - color: rgba(255, 255, 255, 0.9); - font-size: 15px; + font-size: 13px; font-weight: 500; - font-family: inherit; -} - -.titleInput:focus { - outline: none; - border-color: rgba(255, 255, 255, 0.3); + color: rgba(255, 255, 255, 0.95); + margin: 0; + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + letter-spacing: -0.2px; } .editButton { - padding: 4px; - border-radius: 4px; - border: none; - background: transparent; - color: rgba(255, 255, 255, 0.4); + all: unset; cursor: pointer; - display: flex; - align-items: center; - justify-content: center; opacity: 0; + color: rgba(255, 255, 255, 0.5); + display: flex; + padding: 4px; + border-radius: 4px; transition: all 0.2s ease; } @@ -205,80 +254,84 @@ } .entryDate { - font-size: 12px; - color: rgba(255, 255, 255, 0.4); + font-size: 11px; + color: rgba(255, 255, 255, 0.5); + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } .entryPreview { - font-size: 13px; + font-size: 12px; color: rgba(255, 255, 255, 0.6); + margin: 0; line-height: 1.5; - margin-bottom: 12px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; - font-family: inherit; + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + letter-spacing: -0.1px; } .entryFooter { display: flex; justify-content: space-between; align-items: center; - margin-top: 8px; -} - -.entryMeta { - display: flex; - align-items: center; - gap: 12px; + margin-top: 12px; } .wordCount { font-size: 12px; - color: rgba(255, 255, 255, 0.4); + color: rgba(255, 255, 255, 0.5); } .deleteButton { - padding: 4px 8px; - border-radius: 4px; - border: none; background: transparent; + border: none; color: rgba(255, 255, 255, 0.4); - font-size: 13px; + padding: 4px; cursor: pointer; - transition: all 0.2s ease; + transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); + opacity: 0; + font-size: 12px; + border-radius: 4px; } -.deleteButton:hover { - background: rgba(255, 0, 0, 0.1); - color: #ff4d4d; +.entryContent:hover .deleteButton { + opacity: 1; } -.chevron { - color: rgba(255, 255, 255, 0.3); +.deleteButton:hover { + color: rgba(255, 59, 48, 0.9); + background: rgba(255, 59, 48, 0.1); } .footer { - padding: 16px; - border-top: 1px solid rgba(255, 255, 255, 0.1); + padding: 16px 20px; + border-top: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.02); } .clearAllButton { width: 100%; - padding: 8px; - border-radius: 6px; - border: none; - background: rgba(255, 255, 255, 0.05); - color: rgba(255, 255, 255, 0.6); - font-size: 13px; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + background: var(--button-bg); + border: 1px solid var(--button-border); + border-radius: 8px; + padding: 8px 16px; + color: var(--text-secondary); + font-family: 'Poppins', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + font-size: 14px; cursor: pointer; transition: all 0.2s ease; } .clearAllButton:hover { - background: rgba(255, 0, 0, 0.1); - color: #ff4d4d; + background: var(--delete-hover); + border-color: var(--button-hover-border); + color: var(--delete-color); } .emptyState { @@ -290,43 +343,58 @@ /* Light theme styles */ .lightTheme { - background: rgba(255, 252, 242, 0.95); - box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1); + background: rgba(255, 255, 255, 0.95); + border-left: 1px solid rgba(0, 0, 0, 0.08); + box-shadow: -8px 0 32px rgba(0, 0, 0, 0.08); +} + +.lightTheme .header { + background: rgba(255, 255, 255, 0.7); + border-bottom-color: rgba(0, 0, 0, 0.06); } .lightTheme .header h2 { color: rgba(0, 0, 0, 0.9); } -.lightTheme .closeButton, .lightTheme .newButton { - color: rgba(0, 0, 0, 0.6); +.lightTheme .closeButton, +.lightTheme .newButton { + color: rgba(0, 0, 0, 0.5); } -.lightTheme .closeButton:hover, .lightTheme .newButton:hover { - background: rgba(0, 0, 0, 0.05); - color: rgba(0, 0, 0, 0.9); +.lightTheme .closeButton:hover, +.lightTheme .newButton:hover { + background: rgba(0, 0, 0, 0.06); + color: rgba(0, 0, 0, 0.75); } -.lightTheme .searchContainer { - background: rgba(0, 0, 0, 0.03); +.lightTheme .newButton { + background: rgba(0, 0, 0, 0.06); } -.lightTheme .searchInput { - background: rgba(255, 255, 255, 0.8); - border: 1px solid rgba(0, 0, 0, 0.1); +.lightTheme .searchContainer { + background: rgba(255, 255, 255, 0.7); + border-bottom-color: rgba(0, 0, 0, 0.06); } -.lightTheme .searchInput:focus { - background: rgba(255, 255, 255, 0.95); - border-color: rgba(0, 0, 0, 0.15); +.lightTheme .searchInput { + background: rgba(0, 0, 0, 0.04); + border-color: rgba(0, 0, 0, 0.08); + color: rgba(0, 0, 0, 0.9); } .lightTheme .searchInput::placeholder { color: rgba(0, 0, 0, 0.4); } -.lightTheme .entryItem { - background: rgba(0, 0, 0, 0.02); +.lightTheme .searchInput:focus { + background: rgba(0, 0, 0, 0.06); + border-color: rgba(0, 0, 0, 0.15); + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08); +} + +.lightTheme .entriesList { + background: rgba(255, 255, 255, 0.7); } .lightTheme .entryItem:hover { @@ -335,15 +403,23 @@ .lightTheme .entryItem.active { background: rgba(0, 0, 0, 0.06); - border-color: rgba(0, 0, 0, 0.1); } .lightTheme .entryTitle { color: rgba(0, 0, 0, 0.9); } +.lightTheme .editButton { + color: rgba(0, 0, 0, 0.3); +} + +.lightTheme .editButton:hover { + background: rgba(0, 0, 0, 0.06); + color: rgba(0, 0, 0, 0.6); +} + .lightTheme .entryDate { - color: rgba(0, 0, 0, 0.4); + color: rgba(0, 0, 0, 0.5); } .lightTheme .entryPreview { @@ -358,34 +434,147 @@ color: rgba(0, 0, 0, 0.4); } -.lightTheme .deleteButton:hover { - background: rgba(255, 0, 0, 0.05); - color: #ff4d4d; +.lightTheme .footer { + background: rgba(255, 255, 255, 0.7); + border-top-color: rgba(0, 0, 0, 0.06); } .lightTheme .clearAllButton { - background: rgba(0, 0, 0, 0.05); - color: rgba(0, 0, 0, 0.6); + background: rgba(255, 59, 48, 0.12); + border-color: rgba(255, 59, 48, 0.4); + color: #ff3b30; + font-weight: 600; } .lightTheme .clearAllButton:hover { - background: rgba(255, 0, 0, 0.05); - color: #ff4d4d; + background: rgba(255, 59, 48, 0.18); + border-color: rgba(255, 59, 48, 0.5); + color: #ff1f0f; } -.lightTheme .emptyState { - color: rgba(0, 0, 0, 0.4); +.lightTheme .entriesList::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.1); } -.lightTheme .titleInput { - border-color: rgba(0, 0, 0, 0.1); - background: rgba(0, 0, 0, 0.05); - color: rgba(0, 0, 0, 0.9); +.lightTheme .entriesList::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.15); +} + +/* Light theme confirmation dialog */ +.lightTheme .confirmContent { + background: rgba(255, 255, 255, 0.98); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); +} + +.lightTheme .confirmContent h3 { + color: rgba(0, 0, 0, 0.85); +} + +.lightTheme .confirmContent p { + color: rgba(0, 0, 0, 0.6); +} + +.lightTheme .cancelButton { + background: rgba(0, 0, 0, 0.06); + color: rgba(0, 0, 0, 0.6); } -.lightTheme .titleInput:focus { - border-color: rgba(0, 0, 0, 0.2); +.lightTheme .cancelButton:hover { background: rgba(0, 0, 0, 0.08); + color: rgba(0, 0, 0, 0.75); +} + +.lightTheme .confirmButton { + background: rgba(255, 59, 48, 0.08); + color: #ff3b30; +} + +.lightTheme .confirmButton:hover { + background: rgba(255, 59, 48, 0.12); +} + +.confirmDialog { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 2000; + backdrop-filter: blur(4px); + font-family: 'Poppins', sans-serif; +} + +.confirmContent { + background: rgba(30, 30, 30, 0.95); + border-radius: 12px; + padding: 24px; + width: 90%; + max-width: 400px; + animation: dialogAppear 0.2s ease; +} + +.confirmContent h3 { + margin: 0 0 12px; + font-size: 18px; + font-weight: 500; + color: rgba(255, 255, 255, 0.9); +} + +.confirmContent p { + margin: 0 0 24px; + font-size: 15px; + line-height: 1.5; + color: rgba(255, 255, 255, 0.7); +} + +.confirmActions { + display: flex; + justify-content: flex-end; + gap: 12px; +} + +.cancelButton, +.confirmButton { + all: unset; + cursor: pointer; + padding: 8px 16px; + border-radius: 6px; + font-size: 14px; + font-weight: 500; + transition: all 0.2s ease; +} + +.cancelButton { + background: rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.8); +} + +.cancelButton:hover { + background: rgba(255, 255, 255, 0.15); +} + +.confirmButton { + background: rgba(255, 59, 48, 0.15); + color: #ff3b30; +} + +.confirmButton:hover { + background: rgba(255, 59, 48, 0.25); +} + +@keyframes dialogAppear { + from { + opacity: 0; + transform: translateY(-8px); + } + to { + opacity: 1; + transform: translateY(0); + } } /* Mobile styles */ @@ -393,14 +582,6 @@ .panel { width: 100%; } - - .entriesList { - padding: 8px; - } - - .entryItem { - padding: 12px; - } } .loadingContainer { diff --git a/src/components/HistoryPanel.tsx b/src/components/HistoryPanel.tsx index 8e7d816..e053726 100644 --- a/src/components/HistoryPanel.tsx +++ b/src/components/HistoryPanel.tsx @@ -2,6 +2,8 @@ import React, { useState, useRef, useEffect } from 'react'; import { toast } from 'react-hot-toast'; import styles from './HistoryPanel.module.css'; import { Entry } from '../types'; +import { FiTrash2, FiEdit2, FiX } from 'react-icons/fi'; +import { MdDeleteSweep } from 'react-icons/md'; interface HistoryPanelProps { entries: Entry[]; @@ -35,8 +37,7 @@ export const HistoryPanel: React.FC = ({ const [editedTitle, setEditedTitle] = useState(''); const [searchQuery, setSearchQuery] = useState(''); const [showClearConfirm, setShowClearConfirm] = useState(false); - const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); - const [selectedEntryId, setSelectedEntryId] = useState(null); + const [entryToDelete, setEntryToDelete] = useState(null); const [isLoading, setIsLoading] = useState(false); const inputRef = useRef(null); @@ -86,47 +87,16 @@ export const HistoryPanel: React.FC = ({ } }; - const handleDeleteClick = (e: React.MouseEvent, id: string) => { - e.stopPropagation(); - setSelectedEntryId(id); - setShowDeleteConfirm(true); - }; - - const handleConfirmDelete = async () => { - if (selectedEntryId) { - onDelete(selectedEntryId); - setShowDeleteConfirm(false); - setSelectedEntryId(null); - } - }; - - const handleClearAllClick = () => { - setShowClearConfirm(true); - }; - - const handleConfirmClear = async () => { - onClearAll(); - setShowClearConfirm(false); - }; - return ( -
+

History

- -
@@ -153,48 +123,56 @@ export const HistoryPanel: React.FC = ({
onSelect(entry)} > -
-
- {isEditingId === entry.id ? ( - setEditedTitle(e.target.value)} - onBlur={() => handleTitleSave(entry)} - onKeyDown={(e) => handleKeyDown(e, entry)} - className={styles.titleInput} - /> - ) : ( - <> - handleTitleClick(entry, e)} - > - {entry.title} - - - - )} +
onSelect(entry)}> +
+
+ {isEditingId === entry.id ? ( + setEditedTitle(e.target.value)} + onBlur={() => handleTitleSave(entry)} + onKeyDown={(e) => handleKeyDown(e, entry)} + className={styles.titleInput} + /> + ) : ( + <> + handleTitleClick(entry, e)} + > + {entry.title || 'Untitled'} + + + + )} +
+ + {new Date(entry.createdAt).toLocaleDateString()} + +
+

{getPreviewText(entry.content)}

+
+ + {entry.content.trim().split(/\s+/).length} words + +
- -
-
- {getPreviewText(entry.content)}
)) @@ -204,31 +182,61 @@ export const HistoryPanel: React.FC = ({
- {showDeleteConfirm && ( + {entryToDelete && (
-

Delete Entry

-

Are you sure you want to delete this entry?

-
- - +
+

Delete Entry

+

Are you sure you want to delete this entry? This action cannot be undone.

+
+ + +
)} {showClearConfirm && (
-

Clear All Entries

-

Are you sure you want to delete all entries? This cannot be undone.

-
- - +
+

Clear All Entries

+

Are you sure you want to delete all entries? This action cannot be undone.

+
+ + +
)} diff --git a/src/components/ThemeToggle.module.css b/src/components/ThemeToggle.module.css new file mode 100644 index 0000000..f1036e1 --- /dev/null +++ b/src/components/ThemeToggle.module.css @@ -0,0 +1,25 @@ +.toggle { + background: none; + border: none; + padding: 8px; + cursor: pointer; + border-radius: 50%; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.toggle:hover { + background: var(--hover-bg); +} + +.toggle:active { + transform: scale(0.95); +} + +.icon { + font-size: 1.25rem; + line-height: 1; + display: flex; +} \ No newline at end of file diff --git a/src/components/ThemeToggle.tsx b/src/components/ThemeToggle.tsx new file mode 100644 index 0000000..f4709a5 --- /dev/null +++ b/src/components/ThemeToggle.tsx @@ -0,0 +1,20 @@ +import styles from './ThemeToggle.module.css' + +interface ThemeToggleProps { + theme: 'light' | 'dark' + onToggle: () => void +} + +export function ThemeToggle({ theme, onToggle }: ThemeToggleProps) { + return ( + + ) +} \ No newline at end of file diff --git a/src/components/Timer.module.css b/src/components/Timer.module.css index 67e5530..5fa6228 100644 --- a/src/components/Timer.module.css +++ b/src/components/Timer.module.css @@ -10,7 +10,7 @@ justify-content: center; z-index: 2000; backdrop-filter: blur(8px); - animation: dialogAppear 0.2s ease; + animation: fadeIn 0.3s ease; } .overlay.light { @@ -117,20 +117,22 @@ } .timerButton { - background: transparent; - border: none; - color: inherit; + all: unset; cursor: pointer; - font-size: 1rem; - padding: 8px; - transition: opacity 0.2s; + padding: 4px 8px; + border-radius: 6px; + transition: all 0.2s ease; display: flex; align-items: center; gap: 4px; } .timerButton:hover { - opacity: 0.8; + background: rgba(255, 255, 255, 0.1); +} + +:global(.lightTheme) .timerButton:hover { + background: rgba(0, 0, 0, 0.05); } .timerPopup { @@ -154,10 +156,14 @@ } .timerDisplay { - font-size: 2rem; - font-weight: 500; - text-align: center; - font-variant-numeric: tabular-nums; + display: flex; + align-items: center; + gap: 4px; +} + +.clockIcon { + font-size: 14px; + opacity: 0.7; } .timerControls { @@ -255,6 +261,17 @@ .timerContainer { position: relative; + display: flex; + align-items: center; + min-width: 50px; + font-variant-numeric: tabular-nums; + font-size: 15px; + opacity: 0.85; + transition: opacity 0.2s ease; +} + +.timerContainer:hover { + opacity: 1; } .timerButton.active { @@ -295,14 +312,19 @@ text-align: center; } +/* Style .timerDialog as a modal, removing absolute positioning */ .timerDialog { + /* Removed absolute positioning */ + /* transform: translateX(-50%); */ + /* Removed transform */ background: rgba(30, 30, 30, 0.95); - border-radius: 16px; + border-radius: 12px; /* Adjusted from 16px */ padding: 24px; - width: 90%; - max-width: 400px; + width: 90%; /* Use width instead of min-width for responsiveness */ + max-width: 360px; /* Limit max width */ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); - animation: dialogSlide 0.2s ease; + animation: dialogAppear 0.2s ease; /* Re-use dialogAppear animation */ + /* Removed z-index, handled by overlay */ } .dialogHeader { @@ -346,111 +368,91 @@ } .presetButtons { + position: absolute; + bottom: 100%; + right: 0; + margin-bottom: 8px; + background: var(--nav-bg); + border-radius: 8px; + padding: 8px; + box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.1); display: flex; - gap: 8px; + flex-direction: column-reverse; + gap: 4px; + min-width: 100px; + z-index: 10; + animation: slideUp 0.2s ease; } .presetButton { all: unset; cursor: pointer; - padding: 6px 12px; + padding: 8px 12px; border-radius: 6px; font-size: 14px; - background: rgba(255, 255, 255, 0.1); + opacity: 0.8; transition: all 0.2s ease; + text-align: center; + background: rgba(255, 255, 255, 0.1); } .presetButton:hover { + opacity: 1; background: rgba(255, 255, 255, 0.15); + transform: translateX(2px); } -.presetButton.active { - background: rgba(74, 158, 255, 0.2); - color: #4a9eff; -} - -.customTime { - display: flex; - flex-direction: column; - gap: 16px; +:global(.lightTheme) .presetButton { + background: rgba(0, 0, 0, 0.05); + color: #333; } -.timeInputs { - display: flex; - justify-content: center; - gap: 12px; +:global(.lightTheme) .presetButton:hover { + background: rgba(0, 0, 0, 0.1); } -.inputGroup { +.activeTimer { display: flex; - flex-direction: column; + align-items: center; gap: 8px; } -.timeInput { - background: rgba(255, 255, 255, 0.1); - border: 1px solid rgba(255, 255, 255, 0.2); - border-radius: 8px; - padding: 12px; - color: rgba(255, 255, 255, 0.9); - font-size: 16px; - transition: all 0.2s ease; -} - -.timeInput:hover { - border-color: rgba(255, 255, 255, 0.3); -} - -.timeInput:focus { - outline: none; - border-color: #4a9eff; - background: rgba(255, 255, 255, 0.15); -} - -.timeUnit { - font-size: 13px; - opacity: 0.7; +.time { + font-size: 15px; + font-variant-numeric: tabular-nums; } -.dialogActions { +.controls { display: flex; - gap: 12px; - margin-top: 8px; + gap: 4px; + align-items: center; } -.cancelButton, -.startButton { - flex: 1; - padding: 12px; - border: none; - border-radius: 8px; - font-size: 15px; - font-weight: 500; +.control { + all: unset; cursor: pointer; + padding: 4px; + border-radius: 4px; + opacity: 0.7; transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; } -.cancelButton { +.control:hover { + opacity: 1; background: rgba(255, 255, 255, 0.1); - color: rgba(255, 255, 255, 0.9); -} - -.cancelButton:hover { - background: rgba(255, 255, 255, 0.15); + transform: translateY(-1px); } -.startButton { - background: #4a9eff; - color: white; +.control:active { + transform: translateY(0); } -.startButton:hover { - background: #3d8be6; -} - -.startButton:disabled { - opacity: 0.5; - cursor: not-allowed; +:global(.lightTheme) .control:hover { + background: rgba(0, 0, 0, 0.1); } @keyframes dialogAppear { @@ -462,20 +464,21 @@ } } -@keyframes dialogSlide { +@keyframes slideUp { from { - transform: translateY(20px); opacity: 0; + transform: translateY(10px); } to { - transform: translateY(0); opacity: 1; + transform: translateY(0); } } /* Light theme */ [data-theme='light'] .timerDialog { background: rgba(255, 252, 242, 0.95); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); /* Adjusted shadow */ } [data-theme='light'] .dialogHeader h3 { @@ -527,168 +530,289 @@ background: #0052a3; } -.activeTimer { +.customTime { display: flex; - align-items: center; - gap: 8px; + flex-direction: column; + gap: 16px; } -.time { - font-size: 15px; - font-variant-numeric: tabular-nums; - min-width: 45px; +.timeInputs { + display: flex; + justify-content: center; + gap: 12px; } -.controls { +.inputGroup { display: flex; - gap: 4px; + flex-direction: column; + gap: 8px; } -.control { - all: unset; - cursor: pointer; - padding: 4px; - border-radius: 4px; +.timeInput { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 8px; + padding: 12px; + color: rgba(255, 255, 255, 0.9); + font-size: 16px; transition: all 0.2s ease; } -.control:hover { - background: rgba(255, 255, 255, 0.1); +.timeInput:hover { + border-color: rgba(255, 255, 255, 0.3); } -:global(.lightTheme) .control:hover { - background: rgba(0, 0, 0, 0.1); +.timeInput:focus { + outline: none; + border-color: #4a9eff; + background: rgba(255, 255, 255, 0.15); } -.presetButtons { +.timeUnit { + font-size: 13px; + opacity: 0.7; +} + +.dialogActions { display: flex; - gap: 8px; + gap: 12px; + margin-top: 8px; } -.presetButton { - all: unset; +.cancelButton, +.startButton { + flex: 1; + padding: 12px; + border: none; + border-radius: 8px; + font-size: 15px; + font-weight: 500; cursor: pointer; - padding: 6px 12px; - border-radius: 6px; - font-size: 14px; - background: rgba(255, 255, 255, 0.1); transition: all 0.2s ease; } -.presetButton:hover { +.cancelButton { + background: rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.9); +} + +.cancelButton:hover { background: rgba(255, 255, 255, 0.15); } -:global(.lightTheme) .presetButton { +.startButton { + background: #4a9eff; + color: white; +} + +.startButton:hover { + background: #3d8be6; +} + +.startButton:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Light theme styles */ +:global(.lightTheme) .customTimeInput { background: rgba(0, 0, 0, 0.05); } -:global(.lightTheme) .presetButton:hover { - background: rgba(0, 0, 0, 0.1); +:global(.lightTheme) .customTimeInput:focus { + background: rgba(0, 0, 0, 0.08); } -.customInput { - display: flex; - flex-direction: column; - gap: 12px; - min-width: 200px; +:global(.lightTheme) .customTimeInput::placeholder { + color: rgba(0, 0, 0, 0.4); } -.customTimeInput { - all: unset; - padding: 8px 12px; - border-radius: 6px; - background: rgba(255, 255, 255, 0.1); - color: inherit; - font-size: 14px; - text-align: center; - transition: all 0.2s ease; +:global(.lightTheme) .customSubmit { + background: rgba(0, 102, 204, 0.1); + color: #0066cc; } -.customTimeInput:focus { - background: rgba(255, 255, 255, 0.15); +:global(.lightTheme) .customSubmit:hover { + background: rgba(0, 102, 204, 0.15); } -.customTimeInput::placeholder { - color: rgba(255, 255, 255, 0.5); +:global(.lightTheme) .customCancel { + background: rgba(0, 0, 0, 0.05); +} + +:global(.lightTheme) .customCancel:hover { + background: rgba(0, 0, 0, 0.08); +} + +:global(.lightTheme) .customButton:hover { + border-color: rgba(0, 0, 0, 0.3) !important; +} + +/* Ensure custom button visibility in light theme */ +:global(.lightTheme) .presetButton.customButton { + color: rgba(0, 0, 0, 0.7); /* Make text darker */ + border: 1px solid rgba(0, 0, 0, 0.15); /* Add subtle border */ } -.customButtons { +:global(.lightTheme) .presetButton.customButton:hover { + color: rgba(0, 0, 0, 0.9); + border-color: rgba(0, 0, 0, 0.25); + background: rgba(0, 0, 0, 0.08); +} + +/* Ensure overlay is always full-screen and blurred */ +.overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); display: flex; - gap: 8px; + align-items: center; + justify-content: center; + z-index: 2000; + backdrop-filter: blur(8px); + animation: fadeIn 0.3s ease; } -.customSubmit, -.customCancel { - all: unset; - flex: 1; - padding: 6px 12px; - border-radius: 6px; - font-size: 14px; +[data-theme='light'] .overlay { + background: rgba(0,0,0,0.18); + backdrop-filter: blur(8px); +} + +.notification { + background: rgba(30, 30, 30, 0.95); + border-radius: 16px; + padding: 32px; + max-width: 400px; + width: 90%; text-align: center; - cursor: pointer; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); + animation: slideUp 0.3s ease; transition: all 0.2s ease; } -.customSubmit { - background: rgba(74, 158, 255, 0.2); - color: #4a9eff; +.content { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; } -.customSubmit:hover { - background: rgba(74, 158, 255, 0.3); +.icon { + font-size: 48px; + margin-bottom: 8px; + animation: bounce 1s ease infinite; + opacity: 0.9; } -.customCancel { - background: rgba(255, 255, 255, 0.1); +.notification h2 { + margin: 0; + font-size: 24px; + font-weight: 600; + color: rgba(255, 255, 255, 0.95); + transition: color 0.2s ease; } -.customCancel:hover { - background: rgba(255, 255, 255, 0.15); +.notification p { + margin: 0; + font-size: 16px; + color: rgba(255, 255, 255, 0.8); + transition: color 0.2s ease; } -.customButton { - border: 1px dashed rgba(255, 255, 255, 0.2) !important; +.subtext { + font-size: 14px !important; + color: rgba(255, 255, 255, 0.6) !important; + transition: color 0.2s ease; } -.customButton:hover { - border-color: rgba(255, 255, 255, 0.3) !important; +.closeButton { + all: unset; + margin-top: 8px; + padding: 12px 32px; + background: rgba(74, 158, 255, 0.2); + color: #4a9eff; + border-radius: 8px; + font-size: 15px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; } -/* Light theme styles */ -:global(.lightTheme) .customTimeInput { - background: rgba(0, 0, 0, 0.05); +.closeButton:hover { + background: rgba(74, 158, 255, 0.3); + transform: translateY(-1px); } -:global(.lightTheme) .customTimeInput:focus { - background: rgba(0, 0, 0, 0.08); +.closeButton:active { + transform: translateY(0); } -:global(.lightTheme) .customTimeInput::placeholder { - color: rgba(0, 0, 0, 0.4); +/* Light theme */ +.lightTheme { + background: rgba(255, 252, 242, 0.95); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); } -:global(.lightTheme) .customSubmit { +.lightTheme h2 { + color: rgba(0, 0, 0, 0.9); +} + +.lightTheme p { + color: rgba(0, 0, 0, 0.7); +} + +.lightTheme .subtext { + color: rgba(0, 0, 0, 0.5) !important; +} + +.lightTheme .closeButton { background: rgba(0, 102, 204, 0.1); color: #0066cc; } -:global(.lightTheme) .customSubmit:hover { +.lightTheme .closeButton:hover { background: rgba(0, 102, 204, 0.15); } -:global(.lightTheme) .customCancel { - background: rgba(0, 0, 0, 0.05); +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } } -:global(.lightTheme) .customCancel:hover { - background: rgba(0, 0, 0, 0.08); +@keyframes slideUp { + from { + transform: translateY(20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } } -:global(.lightTheme) .customButton { - border: 1px dashed rgba(0, 0, 0, 0.2) !important; +@keyframes bounce { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-10px); + } } -:global(.lightTheme) .customButton:hover { - border-color: rgba(0, 0, 0, 0.3) !important; +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } } \ No newline at end of file diff --git a/src/components/Timer.tsx b/src/components/Timer.tsx index 3306e07..fe36232 100644 --- a/src/components/Timer.tsx +++ b/src/components/Timer.tsx @@ -1,4 +1,5 @@ -import React, { useState } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; +import { FiPause, FiPlay, FiStopCircle, FiClock } from 'react-icons/fi'; import styles from './Timer.module.css'; interface TimerProps { @@ -10,7 +11,7 @@ interface TimerProps { isRunning: boolean; } -const TIMER_PRESETS = [5, 15, 25, 30]; +const TIMER_PRESETS = [15, 30, 45, 60]; export const Timer: React.FC = ({ onStart, @@ -20,8 +21,20 @@ export const Timer: React.FC = ({ timeLeft, isRunning }) => { - const [showCustomInput, setShowCustomInput] = useState(false); - const [customMinutes, setCustomMinutes] = useState(''); + const [showPresets, setShowPresets] = useState(false); + const containerRef = useRef(null); + + // Handle clicks outside to close the preset menu + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (containerRef.current && !containerRef.current.contains(event.target as Node)) { + setShowPresets(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); const formatTime = (seconds: number): string => { const minutes = Math.floor(seconds / 60); @@ -29,107 +42,83 @@ export const Timer: React.FC = ({ return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; }; - const handleCustomSubmit = (e: React.FormEvent) => { - e.preventDefault(); - const minutes = parseInt(customMinutes); - if (!isNaN(minutes) && minutes > 0) { + const handleTimerClick = (minutes?: number) => { + if (minutes) { onStart(minutes); - setShowCustomInput(false); - setCustomMinutes(''); + setShowPresets(false); + } else if (timeLeft === 0) { + setShowPresets(prev => !prev); } }; - // If there's no time left, show presets and custom input - if (timeLeft === 0) { - return ( -
- {showCustomInput ? ( -
setShowCustomInput(false)}> -
e.stopPropagation()} - > -
-

Set Custom Timer

+ return ( +
+
handleTimerClick()} + > + {timeLeft === 0 ? ( +
+ + 15m +
+ ) : ( +
+ {formatTime(timeLeft)} +
+ {isRunning ? ( -
-
-
- - setCustomMinutes(e.target.value)} - className={styles.timeInput} - placeholder="Enter minutes" - autoFocus - /> -
-
- - -
-
-
-
- ) : ( -
- {TIMER_PRESETS.map((minutes) => ( - + )} + - ))} - +
)}
- ); - } - - // If timer is set (timeLeft > 0), show the active timer - return ( -
-
- {formatTime(timeLeft)} -
- {isRunning ? ( - - ) : ( - - )} - + {timeLeft === 0 && showPresets && ( +
+ {TIMER_PRESETS.map((minutes) => ( + + ))}
-
+ )}
); }; \ No newline at end of file diff --git a/src/components/TodoPanel.module.css b/src/components/TodoPanel.module.css new file mode 100644 index 0000000..d53a728 --- /dev/null +++ b/src/components/TodoPanel.module.css @@ -0,0 +1,161 @@ +.container { + max-width: 600px; + margin: 2rem auto; + padding: var(--spacing-lg); + background: var(--bg-secondary); + border-radius: var(--border-radius-lg); + border: 1px solid var(--border-color); + box-shadow: var(--shadow-md); +} + +.form { + display: flex; + gap: var(--spacing-sm); + margin-bottom: var(--spacing-lg); +} + +.input { + flex: 1; + padding: var(--spacing-md); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-md); + background: var(--bg-primary); + color: var(--color-primary); + font-size: var(--font-base); + transition: var(--transition-base); +} + +.input:hover { + border-color: var(--border-hover); +} + +.input:focus { + outline: none; + border-color: var(--color-accent); + box-shadow: 0 0 0 2px rgba(255, 59, 48, 0.2); +} + +.addButton { + padding: var(--spacing-md) var(--spacing-lg); + background: var(--color-accent); + color: white; + border: none; + border-radius: var(--border-radius-md); + font-size: var(--font-base); + font-weight: 500; + cursor: pointer; + transition: var(--transition-base); +} + +.addButton:hover { + opacity: 0.9; + transform: translateY(-1px); +} + +.addButton:active { + transform: translateY(0); +} + +.todoList { + list-style: none; + padding: 0; + margin: 0; +} + +.todoItem { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-md); + border-bottom: 1px solid var(--border-color); + transition: var(--transition-base); +} + +.todoItem:last-child { + border-bottom: none; +} + +.todoLabel { + display: flex; + align-items: center; + gap: var(--spacing-md); + flex: 1; + cursor: pointer; +} + +.checkbox { + width: 20px; + height: 20px; + border: 2px solid var(--border-color); + border-radius: var(--border-radius-sm); + cursor: pointer; + transition: var(--transition-base); +} + +.checkbox:checked { + background: var(--color-accent); + border-color: var(--color-accent); +} + +.todoText { + font-size: var(--font-base); + color: var(--color-primary); + transition: var(--transition-base); +} + +.completed { + text-decoration: line-through; + opacity: 0.6; +} + +.deleteButton { + background: none; + border: none; + color: var(--color-primary); + opacity: 0.5; + font-size: 1.5rem; + cursor: pointer; + padding: var(--spacing-xs); + border-radius: 50%; + transition: var(--transition-base); + display: flex; + align-items: center; + justify-content: center; +} + +.deleteButton:hover { + opacity: 1; + background: var(--bg-hover); +} + +.footer { + display: flex; + align-items: center; + justify-content: space-between; + padding-top: var(--spacing-lg); + margin-top: var(--spacing-lg); + border-top: 1px solid var(--border-color); +} + +.count { + color: var(--color-primary); + opacity: 0.7; + font-size: var(--font-sm); +} + +.clearButton { + background: none; + border: none; + color: var(--color-primary); + opacity: 0.7; + font-size: var(--font-sm); + cursor: pointer; + padding: var(--spacing-xs) var(--spacing-sm); + border-radius: var(--border-radius-sm); + transition: var(--transition-base); +} + +.clearButton:hover { + opacity: 1; + background: var(--bg-hover); +} \ No newline at end of file diff --git a/src/components/TodoPanel.tsx b/src/components/TodoPanel.tsx new file mode 100644 index 0000000..645363f --- /dev/null +++ b/src/components/TodoPanel.tsx @@ -0,0 +1,92 @@ +import { useState } from 'react' +import styles from './TodoPanel.module.css' + +interface Todo { + id: string + text: string + completed: boolean +} + +export function TodoPanel() { + const [todos, setTodos] = useState([]) + const [newTodo, setNewTodo] = useState('') + + const addTodo = (e: React.FormEvent) => { + e.preventDefault() + if (!newTodo.trim()) return + + setTodos(prev => [...prev, { + id: crypto.randomUUID(), + text: newTodo.trim(), + completed: false + }]) + setNewTodo('') + } + + const toggleTodo = (id: string) => { + setTodos(prev => prev.map(todo => + todo.id === id ? { ...todo, completed: !todo.completed } : todo + )) + } + + const deleteTodo = (id: string) => { + setTodos(prev => prev.filter(todo => todo.id !== id)) + } + + const clearCompleted = () => { + setTodos(prev => prev.filter(todo => !todo.completed)) + } + + return ( +
+
+ setNewTodo(e.target.value)} + placeholder="Add a new task..." + className={styles.input} + /> + +
+ +
    + {todos.map(todo => ( +
  • + + +
  • + ))} +
+ + {todos.length > 0 && ( +
+ + {todos.filter(t => !t.completed).length} items left + + +
+ )} +
+ ) +} \ No newline at end of file diff --git a/src/styles/theme.css b/src/styles/theme.css new file mode 100644 index 0000000..4d49c23 --- /dev/null +++ b/src/styles/theme.css @@ -0,0 +1,83 @@ +:root { + /* Base transitions */ + --transition-base: all 0.15s ease; + + /* Default theme (light) */ + --bg-primary: rgb(255, 252, 242); + --color-primary: rgba(0, 0, 0, 0.9); + --nav-bg: rgba(255, 252, 242, 0.95); + --border-color: rgba(0, 0, 0, 0.1); + + /* Common variables */ + --border-radius-sm: 4px; + --border-radius-md: 8px; + --border-radius-lg: 12px; + --spacing-xs: 4px; + --spacing-sm: 8px; + --spacing-md: 16px; + --spacing-lg: 24px; + --spacing-xl: 32px; + + /* Colors */ + --color-secondary: rgba(0, 0, 0, 0.7); + --color-muted: rgba(0, 0, 0, 0.4); + --color-accent: #ff3b30; + + /* Backgrounds */ + --bg-secondary: rgba(0, 0, 0, 0.03); + --bg-hover: rgba(0, 0, 0, 0.05); + --bg-active: rgba(0, 0, 0, 0.1); + + /* Borders */ + --border-hover: rgba(0, 0, 0, 0.2); + + /* Shadows */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.1); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.25); + + /* Typography */ + --font-sm: 13px; + --font-base: 14px; + --font-md: 15px; + --font-lg: 18px; + --font-xl: 24px; + + /* Layout */ + --nav-height: 60px; + --sidebar-width: 340px; + --border-radius: 8px; + + /* Animation */ + --transition-fast: 0.15s ease; + --transition-slow: 0.3s cubic-bezier(0.16, 1, 0.3, 1); +} + +[data-theme="dark"] { + --bg-primary: #1a1a1a; + --color-primary: rgba(255, 255, 255, 0.9); + --nav-bg: rgba(0, 0, 0, 0.85); + --border-color: rgba(255, 255, 255, 0.1); + --color-secondary: rgba(255, 255, 255, 0.7); + --color-muted: rgba(255, 255, 255, 0.4); + --bg-secondary: rgba(255, 255, 255, 0.05); + --bg-hover: rgba(255, 255, 255, 0.08); + --bg-active: rgba(255, 255, 255, 0.12); + --border-hover: rgba(255, 255, 255, 0.2); +} + +/* Accessibility */ +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} + +/* Focus styles */ +*:focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: 2px; +} \ No newline at end of file