From 90ddbc6fc24f48419cbe81ab0b3b0b6a4e74066c Mon Sep 17 00:00:00 2001 From: HM Nayem Date: Sun, 16 Feb 2025 00:01:30 +0600 Subject: [PATCH 1/2] lecture 1 completed --- index.html | 6 +- package.json | 4 + pnpm-lock.yaml | 411 +++++++++++++++++++++++--- src/App.css | 42 --- src/App.tsx | 110 ++++--- src/App_lecture1.tsx | 70 +++++ src/assets/react.svg | 1 - src/demo/hoc/example.tsx | 67 +++++ src/demo/hoc/withDataFetch.tsx | 48 +++ src/examples/EX1_SRP.tsx | 152 ++++++++++ src/examples/EX2_SOC.tsx | 56 ++++ src/examples/EX3_ProjectDashboard.tsx | 271 +++++++++++++++++ src/examples/EX4_HOC.tsx | 45 +++ src/examples/EX5_RP1.tsx | 21 ++ src/examples/EX6_RP2.tsx | 34 +++ src/examples/EX7_RP3.tsx | 32 ++ src/examples/EX8.RP4.tsx | 51 ++++ src/index.css | 78 +---- src/main.tsx | 2 +- vite.config.ts | 2 + 20 files changed, 1317 insertions(+), 186 deletions(-) delete mode 100644 src/App.css create mode 100644 src/App_lecture1.tsx delete mode 100644 src/assets/react.svg create mode 100644 src/demo/hoc/example.tsx create mode 100644 src/demo/hoc/withDataFetch.tsx create mode 100644 src/examples/EX1_SRP.tsx create mode 100644 src/examples/EX2_SOC.tsx create mode 100644 src/examples/EX3_ProjectDashboard.tsx create mode 100644 src/examples/EX4_HOC.tsx create mode 100644 src/examples/EX5_RP1.tsx create mode 100644 src/examples/EX6_RP2.tsx create mode 100644 src/examples/EX7_RP3.tsx create mode 100644 src/examples/EX8.RP4.tsx diff --git a/index.html b/index.html index e4b78ea..6693102 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,11 @@ - Vite + React + TS + + React Design Patterns + + +
diff --git a/package.json b/package.json index 83ccf11..e344965 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@tailwindcss/vite": "^4.0.6", "react": "^19.0.0", "react-dom": "^19.0.0" }, @@ -19,12 +20,15 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", "babel-plugin-react-compiler": "19.0.0-beta-30d8a17-20250209", "eslint": "^9.19.0", "eslint-plugin-react-compiler": "19.0.0-beta-30d8a17-20250209", "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.18", "globals": "^15.14.0", + "postcss": "^8.5.2", + "tailwindcss": "^4.0.6", "typescript": "~5.7.2", "typescript-eslint": "^8.22.0", "vite": "^6.1.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24289dc..ced0499 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@tailwindcss/vite': + specifier: ^4.0.6 + version: 4.0.6(vite@6.1.0(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)) react: specifier: ^19.0.0 version: 19.0.0 @@ -29,34 +32,43 @@ importers: version: 19.0.3(@types/react@19.0.8) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.3.4(vite@6.1.0(@types/node@22.13.4)) + version: 4.3.4(vite@6.1.0(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)) + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.5.2) babel-plugin-react-compiler: specifier: 19.0.0-beta-30d8a17-20250209 version: 19.0.0-beta-30d8a17-20250209 eslint: specifier: ^9.19.0 - version: 9.20.1 + version: 9.20.1(jiti@2.4.2) eslint-plugin-react-compiler: specifier: 19.0.0-beta-30d8a17-20250209 - version: 19.0.0-beta-30d8a17-20250209(eslint@9.20.1) + version: 19.0.0-beta-30d8a17-20250209(eslint@9.20.1(jiti@2.4.2)) eslint-plugin-react-hooks: specifier: ^5.0.0 - version: 5.1.0(eslint@9.20.1) + version: 5.1.0(eslint@9.20.1(jiti@2.4.2)) eslint-plugin-react-refresh: specifier: ^0.4.18 - version: 0.4.19(eslint@9.20.1) + version: 0.4.19(eslint@9.20.1(jiti@2.4.2)) globals: specifier: ^15.14.0 version: 15.15.0 + postcss: + specifier: ^8.5.2 + version: 8.5.2 + tailwindcss: + specifier: ^4.0.6 + version: 4.0.6 typescript: specifier: ~5.7.2 version: 5.7.3 typescript-eslint: specifier: ^8.22.0 - version: 8.24.0(eslint@9.20.1)(typescript@5.7.3) + version: 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) vite: specifier: ^6.1.0 - version: 6.1.0(@types/node@22.13.4) + version: 6.1.0(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1) packages: @@ -511,6 +523,84 @@ packages: cpu: [x64] os: [win32] + '@tailwindcss/node@4.0.6': + resolution: {integrity: sha512-jb6E0WeSq7OQbVYcIJ6LxnZTeC4HjMvbzFBMCrQff4R50HBlo/obmYNk6V2GCUXDeqiXtvtrQgcIbT+/boB03Q==} + + '@tailwindcss/oxide-android-arm64@4.0.6': + resolution: {integrity: sha512-xDbym6bDPW3D2XqQqX3PjqW3CKGe1KXH7Fdkc60sX5ZLVUbzPkFeunQaoP+BuYlLc2cC1FoClrIRYnRzof9Sow==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.0.6': + resolution: {integrity: sha512-1f71/ju/tvyGl5c2bDkchZHy8p8EK/tDHCxlpYJ1hGNvsYihZNurxVpZ0DefpN7cNc9RTT8DjrRoV8xXZKKRjg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.0.6': + resolution: {integrity: sha512-s/hg/ZPgxFIrGMb0kqyeaqZt505P891buUkSezmrDY6lxv2ixIELAlOcUVTkVh245SeaeEiUVUPiUN37cwoL2g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.0.6': + resolution: {integrity: sha512-Z3Wo8FWZnmio8+xlcbb7JUo/hqRMSmhQw8IGIRoRJ7GmLR0C+25Wq+bEX/135xe/yEle2lFkhu9JBHd4wZYiig==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.6': + resolution: {integrity: sha512-SNSwkkim1myAgmnbHs4EjXsPL7rQbVGtjcok5EaIzkHkCAVK9QBQsWeP2Jm2/JJhq4wdx8tZB9Y7psMzHYWCkA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.0.6': + resolution: {integrity: sha512-tJ+mevtSDMQhKlwCCuhsFEFg058kBiSy4TkoeBG921EfrHKmexOaCyFKYhVXy4JtkaeeOcjJnCLasEeqml4i+Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.0.6': + resolution: {integrity: sha512-IoArz1vfuTR4rALXMUXI/GWWfx2EaO4gFNtBNkDNOYhlTD4NVEwE45nbBoojYiTulajI4c2XH8UmVEVJTOJKxA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.0.6': + resolution: {integrity: sha512-QtsUfLkEAeWAC3Owx9Kg+7JdzE+k9drPhwTAXbXugYB9RZUnEWWx5x3q/au6TvUYcL+n0RBqDEO2gucZRvRFgQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.0.6': + resolution: {integrity: sha512-QthvJqIji2KlGNwLcK/PPYo7w1Wsi/8NK0wAtRGbv4eOPdZHkQ9KUk+oCoP20oPO7i2a6X1aBAFQEL7i08nNMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-win32-arm64-msvc@4.0.6': + resolution: {integrity: sha512-+oka+dYX8jy9iP00DJ9Y100XsqvbqR5s0yfMZJuPR1H/lDVtDfsZiSix1UFBQ3X1HWxoEEl6iXNJHWd56TocVw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.0.6': + resolution: {integrity: sha512-+o+juAkik4p8Ue/0LiflQXPmVatl6Av3LEZXpBTfg4qkMIbZdhCGWFzHdt2NjoMiLOJCFDddoV6GYaimvK1Olw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.0.6': + resolution: {integrity: sha512-lVyKV2y58UE9CeKVcYykULe9QaE1dtKdxDEdrTPIdbzRgBk6bdxHNAoDqvcqXbIGXubn3VOl1O/CFF77v/EqSA==} + engines: {node: '>= 10'} + + '@tailwindcss/vite@4.0.6': + resolution: {integrity: sha512-O25vZ/URWbZ2JHdk2o8wH7jOKqEGCsYmX3GwGmYS5DjE4X3mpf93a72Rn7VRnefldNauBzr5z2hfZptmBNtTUQ==} + peerDependencies: + vite: ^5.2.0 || ^6 + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -613,6 +703,13 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + autoprefixer@10.4.20: + resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + babel-plugin-react-compiler@19.0.0-beta-30d8a17-20250209: resolution: {integrity: sha512-0pQHlz5nmBiEQ8ZWWVLeaBzz/FkToAdXEXBBnd21uSrDtIzhSe+s3VMvqMsv6vYHNTr+0KmsvVfEqXQp0W0kzg==} @@ -677,9 +774,18 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + electron-to-chromium@1.5.101: resolution: {integrity: sha512-L0ISiQrP/56Acgu4/i/kfPwWSgrzYZUnQrC0+QPFuhqlLP1Ir7qzPPDVS9BcKIyWTRU8+o6CC8dKw38tSWhYIA==} + enhanced-resolve@5.18.1: + resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} + engines: {node: '>=10.13.0'} + esbuild@0.24.2: resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} engines: {node: '>=18'} @@ -787,6 +893,9 @@ packages: flatted@3.3.2: resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -816,6 +925,9 @@ packages: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -856,6 +968,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -889,6 +1005,70 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lightningcss-darwin-arm64@1.29.1: + resolution: {integrity: sha512-HtR5XJ5A0lvCqYAoSv2QdZZyoHNttBpa5EP9aNuzBQeKGfbyH5+UipLWvVzpP4Uml5ej4BYs5I9Lco9u1fECqw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.29.1: + resolution: {integrity: sha512-k33G9IzKUpHy/J/3+9MCO4e+PzaFblsgBjSGlpAaFikeBFm8B/CkO3cKU9oI4g+fjS2KlkLM/Bza9K/aw8wsNA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.29.1: + resolution: {integrity: sha512-0SUW22fv/8kln2LnIdOCmSuXnxgxVC276W5KLTwoehiO0hxkacBxjHOL5EtHD8BAXg2BvuhsJPmVMasvby3LiQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.29.1: + resolution: {integrity: sha512-sD32pFvlR0kDlqsOZmYqH/68SqUMPNj+0pucGxToXZi4XZgZmqeX/NkxNKCPsswAXU3UeYgDSpGhu05eAufjDg==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.29.1: + resolution: {integrity: sha512-0+vClRIZ6mmJl/dxGuRsE197o1HDEeeRk6nzycSy2GofC2JsY4ifCRnvUWf/CUBQmlrvMzt6SMQNMSEu22csWQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.29.1: + resolution: {integrity: sha512-UKMFrG4rL/uHNgelBsDwJcBqVpzNJbzsKkbI3Ja5fg00sgQnHw/VrzUTEc4jhZ+AN2BvQYz/tkHu4vt1kLuJyw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.29.1: + resolution: {integrity: sha512-u1S+xdODy/eEtjADqirA774y3jLcm8RPtYztwReEXoZKdzgsHYPl0s5V52Tst+GKzqjebkULT86XMSxejzfISw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.29.1: + resolution: {integrity: sha512-L0Tx0DtaNUTzXv0lbGCLB/c/qEADanHbu4QdcNOXLIe1i8i22rZRpbT3gpWYsCh9aSL9zFujY/WmEXIatWvXbw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.29.1: + resolution: {integrity: sha512-QoOVnkIEFfbW4xPi+dpdft/zAKmgLgsRHfJalEPYuJDOWf7cLQzYg0DEh8/sn737FaeMJxHZRc1oBreiwZCjog==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.29.1: + resolution: {integrity: sha512-NygcbThNBe4JElP+olyTI/doBNGJvLs3bFCRPdvuCcxZCcCZ71B858IHpdm7L1btZex0FvCmM17FK98Y9MRy1Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.29.1: + resolution: {integrity: sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q==} + engines: {node: '>= 12.0.0'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -928,6 +1108,10 @@ packages: node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -959,6 +1143,9 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + postcss@8.5.2: resolution: {integrity: sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==} engines: {node: ^10 || ^12 || >=14} @@ -1035,6 +1222,13 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + tailwindcss@4.0.6: + resolution: {integrity: sha512-mysewHYJKaXgNOW6pp5xon/emCsfAMnO8WMaGKZZ35fomnR/T5gYnRg2/yRTTrtXiEl1tiVkeRt0eMO6HxEZqw==} + + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -1382,9 +1576,9 @@ snapshots: '@esbuild/win32-x64@0.24.2': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@9.20.1)': + '@eslint-community/eslint-utils@4.4.1(eslint@9.20.1(jiti@2.4.2))': dependencies: - eslint: 9.20.1 + eslint: 9.20.1(jiti@2.4.2) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} @@ -1527,6 +1721,67 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.34.7': optional: true + '@tailwindcss/node@4.0.6': + dependencies: + enhanced-resolve: 5.18.1 + jiti: 2.4.2 + tailwindcss: 4.0.6 + + '@tailwindcss/oxide-android-arm64@4.0.6': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.0.6': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.0.6': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.0.6': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.6': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.0.6': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.0.6': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.0.6': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.0.6': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.0.6': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.0.6': + optional: true + + '@tailwindcss/oxide@4.0.6': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.0.6 + '@tailwindcss/oxide-darwin-arm64': 4.0.6 + '@tailwindcss/oxide-darwin-x64': 4.0.6 + '@tailwindcss/oxide-freebsd-x64': 4.0.6 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.6 + '@tailwindcss/oxide-linux-arm64-gnu': 4.0.6 + '@tailwindcss/oxide-linux-arm64-musl': 4.0.6 + '@tailwindcss/oxide-linux-x64-gnu': 4.0.6 + '@tailwindcss/oxide-linux-x64-musl': 4.0.6 + '@tailwindcss/oxide-win32-arm64-msvc': 4.0.6 + '@tailwindcss/oxide-win32-x64-msvc': 4.0.6 + + '@tailwindcss/vite@4.0.6(vite@6.1.0(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1))': + dependencies: + '@tailwindcss/node': 4.0.6 + '@tailwindcss/oxide': 4.0.6 + lightningcss: 1.29.1 + tailwindcss: 4.0.6 + vite: 6.1.0(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1) + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.26.9 @@ -1564,15 +1819,15 @@ snapshots: dependencies: csstype: 3.1.3 - '@typescript-eslint/eslint-plugin@8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1)(typescript@5.7.3))(eslint@9.20.1)(typescript@5.7.3)': + '@typescript-eslint/eslint-plugin@8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.24.0(eslint@9.20.1)(typescript@5.7.3) + '@typescript-eslint/parser': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) '@typescript-eslint/scope-manager': 8.24.0 - '@typescript-eslint/type-utils': 8.24.0(eslint@9.20.1)(typescript@5.7.3) - '@typescript-eslint/utils': 8.24.0(eslint@9.20.1)(typescript@5.7.3) + '@typescript-eslint/type-utils': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/utils': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) '@typescript-eslint/visitor-keys': 8.24.0 - eslint: 9.20.1 + eslint: 9.20.1(jiti@2.4.2) graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 @@ -1581,14 +1836,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.24.0(eslint@9.20.1)(typescript@5.7.3)': + '@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': dependencies: '@typescript-eslint/scope-manager': 8.24.0 '@typescript-eslint/types': 8.24.0 '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3) '@typescript-eslint/visitor-keys': 8.24.0 debug: 4.4.0 - eslint: 9.20.1 + eslint: 9.20.1(jiti@2.4.2) typescript: 5.7.3 transitivePeerDependencies: - supports-color @@ -1598,12 +1853,12 @@ snapshots: '@typescript-eslint/types': 8.24.0 '@typescript-eslint/visitor-keys': 8.24.0 - '@typescript-eslint/type-utils@8.24.0(eslint@9.20.1)(typescript@5.7.3)': + '@typescript-eslint/type-utils@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': dependencies: '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3) - '@typescript-eslint/utils': 8.24.0(eslint@9.20.1)(typescript@5.7.3) + '@typescript-eslint/utils': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) debug: 4.4.0 - eslint: 9.20.1 + eslint: 9.20.1(jiti@2.4.2) ts-api-utils: 2.0.1(typescript@5.7.3) typescript: 5.7.3 transitivePeerDependencies: @@ -1625,13 +1880,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.24.0(eslint@9.20.1)(typescript@5.7.3)': + '@typescript-eslint/utils@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.1) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.1(jiti@2.4.2)) '@typescript-eslint/scope-manager': 8.24.0 '@typescript-eslint/types': 8.24.0 '@typescript-eslint/typescript-estree': 8.24.0(typescript@5.7.3) - eslint: 9.20.1 + eslint: 9.20.1(jiti@2.4.2) typescript: 5.7.3 transitivePeerDependencies: - supports-color @@ -1641,14 +1896,14 @@ snapshots: '@typescript-eslint/types': 8.24.0 eslint-visitor-keys: 4.2.0 - '@vitejs/plugin-react@4.3.4(vite@6.1.0(@types/node@22.13.4))': + '@vitejs/plugin-react@4.3.4(vite@6.1.0(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1))': dependencies: '@babel/core': 7.26.9 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.9) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.9) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 6.1.0(@types/node@22.13.4) + vite: 6.1.0(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1) transitivePeerDependencies: - supports-color @@ -1671,6 +1926,16 @@ snapshots: argparse@2.0.1: {} + autoprefixer@10.4.20(postcss@8.5.2): + dependencies: + browserslist: 4.24.4 + caniuse-lite: 1.0.30001699 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.5.2 + postcss-value-parser: 4.2.0 + babel-plugin-react-compiler@19.0.0-beta-30d8a17-20250209: dependencies: '@babel/types': 7.26.9 @@ -1730,8 +1995,15 @@ snapshots: deep-is@0.1.4: {} + detect-libc@1.0.3: {} + electron-to-chromium@1.5.101: {} + enhanced-resolve@5.18.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + esbuild@0.24.2: optionalDependencies: '@esbuild/aix-ppc64': 0.24.2 @@ -1764,25 +2036,25 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-react-compiler@19.0.0-beta-30d8a17-20250209(eslint@9.20.1): + eslint-plugin-react-compiler@19.0.0-beta-30d8a17-20250209(eslint@9.20.1(jiti@2.4.2)): dependencies: '@babel/core': 7.26.9 '@babel/parser': 7.26.9 '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.26.9) - eslint: 9.20.1 + eslint: 9.20.1(jiti@2.4.2) hermes-parser: 0.25.1 zod: 3.24.2 zod-validation-error: 3.4.0(zod@3.24.2) transitivePeerDependencies: - supports-color - eslint-plugin-react-hooks@5.1.0(eslint@9.20.1): + eslint-plugin-react-hooks@5.1.0(eslint@9.20.1(jiti@2.4.2)): dependencies: - eslint: 9.20.1 + eslint: 9.20.1(jiti@2.4.2) - eslint-plugin-react-refresh@0.4.19(eslint@9.20.1): + eslint-plugin-react-refresh@0.4.19(eslint@9.20.1(jiti@2.4.2)): dependencies: - eslint: 9.20.1 + eslint: 9.20.1(jiti@2.4.2) eslint-scope@8.2.0: dependencies: @@ -1793,9 +2065,9 @@ snapshots: eslint-visitor-keys@4.2.0: {} - eslint@9.20.1: + eslint@9.20.1(jiti@2.4.2): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.1) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.1(jiti@2.4.2)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.19.2 '@eslint/core': 0.11.0 @@ -1829,6 +2101,8 @@ snapshots: minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 + optionalDependencies: + jiti: 2.4.2 transitivePeerDependencies: - supports-color @@ -1888,6 +2162,8 @@ snapshots: flatted@3.3.2: {} + fraction.js@4.3.7: {} + fsevents@2.3.3: optional: true @@ -1907,6 +2183,8 @@ snapshots: globals@15.15.0: {} + graceful-fs@4.2.11: {} + graphemer@1.4.0: {} has-flag@4.0.0: {} @@ -1936,6 +2214,8 @@ snapshots: isexe@2.0.0: {} + jiti@2.4.2: {} + js-tokens@4.0.0: {} js-yaml@4.1.0: @@ -1961,6 +2241,51 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lightningcss-darwin-arm64@1.29.1: + optional: true + + lightningcss-darwin-x64@1.29.1: + optional: true + + lightningcss-freebsd-x64@1.29.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.29.1: + optional: true + + lightningcss-linux-arm64-gnu@1.29.1: + optional: true + + lightningcss-linux-arm64-musl@1.29.1: + optional: true + + lightningcss-linux-x64-gnu@1.29.1: + optional: true + + lightningcss-linux-x64-musl@1.29.1: + optional: true + + lightningcss-win32-arm64-msvc@1.29.1: + optional: true + + lightningcss-win32-x64-msvc@1.29.1: + optional: true + + lightningcss@1.29.1: + dependencies: + detect-libc: 1.0.3 + optionalDependencies: + lightningcss-darwin-arm64: 1.29.1 + lightningcss-darwin-x64: 1.29.1 + lightningcss-freebsd-x64: 1.29.1 + lightningcss-linux-arm-gnueabihf: 1.29.1 + lightningcss-linux-arm64-gnu: 1.29.1 + lightningcss-linux-arm64-musl: 1.29.1 + lightningcss-linux-x64-gnu: 1.29.1 + lightningcss-linux-x64-musl: 1.29.1 + lightningcss-win32-arm64-msvc: 1.29.1 + lightningcss-win32-x64-msvc: 1.29.1 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -1994,6 +2319,8 @@ snapshots: node-releases@2.0.19: {} + normalize-range@0.1.2: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -2023,6 +2350,8 @@ snapshots: picomatch@2.3.1: {} + postcss-value-parser@4.2.0: {} + postcss@8.5.2: dependencies: nanoid: 3.3.8 @@ -2097,6 +2426,10 @@ snapshots: dependencies: has-flag: 4.0.0 + tailwindcss@4.0.6: {} + + tapable@2.2.1: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -2109,12 +2442,12 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.24.0(eslint@9.20.1)(typescript@5.7.3): + typescript-eslint@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1)(typescript@5.7.3))(eslint@9.20.1)(typescript@5.7.3) - '@typescript-eslint/parser': 8.24.0(eslint@9.20.1)(typescript@5.7.3) - '@typescript-eslint/utils': 8.24.0(eslint@9.20.1)(typescript@5.7.3) - eslint: 9.20.1 + '@typescript-eslint/eslint-plugin': 8.24.0(@typescript-eslint/parser@8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/parser': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/utils': 8.24.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3) + eslint: 9.20.1(jiti@2.4.2) typescript: 5.7.3 transitivePeerDependencies: - supports-color @@ -2133,7 +2466,7 @@ snapshots: dependencies: punycode: 2.3.1 - vite@6.1.0(@types/node@22.13.4): + vite@6.1.0(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1): dependencies: esbuild: 0.24.2 postcss: 8.5.2 @@ -2141,6 +2474,8 @@ snapshots: optionalDependencies: '@types/node': 22.13.4 fsevents: 2.3.3 + jiti: 2.4.2 + lightningcss: 1.29.1 which@2.0.2: dependencies: diff --git a/src/App.css b/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#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; -} diff --git a/src/App.tsx b/src/App.tsx index 3d7ded3..d1955bb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,35 +1,75 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' - -function App() { - const [count, setCount] = useState(0) - - return ( - <> -
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

