diff --git a/members/WenHaoHub/task1/.eslintrc.cjs b/members/WenHaoHub/task1/.eslintrc.cjs new file mode 100644 index 000000000..d6c953795 --- /dev/null +++ b/members/WenHaoHub/task1/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/members/WenHaoHub/task1/.gitignore b/members/WenHaoHub/task1/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/members/WenHaoHub/task1/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/members/WenHaoHub/task1/README.md b/members/WenHaoHub/task1/README.md new file mode 100644 index 000000000..85a2f35ce --- /dev/null +++ b/members/WenHaoHub/task1/README.md @@ -0,0 +1,3 @@ +# Task1 + todilist + diff --git a/members/WenHaoHub/task1/index.html b/members/WenHaoHub/task1/index.html new file mode 100644 index 000000000..830244167 --- /dev/null +++ b/members/WenHaoHub/task1/index.html @@ -0,0 +1,16 @@ + + + + + + + + Todo + + + +
+ + + + \ No newline at end of file diff --git a/members/WenHaoHub/task1/package.json b/members/WenHaoHub/task1/package.json new file mode 100644 index 000000000..b0a54a50a --- /dev/null +++ b/members/WenHaoHub/task1/package.json @@ -0,0 +1,32 @@ +{ + "name": "task1", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "clsx": "^2.1.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react-swc": "^3.5.0", + "autoprefixer": "^10.4.19", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.3", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } +} diff --git a/members/WenHaoHub/task1/postcss.config.js b/members/WenHaoHub/task1/postcss.config.js new file mode 100644 index 000000000..2e7af2b7f --- /dev/null +++ b/members/WenHaoHub/task1/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/members/WenHaoHub/task1/public/vite.svg b/members/WenHaoHub/task1/public/vite.svg new file mode 100644 index 000000000..e7b8dfb1b --- /dev/null +++ b/members/WenHaoHub/task1/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/members/WenHaoHub/task1/src/App.css b/members/WenHaoHub/task1/src/App.css new file mode 100644 index 000000000..e090c7d50 --- /dev/null +++ b/members/WenHaoHub/task1/src/App.css @@ -0,0 +1,47 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} + +.todo { + display: flex; + height: 65px; +} diff --git a/members/WenHaoHub/task1/src/App.tsx b/members/WenHaoHub/task1/src/App.tsx new file mode 100644 index 000000000..f635ddb25 --- /dev/null +++ b/members/WenHaoHub/task1/src/App.tsx @@ -0,0 +1,34 @@ +import { useEffect, useState } from 'react' +import './App.css' +import Header from './componets/Header' +import AddToDo from './componets/AddToDo' +import ToDoList from './componets/ToDoList' + +export interface Todo { + id: string + text: string + completed: boolean +} + +const STORAGE_KEY = 'todo' + +function App() { + const [data, setData] = useState(JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')) + + useEffect(() => { + localStorage.setItem(STORAGE_KEY, JSON.stringify(data)) + }, [data]) + + return ( +
+
+ + +
+ ) +} + +export default App diff --git a/members/WenHaoHub/task1/src/assets/react.svg b/members/WenHaoHub/task1/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/members/WenHaoHub/task1/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/members/WenHaoHub/task1/src/componets/AddToDo.css b/members/WenHaoHub/task1/src/componets/AddToDo.css new file mode 100644 index 000000000..d1cc56e99 --- /dev/null +++ b/members/WenHaoHub/task1/src/componets/AddToDo.css @@ -0,0 +1,25 @@ +.toggle-all { + width: 1px; + height: 1px; + border: none; + opacity: 0; +} + +.toggle-all + label { + display: flex; + align-items: center; + justify-content: center; + width: 45px; + height: 65px; + font-size: 0; +} + +.toggle-all + label:before { + content: '❯'; + display: inline-block; + font-size: 22px; + color: #949494; + padding: 10px 27px 10px 27px; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); +} diff --git a/members/WenHaoHub/task1/src/componets/AddToDo.tsx b/members/WenHaoHub/task1/src/componets/AddToDo.tsx new file mode 100644 index 000000000..8cc9a729c --- /dev/null +++ b/members/WenHaoHub/task1/src/componets/AddToDo.tsx @@ -0,0 +1,69 @@ +import { useCallback, useState } from 'react' +import { Todo } from '../App' +import './AddToDo.css' + +function AddToDo({ addItem }: { addItem: React.Dispatch> }) { + const [text, setText] = useState('') + const [isSelect, setSelect] = useState(false); + + const handleChange = useCallback((e: React.ChangeEvent) => { + setText(e.target.value) + }, []) + + const handleAdd = + (e: React.KeyboardEvent) => { + if (e.key !== 'Enter') return + if (text.trim() === '') return //原生方法 + //写函数 表示前一次的state, + addItem((prev) => { + console.log('>>>prev', prev); + return [...prev, { id: Date.now().toString(), text, completed: false }] + }) + setText('') + } + + + + const selectAll = (e: React.ChangeEvent) => { + addItem((prev) => { + const isAllCompleted = prev.every((todo) => todo.completed) + if (e.target.checked === false && isAllCompleted) { + setSelect(e.target.checked) + return prev.map((todo) => ({ ...todo, completed: !isAllCompleted })) + } else if (e.target.checked === false && !isAllCompleted) { + return prev.map((todo) => ({ ...todo, completed: !isAllCompleted })) + } else if (e.target.checked === true && isAllCompleted) { + setSelect(e.target.checked) + return prev.map((todo) => ({ ...todo, completed: true })) + + } else { + setSelect(e.target.checked) + return prev.map((todo) => ({ ...todo, completed: !isAllCompleted })) + } + }) + } + + return ( +
+ + + +
+ ) +} + +export default AddToDo diff --git a/members/WenHaoHub/task1/src/componets/Header.tsx b/members/WenHaoHub/task1/src/componets/Header.tsx new file mode 100644 index 000000000..6d74f1c1d --- /dev/null +++ b/members/WenHaoHub/task1/src/componets/Header.tsx @@ -0,0 +1,9 @@ +function Header() { + return ( +
+

TodoList

+
+ ) +} + +export default Header diff --git a/members/WenHaoHub/task1/src/componets/ToDoItem.tsx b/members/WenHaoHub/task1/src/componets/ToDoItem.tsx new file mode 100644 index 000000000..506abac74 --- /dev/null +++ b/members/WenHaoHub/task1/src/componets/ToDoItem.tsx @@ -0,0 +1,29 @@ +import { clsx } from 'clsx' +import { Todo } from '../App' + +type ToDoItemProps = { + todo: Todo + onToggle: (id: string) => void + removeItem: (id: string) => void +} + +function ToDoItem({ todo, onToggle, removeItem }: ToDoItemProps) { + console.log('>>>ToDoItem',); + return ( +
+ onToggle(todo.id)} + /> + {todo.text} + +
+ ) +} + +export default ToDoItem diff --git a/members/WenHaoHub/task1/src/componets/ToDoList.tsx b/members/WenHaoHub/task1/src/componets/ToDoList.tsx new file mode 100644 index 000000000..7b30a1854 --- /dev/null +++ b/members/WenHaoHub/task1/src/componets/ToDoList.tsx @@ -0,0 +1,86 @@ +import { clsx } from 'clsx' +import { useEffect, useState } from 'react' +import { Todo } from '../App' +import ToDoItem from './ToDoItem' + +function ToDoList({ data, updateData }: { data: Todo[]; updateData: React.Dispatch> }) { + const [showData, setShowData] = useState(data) + const [currentType, setCurrentType] = useState<'all' | 'active' | 'completed'>('all') + + const onToggle = (id: string) => { + updateData((prev) => prev.map((todo) => (todo.id === id ? { ...todo, completed: !todo.completed } : todo))) + } + + const onClearCompleted = () => { + updateData((prev) => prev.filter((todo) => !todo.completed)) + } + + const onFilter = (type: 'all' | 'active' | 'completed') => { + setCurrentType(type) + } + + const removeItem = (id: string) => { + updateData((prev) => prev.filter((todo) => todo.id !== id)) + } + useEffect(() => { + setShowData( + data.filter((todo) => { + if (currentType === 'all') return true + if (currentType === 'active') return !todo.completed + if (currentType === 'completed') return todo.completed + return false + }), + ) + }, [currentType, data]) + + return ( +
+
    + {showData.map((todo) => ( + + ))} +
+
+
+ + + +
+ {data.filter((todo) => !todo.completed).length} 条待完成 + + {/*
+ {data.some((todo) => todo.completed) && ( + + )} +
*/} +
+
+ ) +} + +export default ToDoList diff --git a/members/WenHaoHub/task1/src/index.css b/members/WenHaoHub/task1/src/index.css new file mode 100644 index 000000000..0e2d22291 --- /dev/null +++ b/members/WenHaoHub/task1/src/index.css @@ -0,0 +1,101 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: flex-start; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +.button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.3em 0.6em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +.button:hover { + border-color: #646cff; +} +.button:focus, +.button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + .button { + background-color: #f9f9f9; + } +} + +.completed { + color: #949494; + text-decoration: line-through; +} + +.selected { + border-color: #646cff; +} + +.todo-item .destroy { + display: none; + background-color: transparent; +} + +.todo-item .destroy:hover { + border: 0; +} + +.todo-item:hover .destroy { + display: block; +} + +.todo-item .destroy::after { + content: '×'; + display: block; + height: 100%; + line-height: 1.1; +} diff --git a/members/WenHaoHub/task1/src/main.tsx b/members/WenHaoHub/task1/src/main.tsx new file mode 100644 index 000000000..3d7150da8 --- /dev/null +++ b/members/WenHaoHub/task1/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/members/WenHaoHub/task1/src/vite-env.d.ts b/members/WenHaoHub/task1/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/members/WenHaoHub/task1/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/members/WenHaoHub/task1/tailwind.config.js b/members/WenHaoHub/task1/tailwind.config.js new file mode 100644 index 000000000..d37737fc0 --- /dev/null +++ b/members/WenHaoHub/task1/tailwind.config.js @@ -0,0 +1,12 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], +} + diff --git a/members/WenHaoHub/task1/tsconfig.json b/members/WenHaoHub/task1/tsconfig.json new file mode 100644 index 000000000..a7fc6fbf2 --- /dev/null +++ b/members/WenHaoHub/task1/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/members/WenHaoHub/task1/tsconfig.node.json b/members/WenHaoHub/task1/tsconfig.node.json new file mode 100644 index 000000000..97ede7ee6 --- /dev/null +++ b/members/WenHaoHub/task1/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/members/WenHaoHub/task1/vite.config.ts b/members/WenHaoHub/task1/vite.config.ts new file mode 100644 index 000000000..861b04b35 --- /dev/null +++ b/members/WenHaoHub/task1/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react-swc' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +})