frontend-hamroun 1.2.82 → 1.2.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/bin/cli.js +58 -870
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.client.cjs +1 -1
  5. package/dist/index.client.cjs.map +1 -1
  6. package/dist/index.client.js +2 -2
  7. package/dist/index.client.js.map +1 -1
  8. package/dist/index.js +116 -133
  9. package/dist/index.js.map +1 -1
  10. package/dist/jsx-runtime.cjs.map +1 -1
  11. package/dist/jsx-runtime.js.map +1 -1
  12. package/dist/renderer-DaVfBeVi.cjs +2 -0
  13. package/dist/renderer-DaVfBeVi.cjs.map +1 -0
  14. package/dist/renderer-nfT7XSpo.js +61 -0
  15. package/dist/renderer-nfT7XSpo.js.map +1 -0
  16. package/dist/server-renderer-B5b0Q0ck.cjs +2 -0
  17. package/dist/server-renderer-B5b0Q0ck.cjs.map +1 -0
  18. package/dist/{server-renderer-QHt45Ip2.js → server-renderer-C4MB-jAp.js} +89 -96
  19. package/dist/server-renderer-C4MB-jAp.js.map +1 -0
  20. package/dist/server-renderer.cjs +1 -1
  21. package/dist/server-renderer.js +1 -1
  22. package/package.json +1 -1
  23. package/templates/basic-app/src/App.tsx +397 -19
  24. package/templates/ssr-template/dist/client/assets/main-D-VH3xOb.js +1 -0
  25. package/templates/ssr-template/dist/client/index.html +23 -0
  26. package/templates/ssr-template/dist/client.js +951 -0
  27. package/templates/ssr-template/dist/server.js +739 -0
  28. package/templates/ssr-template/package.json +22 -22
  29. package/templates/ssr-template/src/App.tsx +874 -11
  30. package/templates/ssr-template/tsconfig.json +14 -10
  31. package/templates/ssr-template/vite.config.ts +19 -17
  32. package/templates/wasm/dist/assets/index-BNqTDBdE.js +295 -0
  33. package/templates/wasm/dist/example.wasm +0 -0
  34. package/templates/wasm/dist/index.html +53 -0
  35. package/templates/wasm/dist/wasm_exec.js +572 -0
  36. package/templates/wasm/package-lock.json +4577 -5307
  37. package/templates/wasm/package.json +25 -42
  38. package/templates/wasm/public/wasm_exec.js +12 -1
  39. package/templates/wasm/src/App.tsx +41 -55
  40. package/templates/wasm/vite.config.ts +24 -42
  41. package/dist/renderer-Bo9zkUZ_.js +0 -52
  42. package/dist/renderer-Bo9zkUZ_.js.map +0 -1
  43. package/dist/renderer-Din1y3YM.cjs +0 -2
  44. package/dist/renderer-Din1y3YM.cjs.map +0 -1
  45. package/dist/server-renderer-CqIpQ-od.cjs +0 -2
  46. package/dist/server-renderer-CqIpQ-od.cjs.map +0 -1
  47. package/dist/server-renderer-QHt45Ip2.js.map +0 -1
@@ -1,19 +1,882 @@
1
- import { useState } from 'frontend-hamroun';
1
+ import {
2
+ jsx,
3
+ useState,
4
+ useEffect,
5
+ useMemo,
6
+ useErrorBoundary
7
+ } from 'frontend-hamroun';
2
8
 