- - ) -} - -export default App +import DragAndDrop from './examples/EX8.RP4'; +import { useState } from 'react'; + +export default function App() { + const items = ['Item 1', 'Item 2', 'Item 3', 'Item 4']; + const [draggedItem, setDraggedItem] = useState(null); + + const dropZoneConfig = { + highlightOnHover: true, + maxItems: 3, // Allow up to 3 items to be dropped + }; + + return ( +
+ ( +
+
+

Draggable Items

+ {items.map((item) => ( +
{ + onDragStart(e, item); + setDraggedItem(item); + }} + style={{ + padding: '10px', + marginBottom: '10px', + backgroundColor: '#f9f9f9', + border: '1px solid #ddd', + cursor: 'move', + }} + > + {item} +
+ ))} +
+ +
{ + onDrop(e); + setDraggedItem(null); + }} + onDragOver={(e) => e.preventDefault()} + style={{ + width: '45%', + padding: '20px', + border: `2px solid ${dropZoneActive ? '#4CAF50' : '#ccc'}`, + backgroundColor: dropZoneActive ? '#eaf8e3' : '#f9f9f9', + transition: + 'background-color 0.3s ease, border-color 0.3s ease', + }} + > +

Drop Zone

+

{dropZoneActive ? 'Release to drop' : 'Drag items here'}

+
+ {dropZoneActive &&

Item being dragged: {draggedItem}

} +
+
+
+ )} + /> +
+ ); +} diff --git a/src/App_lecture1.tsx b/src/App_lecture1.tsx new file mode 100644 index 0000000..c59ef0a --- /dev/null +++ b/src/App_lecture1.tsx @@ -0,0 +1,70 @@ +import { MouseTracker } from './examples/EX5_RP1'; +import { DisableContextMenu } from './examples/EX6_RP2'; +import { ListRenderer } from './examples/EX7_RP3'; + +export default function App() { + return ( +
+
+ ( +
+

Mouse Tracker

+

X: {x}

+

Y: {y}

+
+ )} + /> + + ( +
+
+

X: {x}

+
+
+

Y: {y}

+
+
+ )} + /> +
+ +
+
+ ( +
+

Disable Context Menu

+

Right click on the div below to see the difference

+
+ )} + /> +
+ +
+
+
+ +
+
+ ( +
+ {item} +
+ )} + /> +
+
+
+ ); +} diff --git a/src/assets/react.svg b/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/demo/hoc/example.tsx b/src/demo/hoc/example.tsx new file mode 100644 index 0000000..5a2e021 --- /dev/null +++ b/src/demo/hoc/example.tsx @@ -0,0 +1,67 @@ +import { withDataFetch } from './withDataFetch'; + +type BaseResponse = { + data: unknown; + loading: boolean; + error: string | null; +}; + +type User = { + id: number; + name: string; + username: string; + email: string; +}; + +export const Users = ({ data, loading, error }: BaseResponse) => { + const users = data as User[]; + + return ( +
+ {loading &&

Loading...

} + {error &&

Error: {error}

} +

Users

+ {users && ( +
    + {users.slice(0, 5).map((user) => ( +
  • {user.name}
  • + ))} +
+ )} +
+ ); +}; + +export const UsersWithData = withDataFetch( + Users, + 'https://jsonplaceholder.typicode.com/users' +); + +type Post = { + id: number; + title: string; +}; + +const Posts = ({ data, loading, error }: BaseResponse) => { + const posts = data as Post[]; + + return ( +
+ {loading &&

Loading...

} + {error &&

Error: {error}

} +

Posts

+ {posts && ( +
    + {posts.slice(0, 5).map((post) => ( +
  • {post.title}
  • + ))} +
+ )} +
+ ); +}; + +export const PostsWithData = withDataFetch( + Posts, + 'https://jsonplaceholder.typicode.com/posts' +); diff --git a/src/demo/hoc/withDataFetch.tsx b/src/demo/hoc/withDataFetch.tsx new file mode 100644 index 0000000..7b546b6 --- /dev/null +++ b/src/demo/hoc/withDataFetch.tsx @@ -0,0 +1,48 @@ +import { ComponentType, useEffect, useState } from 'react'; + +export const withDataFetch = (Component: ComponentType, url: string) => { + return (props: any) => { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + const controller = new AbortController(); + setLoading(true); + + fetchData(url, controller.signal) + .then(({ data, error }) => { + if (error) { + setError(error); + return; + } + setData(data); + setError(null); + }) + .finally(() => setLoading(false)); + + return () => controller.abort(); + }, [url]); + + return ; + }; +}; + +const fetchData = async (url: string, signal: AbortSignal) => { + try { + const response = await fetch(url, { signal }); + + if (!response.ok) { + throw new Error('Failed to fetch data'); + } + + const result = await response.json(); + return { data: result, error: null }; + } catch (error) { + if (error instanceof Error) { + return { data: null, error: error.message }; + } else { + return { data: null, error: 'An unknown error occurred' }; + } + } +}; diff --git a/src/examples/EX1_SRP.tsx b/src/examples/EX1_SRP.tsx new file mode 100644 index 0000000..6002d51 --- /dev/null +++ b/src/examples/EX1_SRP.tsx @@ -0,0 +1,152 @@ +import { memo, useEffect, useMemo, useState } from 'react'; + +/** + * Bad Example: + * - The Dashboard component is responsible for displaying the user profile, notifications, and tasks. + * - This violates the Single Responsibility Principle (SRP) because the component is responsible for more than one thing. + */ + +export const DashboardBad = () => { + const [user, _setUser] = useState({ + name: 'John Doe', + email: 'john@example.com', + }); + const [notifications, _setNotifications] = useState([ + 'New message', + 'Server update', + ]); + const [tasks, _setTasks] = useState(['Finish report', 'Update project']); + + return ( +
+

Dashboard

+ + {/* User Profile */} +
+

User Profile

+

Name: {user.name}

+

Email: {user.email}

+
+ + {/* Notifications */} +
+

Notifications

+
    + {notifications.map((n, i) => ( +
  • {n}
  • + ))} +
+
+ + {/* Tasks */} +
+

Tasks

+
    + {tasks.map((t, i) => ( +
  • {t}
  • + ))} +
+
+
+ ); +}; + +/** + * Good Example + */ + +export const DashboardGood = () => { + const [notifications, _setNotifications] = useState([ + 'New message', + 'Server update', + ]); + const [tasks, _setTasks] = useState(['Finish report', 'Update project']); + + return ( +
+

Dashboard

+ {/* Memoize */} + {/* Memoize */} + {/* Memoize */} +
+ ); +}; + +type User = { + name: string; + email: string; +}; + +const UserProfile = () => { + const [user, _setUser] = useState({ + name: 'John Doe', + email: 'john@example.com', + }); + + useEffect(() => { + // DO API CALL + // Do Additional Logic + }, []); + + const computedUser = useMemo(() => { + // Do Additional Logic + return user; + }, [user]); + + const updateUser = (user: User) => { + // API CALL + // Do Additional Logic + _setUser(user); + }; + + return ; +}; + +const UserProfileContent = ({ + user, + onUpdate, +}: { + user: User; + onUpdate: (user: User) => void; +}) => { + return ( +
+

User Profile

+

Name: {user.name}

+

Email: {user.email}

+ +
+ ); +}; + +const Notifications = ({ notifications }: { notifications: string[] }) => { + return ( +
+

Notifications

+
    + {notifications.map((n, i) => ( +
  • {n}
  • + ))} +
+
+ ); +}; + +const Tasks = ({ tasks }: { tasks: string[] }) => { + return ( +
+

Tasks

+
    + {tasks.map((t, i) => ( +
  • {t}
  • + ))} +
+
+ ); +}; diff --git a/src/examples/EX2_SOC.tsx b/src/examples/EX2_SOC.tsx new file mode 100644 index 0000000..210d755 --- /dev/null +++ b/src/examples/EX2_SOC.tsx @@ -0,0 +1,56 @@ +import { useEffect, useState } from 'react'; + +export const CheckoutBad = () => { + const [_user, setUser] = useState(null); + const [cart, setCart] = useState([]); + const [_promo, setPromo] = useState(null); + const [loading, setLoading] = useState(false); + + useEffect(() => { + fetchUser(); + fetchCart(); + }, []); + + const fetchUser = async () => { + const response = await fetch('/api/user'); + const data = await response.json(); + setUser(data); + }; + + const fetchCart = async () => { + const response = await fetch('/api/cart'); + const data = await response.json(); + setCart(data); + }; + + const applyPromoCode = async (code: string) => { + const response = await fetch(`/api/promo?code=${code}`); + const data = await response.json(); + setPromo(data); + }; + + const handlePayment = async () => { + setLoading(true); + applyPromoCode('code'); + const response = await fetch('/api/pay', { + method: 'POST', + body: JSON.stringify({ cart }), + }); + const data = await response.json(); + if (data.success) { + alert('Payment Successful!'); + } else { + alert('Payment Failed!'); + } + setLoading(false); + }; + + return ( +
+

Checkout

+ +
+ ); +}; diff --git a/src/examples/EX3_ProjectDashboard.tsx b/src/examples/EX3_ProjectDashboard.tsx new file mode 100644 index 0000000..dc3f0ed --- /dev/null +++ b/src/examples/EX3_ProjectDashboard.tsx @@ -0,0 +1,271 @@ +import { useState, useEffect } from 'react'; + +// 🚨 This component does too many things! +const ProjectDashboard = ({ projectId }: { projectId: string }) => { + const [project, setProject] = useState<{ + name: string; + description: string; + deadline: string; + team: { id: number; name: string; avatar: string }[]; + comments: { id: number; text: string }[]; + } | null>(null); + + const [team, setTeam] = useState< + { id: number; name: string; avatar: string }[] + >([]); + + const [comments, setComments] = useState<{ id: number; text: string }[]>([]); + const [newComment, setNewComment] = useState(''); + const [status, setStatus] = useState('In Progress'); + + // Fetch Project Details + useEffect(() => { + fetch(`/api/projects/${projectId}`) + .then((res) => res.json()) + .then((data) => { + setProject(data); + setTeam(data.team); + }) + .catch(() => console.log('Error loading project')); + }, [projectId]); + + // Fetch Comments + useEffect(() => { + fetch(`/api/projects/${projectId}/comments`) + .then((res) => res.json()) + .then((data) => setComments(data)) + .catch(() => console.log('Error loading comments')); + }, [projectId]); + + // Update Status + const updateStatus = (newStatus: string) => { + setStatus(newStatus); + console.log(`Project status updated to: ${newStatus}`); + }; + + // Handle Adding New Comment + const addComment = () => { + if (newComment.trim() === '') return; + setComments([...comments, { id: Date.now(), text: newComment }]); + setNewComment(''); + }; + + return ( +
+ {/* Project Info */} + {project ? ( +
+

{project.name}

+

{project.description}

+

Deadline: {new Date(project.deadline).toDateString()}

+

Status: {status}

+ +
+ ) : ( +

Loading...

+ )} + + {/* Team Members */} +

Team Members

+
    + {team.map((member) => ( +
  • + {member.name}{' '} + {member.name} +
  • + ))} +
+ + {/* Comments Section */} +

Comments

+
    + {comments.map((comment) => ( +
  • {comment.text}
  • + ))} +
+ setNewComment(e.target.value)} + /> + +
+ ); +}; + +export default ProjectDashboard; + +/** +❌ Violates Separation of Concerns (SoC) → Handles data fetching, UI rendering, business logic, and state management all in one place. +❌ Breaks Single Responsibility Principle (SRP) → Should a single component really be responsible for project details, team members, status updates, and comments? +❌ Tightly Coupled & Hard to Maintain → If you want to change how the comments work, you'll have to touch unrelated parts of the code. +❌ Difficult to Test & Reuse → You can't reuse the team member list or comments elsewhere without copying logic. +*/ + +/** + * ✅ Good Example + */ + +type Team = { + id: number; + name: string; + avatar: string; +}; + +type Comment = { + id: number; + text: string; +}; + +type Project = { + name: string; + description: string; + deadline: string; + team: Team[]; + comments: Comment[]; +}; + +export const ProjectDashboardGood = ({ projectId }: { projectId: string }) => { + // This is a container component + // So that, we can have states and logic for this component + + const [project, setProject] = useState(null); + const [team, setTeam] = useState([]); + const [comments, setComments] = useState([]); + const [status, setStatus] = useState('In Progress'); + + // Fetch Project Details + useEffect(() => { + fetchProject(projectId).then((project: Project) => { + setProject(project); + setTeam(project.team); + }); + }, [projectId]); + + // Fetch Comments + useEffect(() => { + fetchComments(projectId).then((comments: Comment[]) => { + setComments(comments); + }); + }, [projectId]); + + // Update Status + const updateStatus = (newStatus: string) => { + setStatus(newStatus); + mutateStatus(projectId, newStatus); + }; + + return ( +
+ {project ? ( + <> + + + + + + ) : ( +

Loading...

+ )} +
+ ); +}; + +const ProjectInfo = ({ + project, + status, +}: { + project: Project; + status: string; +}) => ( +
+

{project.name}

+

{project.description}

+

Deadline: {new Date(project.deadline).toDateString()}

+

Status: {status}

+
+); + +const StatusUpdater = ({ + onUpdate, +}: { + status: string; + onUpdate: (newStatus: string) => void; +}) => ( +
+ +
+); + +const TeamList = ({ team }: { team: Team[] }) => ( +
+

Team Members

+
    + {team.map((member) => ( +
  • + {member.name} {member.name} +
  • + ))} +
+
+); + +const CommentSection = ({ + comments, + setComments, +}: { + comments: Comment[]; + setComments: (comments: Comment[]) => void; +}) => { + const [newComment, setNewComment] = useState(''); + + const addComment = () => { + if (newComment.trim() === '') return; + setComments([...comments, { id: Date.now(), text: newComment }]); + setNewComment(''); + }; + + return ( +
+

Comments

+
    + {/** New Component */} + {comments.map((comment) => ( +
  • {comment.text}
  • + ))} +
+ + {/* New Component */} + setNewComment(e.target.value)} + /> + +
+ ); +}; + +/** + * Utility functions + */ + +const fetchProject = async (projectId: string) => { + const res = await fetch(`/api/projects/${projectId}`); + return res.json(); +}; + +const fetchComments = async (projectId: string) => { + const res = await fetch(`/api/projects/${projectId}/comments`); + return res.json(); +}; + +const mutateStatus = async (projectId: string, newStatus: string) => { + await fetch(`/api/projects/${projectId}/status`, { + method: 'PUT', + body: JSON.stringify({ status: newStatus }), + }); +}; diff --git a/src/examples/EX4_HOC.tsx b/src/examples/EX4_HOC.tsx new file mode 100644 index 0000000..ccae9b6 --- /dev/null +++ b/src/examples/EX4_HOC.tsx @@ -0,0 +1,45 @@ +import { ComponentType, useEffect, useState } from 'react'; + +export const withDataFetch = (Component: ComponentType, url: string) => { + return (props: any) => { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const controller = new AbortController(); + const signal = controller.signal; + + const fetchData = async () => { + try { + const response = await fetch(url, { signal }); + if (!response.ok) { + throw new Error('Failed to fetch data'); + } + const result = await response.json(); + setData(result); + } catch (err) { + if (err instanceof Error && err.name === 'AbortError') { + return; + } + setError( + err instanceof Error ? err.message : 'An unknown error occurred' + ); + } finally { + setLoading(false); + } + }; + + fetchData(); + + return () => { + controller.abort(); + }; + }, [url]); + + if (loading) return

