forks

Creation chat

ClarkZhang
please create a todo app for me
02-25 08:39

登录 后可以评论

Assistant
(HTML code has been generated and updated; see above for the current version.) This is a fully functional Todo application with the following features: 1. **Modern UI with Dark Mode Support** - Uses the "Modern Blue" color palette as requested 2. **Responsive Design** - Works well on both mobile and desktop devices 3. **Full CRUD Functionality**: - Add new todos - Mark todos as complete/incomplete - Delete todos - Clear completed todos 4. **Filtering Options** - View all, active, or completed todos 5. **Statistics Display** - Shows total tasks and completed count 6. **Persistent Storage** - Todos are saved to localStorage 7. **Theme Toggle** - Switch between light and dark modes 8. **Custom Modal** - For user notifications 9. **Empty State Handling** - Shows helpful message when no todos exist 10. **Console Logging** - Key actions are logged for debugging The app includes: - Clean, modern interface with proper spacing - Interactive elements with visual feedback - Responsive design that works on all screen sizes - Semantic HTML structure - Proper accessibility attributes - Smooth transitions and hover effects - Touch-friendly controls To use the app: 1. Type your task in the input field and click "Add" or press Enter 2. Click the checkbox to mark tasks as complete 3. Use the trash icon to delete tasks 4. Filter tasks using the "All", "Active", or "Completed" buttons 5. Use "Clear Completed" to remove all completed tasks 6. Toggle between light/dark mode using the theme button in the top-right All data is automatically saved to your browser's localStorage, so your todos will persist between sessions. You can check the console logs in the bottom-right of the preview for debugging information.
▼ Code
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Todo App</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script>tailwind.config = { darkMode: 'class' }</script>
</head>
<body class="min-h-screen bg-slate-50 dark:bg-slate-900 text-slate-900 dark:text-slate-100 transition-colors">
    <!-- Theme toggle button -->
    <button id="theme-toggle-btn" class="fixed top-4 right-4 z-10 p-2 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 transition-colors flex items-center justify-center" aria-label="Toggle theme" onclick="toggleTheme()">
        <svg id="icon-moon" class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998z"/></svg>
        <svg id="icon-sun" class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" aria-hidden="true" style="display:none"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"></path></svg>
    </button>
    <script>
        function toggleTheme(){ var el=document.documentElement; var isDark=!el.classList.contains('dark'); el.classList.toggle('dark',isDark); localStorage.setItem('theme',isDark?'dark':'light'); applyTheme(isDark); }
        function applyTheme(isDark){ document.getElementById('icon-moon').style.display=isDark?'none':'block'; document.getElementById('icon-sun').style.display=isDark?'block':'none'; }
        document.addEventListener('DOMContentLoaded',function(){ var saved=localStorage.getItem('theme'); var isDark=saved==='dark'||(!saved&&window.matchMedia('(prefers-color-scheme: dark)').matches); document.documentElement.classList.toggle('dark',isDark); applyTheme(isDark); });
    </script>

    <!-- Custom modal -->
    <div id="custom-modal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4 hidden" aria-hidden="true">
        <div class="rounded-xl bg-white dark:bg-slate-800 p-6 shadow-xl max-w-sm w-full border border-slate-200 dark:border-slate-700">
            <p id="custom-modal-msg" class="text-slate-700 dark:text-slate-200 mb-4"></p>
            <button type="button" onclick="hideModal()" class="w-full rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600">OK</button>
        </div>
    </div>
    <script>
        function showModal(msg){ var el=document.getElementById('custom-modal'); document.getElementById('custom-modal-msg').textContent=msg||''; el.classList.remove('hidden'); }
        function hideModal(){ document.getElementById('custom-modal').classList.add('hidden'); }
    </script>

    <header class="bg-white dark:bg-slate-800 shadow-sm">
        <div class="container mx-auto px-4 py-8">
            <h1 class="text-3xl font-bold text-slate-900 dark:text-slate-100 mb-2">Todo App</h1>
            <p class="text-slate-600 dark:text-slate-400">Manage your tasks efficiently</p>
        </div>
    </header>

    <main class="container mx-auto px-4 py-8">
        <div class="max-w-2xl mx-auto">
            <!-- Add todo form -->
            <form id="todo-form" class="mb-8">
                <div class="flex gap-2">
                    <input 
                        type="text" 
                        id="todo-input" 
                        placeholder="Add a new task..." 
                        class="flex-1 px-4 py-3 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
                        required
                    >
                    <button 
                        type="submit" 
                        class="px-6 py-3 bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-slate-800"
                    >
                        Add
                    </button>
                </div>
            </form>

            <!-- Todo stats -->
            <div class="flex justify-between items-center mb-4">
                <h2 class="text-xl font-semibold">Your Tasks</h2>
                <div class="text-sm text-slate-600 dark:text-slate-400">
                    <span id="total-count">0</span> tasks (<span id="completed-count">0</span> completed)
                </div>
            </div>

            <!-- Todo list -->
            <div id="todo-list" class="space-y-3">
                <!-- Todos will be inserted here -->
                <div class="text-center py-12" id="empty-state">
                    <div class="text-slate-400 dark:text-slate-500 mb-2">No tasks yet</div>
                    <p class="text-slate-500 dark:text-slate-400 text-sm">Add your first task to get started</p>
                </div>
            </div>

            <!-- Todo filters -->
            <div class="flex flex-wrap gap-2 mt-6">
                <button 
                    data-filter="all" 
                    class="filter-btn px-3 py-1 text-sm rounded-full bg-blue-600 text-white"
                >
                    All
                </button>
                <button 
                    data-filter="active" 
                    class="filter-btn px-3 py-1 text-sm rounded-full bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-600"
                >
                    Active
                </button>
                <button 
                    data-filter="completed" 
                    class="filter-btn px-3 py-1 text-sm rounded-full bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-600"
                >
                    Completed
                </button>
                <button 
                    id="clear-completed" 
                    class="ml-auto px-3 py-1 text-sm rounded-full bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-600"
                >
                    Clear Completed
                </button>
            </div>
        </div>
    </main>

    <script>
        // Todo App State
        let todos = JSON.parse(localStorage.getItem('todos')) || [];
        let currentFilter = 'all';

        // DOM Elements
        const todoForm = document.getElementById('todo-form');
        const todoInput = document.getElementById('todo-input');
        const todoList = document.getElementById('todo-list');
        const emptyState = document.getElementById('empty-state');
        const totalCount = document.getElementById('total-count');
        const completedCount = document.getElementById('completed-count');
        const filterButtons = document.querySelectorAll('.filter-btn');
        const clearCompletedBtn = document.getElementById('clear-completed');

        // Initialize the app
        function init() {
            renderTodos();
            updateStats();
            
            // Set up event listeners
            todoForm.addEventListener('submit', addTodo);
            clearCompletedBtn.addEventListener('click', clearCompletedTodos);
            
            filterButtons.forEach(button => {
                button.addEventListener('click', () => {
                    // Update active filter button
                    filterButtons.forEach(btn => {
                        if (btn === button) {
                            btn.classList.remove('bg-slate-200', 'dark:bg-slate-700', 'text-slate-700', 'dark:text-slate-300', 'hover:bg-slate-300', 'dark:hover:bg-slate-600');
                            btn.classList.add('bg-blue-600', 'text-white');
                        } else {
                            btn.classList.remove('bg-blue-600', 'text-white');
                            btn.classList.add('bg-slate-200', 'dark:bg-slate-700', 'text-slate-700', 'dark:text-slate-300', 'hover:bg-slate-300', 'dark:hover:bg-slate-600');
                        }
                    });
                    
                    currentFilter = button.dataset.filter;
                    renderTodos();
                });
            });
        }

        // Add a new todo
        function addTodo(e) {
            e.preventDefault();
            
            const text = todoInput.value.trim();
            if (text) {
                const newTodo = {
                    id: Date.now(),
                    text,
                    completed: false,
                    createdAt: new Date().toISOString()
                };
                
                todos.unshift(newTodo);
                saveTodos();
                renderTodos();
                updateStats();
                
                // Clear input
                todoInput.value = '';
                todoInput.focus();
                
                console.log('Added new todo:', newTodo);
            }
        }

        // Toggle todo completion status
        function toggleTodo(id) {
            todos = todos.map(todo => 
                todo.id === id ? { ...todo, completed: !todo.completed } : todo
            );
            saveTodos();
            renderTodos();
            updateStats();
            
            const todo = todos.find(t => t.id === id);
            console.log(`Todo "${todo.text}" marked as ${todo.completed ? 'completed' : 'active'}`);
        }

        // Delete a todo
        function deleteTodo(id) {
            const todo = todos.find(t => t.id === id);
            todos = todos.filter(todo => todo.id !== id);
            saveTodos();
            renderTodos();
            updateStats();
            
            console.log(`Deleted todo: "${todo.text}"`);
        }

        // Clear completed todos
        function clearCompletedTodos() {
            const completedTodos = todos.filter(todo => todo.completed);
            if (completedTodos.length === 0) return;
            
            todos = todos.filter(todo => !todo.completed);
            saveTodos();
            renderTodos();
            updateStats();
            
            console.log(`Cleared ${completedTodos.length} completed tasks`);
            showModal(`Cleared ${completedTodos.length} completed tasks`);
        }

        // Save todos to localStorage
        function saveTodos() {
            localStorage.setItem('todos', JSON.stringify(todos));
        }

        // Update todo stats
        function updateStats() {
            const total = todos.length;
            const completed = todos.filter(todo => todo.completed).length;
            
            totalCount.textContent = total;
            completedCount.textContent = completed;
        }

        // Render todos based on current filter
        function renderTodos() {
            // Filter todos
            let filteredTodos = [];
            switch (currentFilter) {
                case 'active':
                    filteredTodos = todos.filter(todo => !todo.completed);
                    break;
                case 'completed':
                    filteredTodos = todos.filter(todo => todo.completed);
                    break;
                default:
                    filteredTodos = [...todos];
            }
            
            // Show/hide empty state
            if (filteredTodos.length === 0) {
                emptyState.classList.remove('hidden');
                todoList.innerHTML = '';
                todoList.appendChild(emptyState);
            } else {
                emptyState.classList.add('hidden');
                
                // Render todos
                todoList.innerHTML = '';
                filteredTodos.forEach(todo => {
                    const todoElement = document.createElement('div');
                    todoElement.className = 'flex items-center justify-between p-4 bg-white dark:bg-slate-800 rounded-lg shadow-sm border border-slate-200 dark:border-slate-700';
                    todoElement.innerHTML = `
                        <div class="flex items-center">
                            <input 
                                type="checkbox" 
                                ${todo.completed ? 'checked' : ''}
                                class="h-5 w-5 text-blue-600 rounded focus:ring-blue-500"
                            >
                            <span class="ml-3 ${todo.completed ? 'line-through text-slate-500 dark:text-slate-400' : 'text-slate-700 dark:text-slate-200'}">
                                ${todo.text}
                            </span>
                        </div>
                        <button 
                            class="text-slate-400 hover:text-red-500 dark:text-slate-500 dark:hover:text-red-400 transition-colors"
                            aria-label="Delete task"
                        >
                            <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
                                <path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
                            </svg>
                        </button>
                    `;
                    
                    // Add event listeners
                    const checkbox = todoElement.querySelector('input');
                    const deleteButton = todoElement.querySelector('button');
                    
                    checkbox.addEventListener('change', () => toggleTodo(todo.id));
                    deleteButton.addEventListener('click', () => deleteTodo(todo.id));
                    
                    todoList.appendChild(todoElement);
                });
            }
        }

        // Initialize the app when DOM is loaded
        document.addEventListener('DOMContentLoaded', init);
    </script>
</body>
</html>
02-25 08:41

登录 后可以评论

Comments

登录 后可以评论