3
- export function App() {
4
- const [count, setCount] = useState(0);
9
+ // Todo item interface
10
+ interface Todo {
11
+ id: number;
12
+ text: string;
13
+ completed: boolean;
14
+ priority: 'high' | 'medium' | 'low';
15
+ }
16
+
17
+ // Component prop interfaces
18
+ interface TodoItemProps {
19
+ todo: Todo;
20
+ onToggle: (id: number) => void;
21
+ onDelete: (id: number) => void;
22
+ }
23
+
24
+ interface ThemeToggleButtonProps {
25
+ theme: string;
26
+ onToggle: () => void;
27
+ }
28
+
29
+ interface AppFooterProps {
30
+ theme: string;
31
+ isBrowser: boolean;
32
+ }
5
33
 
6
- const increment = () => setCount(count + 1);
7
- const decrement = () => setCount(count - 1);
34
+ // Todo Item Component - simple props-based component
35
+ function TodoItem({ todo, onToggle, onDelete }: TodoItemProps) {
36
+ return (
37
+ <div className={`todo-item ${todo.completed ? 'completed' : ''}`}>
38
+ <input
39
+ type="checkbox"
40
+ checked={todo.completed}
41
+ onChange={() => onToggle(todo.id)}
42
+ className="todo-checkbox"
43
+ />
44
+ <span className="todo-text">{todo.text}</span>
45
+ <span className={`todo-priority priority-${todo.priority}`}>
46
+ {todo.priority === 'high' && '🔴'}
47
+ {todo.priority === 'medium' && '🟡'}
48
+ {todo.priority === 'low' && '🟢'}
49
+ {todo.priority}
50
+ </span>
51
+ <div className="todo-actions">
52
+ <button
53
+ onClick={() => onDelete(todo.id)}
54
+ className="btn btn-sm btn-danger"
55
+ title="Delete todo"
56
+ >
57
+ 🗑️
58
+ </button>
59
+ </div>
60
+ </div>
61
+ );
62
+ }
63
+
64
+ // Theme Toggle Button Component
65
+ function ThemeToggleButton({ theme, onToggle }: ThemeToggleButtonProps) {
66
+ return (
67
+ <button
68
+ onClick={onToggle}
69
+ className="btn btn-secondary theme-toggle"
70
+ title="Toggle theme"
71
+ >
72
+ {theme === 'light' ? '🌙' : '☀️'}
73
+ </button>
74
+ );
75
+ }
8
76
 
77
+ // App Footer Component
78
+ function AppFooter({ theme, isBrowser }: AppFooterProps) {
9
79
  return (
10
- <div>
11
- <h1>Server-Side Rendered App</h1>
12
- <div>
13
- <button onClick={decrement} data-action="decrement">-</button>
14
- <span style={{ margin: '0 10px' }}>{count}</span>
15
- <button onClick={increment} data-action="increment">+</button>
80
+ <footer className="app-footer">
81
+ <div className="container">
82
+ <p>Built with Frontend Hamroun • Hooks: useState, useEffect, useMemo, useErrorBoundary</p>
83
+ <p>Theme: <strong>{theme}</strong> • Environment: <strong>{isBrowser ? 'Client' : 'Server'}</strong></p>
84
+ </div>
85
+ </footer>
86
+ );
87
+ }
88
+
89
+ export function App() {
90
+ // State hooks with proper typing
91
+ const [todos, setTodos] = useState<Todo[]>([
92
+ { id: 1, text: 'Learn Frontend Hamroun hooks', completed: false, priority: 'high' },
93
+ { id: 2, text: 'Build a todo app', completed: false, priority: 'medium' },
94
+ { id: 3, text: 'Master SSR concepts', completed: true, priority: 'low' }
95
+ ]);
96
+ const [newTask, setNewTask] = useState<string>('');
97
+ const [taskFilter, setTaskFilter] = useState<string>('all');
98
+ const [taskPriority, setTaskPriority] = useState<'high' | 'medium' | 'low'>('medium');
99
+ const [theme, setTheme] = useState<string>('light');
100
+ const [isLoading, setIsLoading] = useState<boolean>(false);
101
+
102
+ // Error boundary hook
103
+ const [error, resetError] = useErrorBoundary();
104
+
105
+ // Check if we're in browser environment
106
+ const isBrowser = typeof window !== 'undefined';
107
+
108
+ // Mount effect
109
+ useEffect(() => {
110
+ if (!isBrowser) return;
111
+
112
+ console.log('Todo App mounted');
113
+
114
+ // Load saved todos from localStorage
115
+ const savedTodos = localStorage.getItem('todos');
116
+ const savedTheme = localStorage.getItem('theme');
117
+
118
+ if (savedTodos) {
119
+ try {
120
+ setTodos(JSON.parse(savedTodos));
121
+ } catch (e) {
122
+ console.error('Failed to load saved todos');
123
+ }
124
+ }
125
+
126
+ if (savedTheme) {
127
+ setTheme(savedTheme);
128
+ }
129
+
130
+ return () => {
131
+ console.log('Todo App unmounting');
132
+ };
133
+ }, []);
134
+
135
+ // Theme effect
136
+ useEffect(() => {
137
+ if (!isBrowser) return;
138
+
139
+ document.body.className = `theme-${theme}`;
140
+ document.documentElement.setAttribute('data-theme', theme);
141
+ localStorage.setItem('theme', theme);
142
+ }, [theme, isBrowser]);
143
+
144
+ // Save todos effect
145
+ useEffect(() => {
146
+ if (!isBrowser || todos.length === 0) return;
147
+
148
+ localStorage.setItem('todos', JSON.stringify(todos));
149
+ }, [todos, isBrowser]);
150
+
151
+ // Memoized filtered todos
152
+ const filteredTodos = useMemo(() => {
153
+ console.log('Filtering todos - memoized computation');
154
+ return todos.filter(todo => {
155
+ if (taskFilter === 'completed') return todo.completed;
156
+ if (taskFilter === 'active') return !todo.completed;
157
+ if (taskFilter === 'high') return todo.priority === 'high';
158
+ if (taskFilter === 'medium') return todo.priority === 'medium';
159
+ if (taskFilter === 'low') return todo.priority === 'low';
160
+ return true;
161
+ });
162
+ }, [todos, taskFilter]);
163
+
164
+ // Memoized todo stats
165
+ const todoStats = useMemo(() => {
166
+ const total = todos.length;
167
+ const completed = todos.filter(t => t.completed).length;
168
+ const active = total - completed;
169
+ const highPriority = todos.filter(t => t.priority === 'high' && !t.completed).length;
170
+
171
+ return { total, completed, active, highPriority };
172
+ }, [todos]);
173
+
174
+ // Todo action handlers
175
+ const handleToggleTodo = (id: number) => {
176
+ console.log('Toggling todo:', id);
177
+ setTodos(prev => prev.map(todo =>
178
+ todo.id === id ? { ...todo, completed: !todo.completed } : todo
179
+ ));
180
+ };
181
+
182
+ const handleDeleteTodo = (id: number) => {
183
+ console.log('Deleting todo:', id);
184
+ setTodos(prev => prev.filter(todo => todo.id !== id));
185
+ };
186
+
187
+ const handleAddTodo = (text: string, priority: 'high' | 'medium' | 'low') => {
188
+ if (!text || !text.trim()) return;
189
+
190
+ const newTodo: Todo = {
191
+ id: Date.now(),
192
+ text: text.trim(),
193
+ completed: false,
194
+ priority: priority || 'medium'
195
+ };
196
+ setTodos(prev => [...prev, newTodo]);
197
+ };
198
+
199
+ // Event handlers
200
+ const addTask = () => {
201
+ if (newTask.trim()) {
202
+ setIsLoading(true);
203
+
204
+ // Simulate async operation
205
+ setTimeout(() => {
206
+ handleAddTodo(newTask, taskPriority);
207
+ setNewTask('');
208
+ setIsLoading(false);
209
+ }, 300);
210
+ }
211
+ };
212
+
213
+ const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
214
+ if (e.key === 'Enter') {
215
+ addTask();
216
+ }
217
+ };
218
+
219
+ const clearCompleted = () => {
220
+ setTodos(prev => prev.filter(todo => !todo.completed));
221
+ };
222
+
223
+ const markAllComplete = () => {
224
+ setTodos(prev => prev.map(todo => ({ ...todo, completed: true })));
225
+ };
226
+
227
+ const toggleTheme = () => {
228
+ setTheme(prev => prev === 'light' ? 'dark' : 'light');
229
+ };
230
+
231
+ const simulateError = () => {
232
+ throw new Error('Simulated error for testing error boundary');
233
+ };
234
+
235
+ if (error) {
236
+ return (
237
+ <div className="error-container">
238
+ <h2>Something went wrong!</h2>
239
+ <p>{(error as Error).message}</p>
240
+ <button onClick={resetError} className="btn btn-primary">
241
+ Try Again
242
+ </button>
16
243
  </div>
244
+ );
245
+ }
246
+
247
+ return (
248
+ <div className={`app theme-${theme}`}>
249
+
250
+ {/* Header */}
251
+ <header className="app-header">
252
+ <div className="container">
253
+ <h1 className="app-title">
254
+ 📝 Todo App
255
+ <span className="subtitle">Built with Frontend Hamroun</span>
256
+ </h1>
257
+
258
+ <div className="header-controls">
259
+ <ThemeToggleButton theme={theme} onToggle={toggleTheme} />
260
+ <div className="stats">
261
+ <span className="stat">
262
+ Total: <strong>{todoStats.total}</strong>
263
+ </span>
264
+ <span className="stat">
265
+ Active: <strong>{todoStats.active}</strong>
266
+ </span>
267
+ <span className="stat">
268
+ Done: <strong>{todoStats.completed}</strong>
269
+ </span>
270
+ </div>
271
+ </div>
272
+ </div>
273
+ </header>
274
+
275
+ <main className="main-content">
276
+ <div className="container">
277
+
278
+ {/* Add Todo Section */}
279
+ <section className="card add-todo-section">
280
+ <h2>➕ Add New Todo</h2>
281
+
282
+ <div className="add-todo-form">
283
+ <input
284
+ type="text"
285
+ value={newTask}
286
+ onChange={(e) => setNewTask(e.target.value)}
287
+ onKeyPress={handleKeyPress}
288
+ placeholder="What needs to be done?"
289
+ className="input todo-input"
290
+ disabled={isLoading}
291
+ />
292
+
293
+ <select
294
+ value={taskPriority}
295
+ onChange={(e) => setTaskPriority(e.target.value as 'high' | 'medium' | 'low')}
296
+ className="select priority-select"
297
+ disabled={isLoading}
298
+ >
299
+ <option value="low">🟢 Low Priority</option>
300
+ <option value="medium">🟡 Medium Priority</option>
301
+ <option value="high">🔴 High Priority</option>
302
+ </select>
303
+
304
+ <button
305
+ onClick={addTask}
306
+ className={`btn btn-primary add-btn ${isLoading ? 'loading' : ''}`}
307
+ disabled={isLoading || !newTask.trim()}
308
+ >
309
+ {isLoading ? '⏳ Adding...' : '➕ Add Todo'}
310
+ </button>
311
+ </div>
312
+ </section>
313
+
314
+ {/* Filters Section */}
315
+ <section className="card filters-section">
316
+ <h2>🔍 Filter Todos</h2>
317
+
318
+ <div className="filters">
319
+ {(['all', 'active', 'completed', 'high', 'medium', 'low'] as const).map(filterType => (
320
+ <button
321
+ key={filterType}
322
+ onClick={() => setTaskFilter(filterType)}
323
+ className={`btn btn-sm filter-btn ${taskFilter === filterType ? 'btn-primary' : 'btn-outline'}`}
324
+ >
325
+ {filterType === 'all' && '📋 All'}
326
+ {filterType === 'active' && '⏳ Active'}
327
+ {filterType === 'completed' && '✅ Completed'}
328
+ {filterType === 'high' && '🔴 High Priority'}
329
+ {filterType === 'medium' && '🟡 Medium Priority'}
330
+ {filterType === 'low' && '🟢 Low Priority'}
331
+ </button>
332
+ ))}
333
+ </div>
334
+
335
+ <div className="bulk-actions">
336
+ <button
337
+ onClick={markAllComplete}
338
+ className="btn btn-success btn-sm"
339
+ disabled={todoStats.active === 0}
340
+ >
341
+ ✅ Mark All Complete
342
+ </button>
343
+ <button
344
+ onClick={clearCompleted}
345
+ className="btn btn-warning btn-sm"
346
+ disabled={todoStats.completed === 0}
347
+ >
348
+ 🗑️ Clear Completed
349
+ </button>
350
+ </div>
351
+ </section>
352
+
353
+ {/* Todos List Section */}
354
+ <section className="card todos-section">
355
+ <div className="section-header">
356
+ <h2>📋 Todo List</h2>
357
+ <div className="filter-info">
358
+ Showing <strong>{filteredTodos.length}</strong> of <strong>{todoStats.total}</strong> todos
359
+ {taskFilter !== 'all' && <span className="filter-badge">{taskFilter}</span>}
360
+ </div>
361
+ </div>
362
+
363
+ <div className="todos-list">
364
+ {filteredTodos.length > 0 ? (
365
+ filteredTodos.map(todo => (
366
+ <TodoItem
367
+ key={todo.id}
368
+ todo={todo}
369
+ onToggle={handleToggleTodo}
370
+ onDelete={handleDeleteTodo}
371
+ />
372
+ ))
373
+ ) : (
374
+ <div className="empty-state">
375
+ <p>
376
+ {taskFilter === 'all' ? '🎉 No todos yet. Add one above!' :
377
+ taskFilter === 'completed' ? '📝 No completed todos yet.' :
378
+ taskFilter === 'active' ? '🎯 No active todos. Great job!' :
379
+ `🔍 No ${taskFilter} priority todos found.`}
380
+ </p>
381
+ </div>
382
+ )}
383
+ </div>
384
+ </section>
385
+
386
+ {/* Actions Section */}
387
+ <section className="card actions-section">
388
+ <h2>⚙️ Actions</h2>
389
+ <div className="action-buttons">
390
+ <button onClick={simulateError} className="btn btn-danger">
391
+ 💥 Test Error Boundary
392
+ </button>
393
+ </div>
394
+ </section>
395
+
396
+ </div>
397
+ </main>
398
+
399
+ {/* Footer */}
400
+ <AppFooter theme={theme} isBrowser={isBrowser} />
401
+
402
+ {/* Styles */}
403
+ <style>{`
404
+ * {
405
+ margin: 0;
406
+ padding: 0;
407
+ box-sizing: border-box;
408
+ }
409
+
410
+ :root {
411
+ --primary: #3b82f6;
412
+ --primary-dark: #2563eb;
413
+ --secondary: #6b7280;
414
+ --success: #10b981;
415
+ --warning: #f59e0b;
416
+ --danger: #ef4444;
417
+ --background: #ffffff;
418
+ --surface: #f9fafb;
419
+ --text: #111827;
420
+ --text-muted: #6b7280;
421
+ --border: #e5e7eb;
422
+ --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
423
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
424
+ }
425
+
426
+ [data-theme="dark"] {
427
+ --primary: #3b82f6;
428
+ --secondary: #9ca3af;
429
+ --success: #10b981;
430
+ --warning: #f59e0b;
431
+ --danger: #ef4444;
432
+ --background: #111827;
433
+ --surface: #1f2937;
434
+ --text: #f9fafb;
435
+ --text-muted: #9ca3af;
436
+ --border: #374151;
437
+ }
438
+
439
+ body {
440
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
441
+ line-height: 1.6;
442
+ color: var(--text);
443
+ background-color: var(--background);
444
+ transition: all 0.3s ease;
445
+ }
446
+
447
+ .app {
448
+ min-height: 100vh;
449
+ display: flex;
450
+ flex-direction: column;
451
+ }
452
+
453
+ .container {
454
+ max-width: 800px;
455
+ margin: 0 auto;
456
+ padding: 0 1rem;
457
+ }
458
+
459
+ /* Header */
460
+ .app-header {
461
+ background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
462
+ color: white;
463
+ padding: 2rem 0;
464
+ box-shadow: var(--shadow-lg);
465
+ }
466
+
467
+ .app-header .container {
468
+ display: flex;
469
+ justify-content: space-between;
470
+ align-items: center;
471
+ flex-wrap: wrap;
472
+ gap: 1rem;
473
+ }
474
+
475
+ .app-title {
476
+ font-size: 2rem;
477
+ font-weight: 700;
478
+ margin: 0;
479
+ display: flex;
480
+ flex-direction: column;
481
+ gap: 0.25rem;
482
+ }
483
+
484
+ .subtitle {
485
+ font-size: 1rem;
486
+ font-weight: 400;
487
+ opacity: 0.9;
488
+ }
489
+
490
+ .header-controls {
491
+ display: flex;
492
+ align-items: center;
493
+ gap: 1rem;
494
+ }
495
+
496
+ .stats {
497
+ display: flex;
498
+ gap: 1rem;
499
+ font-size: 0.875rem;
500
+ }
501
+
502
+ .stat {
503
+ background: rgba(255, 255, 255, 0.2);
504
+ padding: 0.5rem 0.75rem;
505
+ border-radius: 0.5rem;
506
+ backdrop-filter: blur(10px);
507
+ }
508
+
509
+ /* Main content */
510
+ .main-content {
511
+ flex: 1;
512
+ padding: 2rem 0;
513
+ }
514
+
515
+ /* Cards */
516
+ .card {
517
+ background: var(--surface);
518
+ border-radius: 1rem;
519
+ padding: 1.5rem;
520
+ margin-bottom: 1.5rem;
521
+ box-shadow: var(--shadow);
522
+ border: 1px solid var(--border);
523
+ transition: transform 0.2s ease;
524
+ }
525
+
526
+ .card:hover {
527
+ transform: translateY(-2px);
528
+ }
529
+
530
+ .card h2 {
531
+ font-size: 1.25rem;
532
+ margin-bottom: 1rem;
533
+ color: var(--text);
534
+ }
535
+
536
+ /* Add Todo Form */
537
+ .add-todo-form {
538
+ display: flex;
539
+ gap: 0.75rem;
540
+ flex-wrap: wrap;
541
+ }
542
+
543
+ .todo-input {
544
+ flex: 1;
545
+ min-width: 250px;
546
+ }
547
+
548
+ .priority-select {
549
+ min-width: 150px;
550
+ }
551
+
552
+ /* Filters */
553
+ .filters {
554
+ display: flex;
555
+ gap: 0.5rem;
556
+ margin-bottom: 1rem;
557
+ flex-wrap: wrap;
558
+ }
559
+
560
+ .filter-btn {
561
+ font-size: 0.875rem;
562
+ }
563
+
564
+ .bulk-actions {
565
+ display: flex;
566
+ gap: 0.5rem;
567
+ flex-wrap: wrap;
568
+ }
569
+
570
+ /* Section Header */
571
+ .section-header {
572
+ display: flex;
573
+ justify-content: space-between;
574
+ align-items: center;
575
+ margin-bottom: 1rem;
576
+ flex-wrap: wrap;
577
+ gap: 1rem;
578
+ }
579
+
580
+ .filter-info {
581
+ font-size: 0.875rem;
582
+ color: var(--text-muted);
583
+ }
584
+
585
+ .filter-badge {
586
+ background: var(--primary);
587
+ color: white;
588
+ padding: 0.25rem 0.5rem;
589
+ border-radius: 0.25rem;
590
+ font-size: 0.75rem;
591
+ margin-left: 0.5rem;
592
+ }
593
+
594
+ /* Todos List */
595
+ .todos-list {
596
+ display: flex;
597
+ flex-direction: column;
598
+ gap: 0.75rem;
599
+ }
600
+
601
+ .todo-item {
602
+ display: flex;
603
+ align-items: center;
604
+ gap: 0.75rem;
605
+ padding: 1rem;
606
+ background: var(--background);
607
+ border-radius: 0.5rem;
608
+ border: 1px solid var(--border);
609
+ transition: all 0.2s ease;
610
+ }
611
+
612
+ .todo-item:hover {
613
+ border-color: var(--primary);
614
+ box-shadow: var(--shadow);
615
+ }
616
+
617
+ .todo-item.completed {
618
+ opacity: 0.7;
619
+ }
620
+
621
+ .todo-item.completed .todo-text {
622
+ text-decoration: line-through;
623
+ }
624
+
625
+ .todo-checkbox {
626
+ width: 1.25rem;
627
+ height: 1.25rem;
628
+ cursor: pointer;
629
+ }
630
+
631
+ .todo-text {
632
+ flex: 1;
633
+ font-size: 1rem;
634
+ }
635
+
636
+ .todo-priority {
637
+ padding: 0.25rem 0.5rem;
638
+ border-radius: 0.25rem;
639
+ font-size: 0.75rem;
640
+ font-weight: 600;
641
+ }
642
+
643
+ .priority-high {
644
+ background: #fee2e2;
645
+ color: #991b1b;
646
+ }
647
+
648
+ .priority-medium {
649
+ background: #fef3c7;
650
+ color: #92400e;
651
+ }
652
+
653
+ .priority-low {
654
+ background: #dcfce7;
655
+ color: #166534;
656
+ }
657
+
658
+ .todo-actions {
659
+ display: flex;
660
+ gap: 0.5rem;
661
+ }
662
+
663
+ /* Form elements */
664
+ .input, .select {
665
+ padding: 0.75rem;
666
+ border: 2px solid var(--border);
667
+ border-radius: 0.5rem;
668
+ font-size: 1rem;
669
+ background: var(--background);
670
+ color: var(--text);
671
+ transition: border-color 0.2s ease;
672
+ }
673
+
674
+ .input:focus, .select:focus {
675
+ outline: none;
676
+ border-color: var(--primary);
677
+ }
678
+
679
+ /* Buttons */
680
+ .btn {
681
+ display: inline-flex;
682
+ align-items: center;
683
+ justify-content: center;
684
+ gap: 0.5rem;
685
+ padding: 0.75rem 1.5rem;
686
+ border: none;
687
+ border-radius: 0.5rem;
688
+ font-size: 0.875rem;
689
+ font-weight: 500;
690
+ cursor: pointer;
691
+ transition: all 0.2s ease;
692
+ text-decoration: none;
693
+ user-select: none;
694
+ }
695
+
696
+ .btn:disabled {
697
+ opacity: 0.5;
698
+ cursor: not-allowed;
699
+ }
700
+
701
+ .btn-sm {
702
+ padding: 0.5rem 1rem;
703
+ font-size: 0.75rem;
704
+ }
705
+
706
+ .btn-primary {
707
+ background: var(--primary);
708
+ color: white;
709
+ }
710
+
711
+ .btn-primary:hover:not(:disabled) {
712
+ background: var(--primary-dark);
713
+ transform: translateY(-1px);
714
+ }
715
+
716
+ .btn-secondary {
717
+ background: var(--secondary);
718
+ color: white;
719
+ }
720
+
721
+ .btn-success {
722
+ background: var(--success);
723
+ color: white;
724
+ }
725
+
726
+ .btn-warning {
727
+ background: var(--warning);
728
+ color: white;
729
+ }
730
+
731
+ .btn-danger {
732
+ background: var(--danger);
733
+ color: white;
734
+ }
735
+
736
+ .btn-outline {
737
+ background: transparent;
738
+ color: var(--text);
739
+ border: 2px solid var(--border);
740
+ }
741
+
742
+ .btn-outline:hover:not(:disabled) {
743
+ background: var(--surface);
744
+ border-color: var(--primary);
745
+ }
746
+
747
+ .btn.loading {
748
+ position: relative;
749
+ color: transparent;
750
+ }
751
+
752
+ .btn.loading::after {
753
+ content: '';
754
+ position: absolute;
755
+ width: 1rem;
756
+ height: 1rem;
757
+ border: 2px solid transparent;
758
+ border-top: 2px solid currentColor;
759
+ border-radius: 50%;
760
+ animation: spin 1s linear infinite;
761
+ color: white;
762
+ }
763
+
764
+ @keyframes spin {
765
+ to { transform: rotate(360deg); }
766
+ }
767
+
768
+ .theme-toggle {
769
+ border-radius: 50%;
770
+ width: 3rem;
771
+ height: 3rem;
772
+ padding: 0;
773
+ font-size: 1.25rem;
774
+ background: rgba(255, 255, 255, 0.2);
775
+ color: white;
776
+ border: 2px solid rgba(255, 255, 255, 0.3);
777
+ }
778
+
779
+ .theme-toggle:hover {
780
+ background: rgba(255, 255, 255, 0.3);
781
+ transform: scale(1.05);
782
+ }
783
+
784
+ /* Empty state */
785
+ .empty-state {
786
+ text-align: center;
787
+ padding: 3rem 1rem;
788
+ color: var(--text-muted);
789
+ }
790
+
791
+ .empty-state p {
792
+ font-size: 1.1rem;
793
+ }
794
+
795
+ /* Actions section */
796
+ .action-buttons {
797
+ display: flex;
798
+ gap: 1rem;
799
+ flex-wrap: wrap;
800
+ }
801
+
802
+ /* Footer */
803
+ .app-footer {
804
+ background: var(--surface);
805
+ border-top: 1px solid var(--border);
806
+ padding: 1.5rem 0;
807
+ text-align: center;
808
+ color: var(--text-muted);
809
+ font-size: 0.875rem;
810
+ }
811
+
812
+ .app-footer p {
813
+ margin: 0.25rem 0;
814
+ }
815
+
816
+ /* Error container */
817
+ .error-container {
818
+ display: flex;
819
+ flex-direction: column;
820
+ align-items: center;
821
+ justify-content: center;
822
+ min-height: 100vh;
823
+ padding: 2rem;
824
+ text-align: center;
825
+ }
826
+
827
+ .error-container h2 {
828
+ color: var(--danger);
829
+ margin-bottom: 1rem;
830
+ }
831
+
832
+ .error-container p {
833
+ color: var(--text-muted);
834
+ margin-bottom: 2rem;
835
+ }
836
+
837
+ /* Responsive design */
838
+ @media (max-width: 768px) {
839
+ .app-header .container {
840
+ flex-direction: column;
841
+ text-align: center;
842
+ }
843
+
844
+ .app-title {
845
+ font-size: 1.75rem;
846
+ }
847
+
848
+ .add-todo-form {
849
+ flex-direction: column;
850
+ }
851
+
852
+ .todo-input, .priority-select {
853
+ min-width: auto;
854
+ width: 100%;
855
+ }
856
+
857
+ .section-header {
858
+ flex-direction: column;
859
+ align-items: stretch;
860
+ }
861
+
862
+ .stats {
863
+ justify-content: center;
864
+ }
865
+
866
+ .filters {
867
+ justify-content: center;
868
+ }
869
+
870
+ .bulk-actions {
871
+ justify-content: center;
872
+ }
873
+ }
874
+
875
+ /* Smooth transitions */
876
+ * {
877
+ transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
878
+ }
879
+ `}</style>
17
880
  </div>
18
881
  );
19
882
  }