Loading...

; + if (error) return

Error: {error}

; + + return ; + }; +}; diff --git a/src/examples/EX5_RP1.tsx b/src/examples/EX5_RP1.tsx new file mode 100644 index 0000000..9c3c0a6 --- /dev/null +++ b/src/examples/EX5_RP1.tsx @@ -0,0 +1,21 @@ +import { useEffect, useState } from 'react'; + +type Props = { + render: (x: number, y: number) => React.ReactNode; +}; + +export const MouseTracker = ({ render }: Props) => { + const [position, setPosition] = useState({ x: 0, y: 0 }); + + useEffect(() => { + const handleMouseMove = (event: MouseEvent) => { + setPosition({ x: event.clientX, y: event.clientY }); + }; + + window.addEventListener('mousemove', handleMouseMove); + + return () => window.removeEventListener('mousemove', handleMouseMove); + }, []); + + return render(position.x, position.y); +}; diff --git a/src/examples/EX6_RP2.tsx b/src/examples/EX6_RP2.tsx new file mode 100644 index 0000000..27b818d --- /dev/null +++ b/src/examples/EX6_RP2.tsx @@ -0,0 +1,34 @@ +import { JSX, useEffect, useRef } from 'react'; + +export const DisableContextMenu = ({ + render, + className = '', +}: { + render: () => JSX.Element; + className?: string; +}) => { + const ref = useRef(null); + + useEffect(() => { + const handleContextMenu = (event: MouseEvent) => { + event.preventDefault(); // Disable right-click context menu + }; + + // Only attach event listener to the referenced div element + const element = ref.current; + if (element) { + element.addEventListener('contextmenu', handleContextMenu); + + // Cleanup the event listener on unmount + return () => { + element.removeEventListener('contextmenu', handleContextMenu); + }; + } + }, []); + + return ( +
+ {render()} +
+ ); +}; diff --git a/src/examples/EX7_RP3.tsx b/src/examples/EX7_RP3.tsx new file mode 100644 index 0000000..13f45be --- /dev/null +++ b/src/examples/EX7_RP3.tsx @@ -0,0 +1,32 @@ +import { JSX } from 'react'; + +type Props = { + items: string[]; + render?: (item: string, index: number, total: number) => JSX.Element; +}; + +export const ListRenderer = ({ items, render }: Props) => { + if (!render && items.length === 0) { + return

No items to display

; + } + + if (!render) { + return ( +
    + {items.map((item) => ( +
  • + {item} +
  • + ))} +
+ ); + } + + return ( +
    + {items.map((item, index) => ( +
  • {render(item, index, items.length)}
  • + ))} +
+ ); +}; diff --git a/src/examples/EX8.RP4.tsx b/src/examples/EX8.RP4.tsx new file mode 100644 index 0000000..37d11a0 --- /dev/null +++ b/src/examples/EX8.RP4.tsx @@ -0,0 +1,51 @@ +import { JSX, useState } from 'react'; + +type DragAndDropProps = { + items: string[]; + dropZoneConfig: { + highlightOnHover?: boolean; + maxItems?: number; // Limit the number of items that can be dropped + }; + render: (params: { + items: string[]; + dropZoneActive: boolean; + onDragStart: (e: React.DragEvent, item: string) => void; + onDrop: (e: React.DragEvent) => void; + }) => JSX.Element; +}; + +const DragAndDrop: React.FC = ({ + items, + dropZoneConfig, + render, +}) => { + const [draggedItem, setDraggedItem] = useState(null); + const [droppedItems, setDroppedItems] = useState([]); + + const handleDragStart = (_e: React.DragEvent, item: string) => { + setDraggedItem(item); + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + if ( + draggedItem && + !droppedItems.includes(draggedItem) && + droppedItems.length < dropZoneConfig.maxItems! + ) { + setDroppedItems((prev) => [...prev, draggedItem]); + setDraggedItem(null); + } + }; + + const dropZoneActive = draggedItem !== null; + + return render({ + items, + dropZoneActive, + onDragStart: handleDragStart, + onDrop: handleDrop, + }); +}; + +export default DragAndDrop; diff --git a/src/index.css b/src/index.css index 6119ad9..14325be 100644 --- a/src/index.css +++ b/src/index.css @@ -1,68 +1,10 @@ -: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: center; - 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.6em 1.2em; - 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; - } -} +@import 'tailwindcss'; + +@theme { + --default-font-family: 'Poppins', sans-serif; + --default-font-size: 16px; + --default-font-weight: 400; + --default-line-height: 1.5; + --default-letter-spacing: 0.01em; + --default-text-color: #333; +} \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx index bef5202..bb7941a 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,7 +1,7 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' -import App from './App.tsx' +import App from './App.tsx'; createRoot(document.getElementById('root')!).render( diff --git a/vite.config.ts b/vite.config.ts index 01b2051..3a6a3ea 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import path from 'path'; +import tailwindcss from '@tailwindcss/vite'; // https://vite.dev/config/ export default defineConfig({ @@ -10,6 +11,7 @@ export default defineConfig({ plugins: ['babel-plugin-react-compiler'], }, }), + tailwindcss(), ], resolve: { alias: { From 058525515be4694702556a82f76d11ea920c831a Mon Sep 17 00:00:00 2001 From: HM Nayem Date: Sun, 16 Feb 2025 03:05:41 +0600 Subject: [PATCH 2/2] chore: moved examples to the lecture 1 directory --- src/App.tsx | 2 +- src/App_lecture1.tsx | 6 +++--- src/examples/{ => lecture_1}/EX1_SRP.tsx | 0 src/examples/{ => lecture_1}/EX2_SOC.tsx | 0 src/examples/{ => lecture_1}/EX3_ProjectDashboard.tsx | 0 src/examples/{ => lecture_1}/EX4_HOC.tsx | 0 src/examples/{ => lecture_1}/EX5_RP1.tsx | 0 src/examples/{ => lecture_1}/EX6_RP2.tsx | 0 src/examples/{ => lecture_1}/EX7_RP3.tsx | 0 src/examples/{EX8.RP4.tsx => lecture_1/EX8_RP4.tsx} | 0 10 files changed, 4 insertions(+), 4 deletions(-) rename src/examples/{ => lecture_1}/EX1_SRP.tsx (100%) rename src/examples/{ => lecture_1}/EX2_SOC.tsx (100%) rename src/examples/{ => lecture_1}/EX3_ProjectDashboard.tsx (100%) rename src/examples/{ => lecture_1}/EX4_HOC.tsx (100%) rename src/examples/{ => lecture_1}/EX5_RP1.tsx (100%) rename src/examples/{ => lecture_1}/EX6_RP2.tsx (100%) rename src/examples/{ => lecture_1}/EX7_RP3.tsx (100%) rename src/examples/{EX8.RP4.tsx => lecture_1/EX8_RP4.tsx} (100%) diff --git a/src/App.tsx b/src/App.tsx index d1955bb..a73c951 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import DragAndDrop from './examples/EX8.RP4'; +import DragAndDrop from './examples/lecture_1/EX8_RP4'; import { useState } from 'react'; export default function App() { diff --git a/src/App_lecture1.tsx b/src/App_lecture1.tsx index c59ef0a..e04cc32 100644 --- a/src/App_lecture1.tsx +++ b/src/App_lecture1.tsx @@ -1,6 +1,6 @@ -import { MouseTracker } from './examples/EX5_RP1'; -import { DisableContextMenu } from './examples/EX6_RP2'; -import { ListRenderer } from './examples/EX7_RP3'; +import { MouseTracker } from './examples/lecture_1/EX5_RP1'; +import { DisableContextMenu } from './examples/lecture_1/EX6_RP2'; +import { ListRenderer } from './examples/lecture_1/EX7_RP3'; export default function App() { return ( diff --git a/src/examples/EX1_SRP.tsx b/src/examples/lecture_1/EX1_SRP.tsx similarity index 100% rename from src/examples/EX1_SRP.tsx rename to src/examples/lecture_1/EX1_SRP.tsx diff --git a/src/examples/EX2_SOC.tsx b/src/examples/lecture_1/EX2_SOC.tsx similarity index 100% rename from src/examples/EX2_SOC.tsx rename to src/examples/lecture_1/EX2_SOC.tsx diff --git a/src/examples/EX3_ProjectDashboard.tsx b/src/examples/lecture_1/EX3_ProjectDashboard.tsx similarity index 100% rename from src/examples/EX3_ProjectDashboard.tsx rename to src/examples/lecture_1/EX3_ProjectDashboard.tsx diff --git a/src/examples/EX4_HOC.tsx b/src/examples/lecture_1/EX4_HOC.tsx similarity index 100% rename from src/examples/EX4_HOC.tsx rename to src/examples/lecture_1/EX4_HOC.tsx diff --git a/src/examples/EX5_RP1.tsx b/src/examples/lecture_1/EX5_RP1.tsx similarity index 100% rename from src/examples/EX5_RP1.tsx rename to src/examples/lecture_1/EX5_RP1.tsx diff --git a/src/examples/EX6_RP2.tsx b/src/examples/lecture_1/EX6_RP2.tsx similarity index 100% rename from src/examples/EX6_RP2.tsx rename to src/examples/lecture_1/EX6_RP2.tsx diff --git a/src/examples/EX7_RP3.tsx b/src/examples/lecture_1/EX7_RP3.tsx similarity index 100% rename from src/examples/EX7_RP3.tsx rename to src/examples/lecture_1/EX7_RP3.tsx diff --git a/src/examples/EX8.RP4.tsx b/src/examples/lecture_1/EX8_RP4.tsx similarity index 100% rename from src/examples/EX8.RP4.tsx rename to src/examples/lecture_1/EX8_RP4.tsx