├── .gitignore ├── README.md ├── app ├── favicon.ico ├── globals.css ├── layout.tsx ├── opengraph-image.png └── page.tsx ├── package.json ├── pnpm-lock.yaml ├── postcss.config.mjs ├── public └── course.tsx └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # env files (can opt-in for commiting if needed) 33 | .env* 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vim for React Developers 2 | 3 | by [leerob](https://leerob.com) 4 | 5 | This mini-course is designed to help **React developers** learn **Vim** commands and apply them to real-world scenarios by fixing and editing React code. The course focuses on the most helpful Vim commands when working with React and JavaScript. 6 | 7 | **https://vimforreactdevs.com** 8 | 9 | ## Prerequisites 10 | 11 | Before starting this course, you should have neovim installed and configured: 12 | 13 | 1. Install Neovim and dependencies 14 | - macOS: `brew install nvim fzf ripgrep` 15 | - Windows: `choco install -y neovim git ripgrep wget fd unzip gzip mingw make` 16 | 2. Follow [this Neovim quickstart](https://github.com/nvim-lua/kickstart.nvim) 17 | 18 | That's it! Download the [course file](./public/course.tsx) and start neovim with `nvim course.tsx`. 19 | 20 | ## Course Content 21 | 22 | View the completed [course file](./public/course.tsx). 23 | 24 | ## Cheat Sheet 25 | 26 | ### Modes 27 | 28 | - Normal Mode: Default mode for navigation and commands (`Esc`) 29 | - Insert Mode: `i`, `I`, `a`, `A` (exit with `Esc`) 30 | - Visual Mode: `v` (character), `V` (line), `Ctrl+v` (block) 31 | - Command Mode: `:` 32 | 33 | ### Navigation 34 | 35 | - Basic: `h` (left), `j` (down), `k` (up), `l` (right) 36 | - Words: `w` (next word), `b` (previous word), `e` (end of word) 37 | - Lines: `0` (start), `^` (first non-whitespace), `$` (end) 38 | - Scrolling: `Ctrl+d` (down), `Ctrl+u` (up) 39 | - File: `gg` (top), `G` (bottom) 40 | 41 | ### Actions 42 | 43 | - `d`: Delete 44 | - `c`: Change (delete and enter Insert Mode) 45 | - `y`: Yank (copy) 46 | - `v`: Visual selection 47 | 48 | ### Motions 49 | 50 | - `i`: Inside 51 | - `a`: Around 52 | 53 | ### Objects 54 | 55 | - `w`: Word 56 | - `p`: Paragraph 57 | - `t`: Tag 58 | - `q`: Quote (or `'`, `"`, ```) 59 | - `b`: Bracket (or `(`, `{`, `[`, `<`) 60 | 61 | ### Examples 62 | 63 | - `dw`: Delete Word 64 | - `cw`: Change Word 65 | - `yy`: Copy Entire Line 66 | - `y$`: Copy To End Of Line 67 | - `diq`: Delete Inside Quotes 68 | - `ca{`: Change Around Braces 69 | - `ci(`: Change Inside Parenthesis 70 | - `yap`: Yank Paragraph 71 | - `dab`: Delete Around Brackets 72 | - `yiw`: Yank Inside Word 73 | - `ciw`: Change Inside Word 74 | - `vit`: Visual Selection Inside Tag 75 | - `ct"`: Change To Next `"` (leaving the `"`) 76 | - `df|`: Delete to Next `|` (including the `|`) 77 | 78 | ### Miscellaneous 79 | 80 | - `.`: Repeat Last Command 81 | - `gd`: Go To Definition 82 | - `f`: Find Character 83 | - `t`: To Character 84 | - `ZZ`: Save & Close Vim 85 | - `u`: Undo 86 | - `Ctrl+r`: Redo 87 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leerob/vim-for-react-devs/f6458adff2d0b03c6cfb0637ea2c74b2109c63ef/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | 3 | @theme { 4 | --font-family-mono: 'Fira Code', monospace; 5 | } 6 | 7 | html { 8 | min-width: 360px; 9 | } 10 | 11 | body { 12 | text-rendering: optimizeLegibility; 13 | } 14 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css'; 2 | import type { Metadata } from 'next'; 3 | import { Fira_Code } from 'next/font/google'; 4 | 5 | const firaCode = Fira_Code({ subsets: ['latin'] }); 6 | 7 | export const metadata: Metadata = { 8 | title: 'Vim for React Developers', 9 | description: 'A bite-sized course to get you quickly productive with Vim.', 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | 22 | {children} 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /app/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leerob/vim-for-react-devs/f6458adff2d0b03c6cfb0637ea2c74b2109c63ef/app/opengraph-image.png -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | const totalLines = 96; 3 | 4 | const topics = [ 5 | 'How to quickly navigate through code with Vim', 6 | 'Examples for how to refactor React patterns', 7 | 'Searching, finding, and replacing text', 8 | 'Time-saving vim motions to memorize', 9 | 'Recommended patterns and neovim configuration', 10 | ]; 11 | 12 | const benefits = [ 13 | 'Created specifically for React developers', 14 | 'Focused on practical, real-world scenarios', 15 | 'Learn to navigate and edit code faster', 16 | 'Includes a comprehensive cheat sheet', 17 | ]; 18 | 19 | return ( 20 |
21 |
22 | {Array.from({ length: totalLines }, (_, i) => ( 23 |
27 | {i + 1} 28 |
29 | ))} 30 |
31 |
32 |

33 | Vim for React Developers 34 |

35 | 36 |

37 | Vim has been around for over 30 years, and vi (which Vim extends) 38 | almost 50 years. 39 |

40 | 41 |

42 | So why does it feel like everyone is talking about Vim right now? 43 | There’s been a Vim renaissance lately from the popularity of Neovim 44 | and its growing community. 45 |

46 | 47 |

48 | Yet, somehow, I had never tried out Vim (or Neovim). I had been using 49 | VS Code for the past six years, and honestly, hadn’t spent much time 50 | exploring other options. 51 |

52 | 53 |

54 | I’ve used Notepad, Eclipse, Android Studio, XCode, Sublime Text, and 55 | finally VS Code over the years. VS Code felt like the best place to do 56 | web development with its git integration and ecosystem of extensions. 57 | But it’s started to feel bulky and slow (to me at least). 58 |

59 | 60 |

61 | The web community has largely chosen VS Code as the go-to IDE. 62 | However, the landscape is changing fast. There’s new tools, like Zed 63 | and Cursor, that are providing AI-enhanced editors. Yet, I haven’t 64 | seen as many web developers considering Neovim as another contender. 65 |

66 | 67 |

68 | I’ve spent the past month learning Vim motions, making Neovim my 69 | default editor, and I believe it’s one of the better options for web 70 | developers right now. Everything I love about VS Code I’m able to get 71 | with Neovim, including fantastic options for git, time-saving 72 | shortcuts for common web dev actions, and even AI tools. 73 |

74 | 75 |

76 | As I learned Vim motions, I applied them to a React codebase to see 77 | what patterns would work well. This mini course takes those patterns 78 | and tips and is designed to get you up to speed on the Vim motions you 79 | need to know. I’ve also included a cheat sheet of the most common 80 | commands needed (and how to think about them as a vocabulary, making 81 | them easier to utilize). 82 |

83 | 84 |

85 | This free course will not teach you every possible Vim motion. 86 | Instead, it will focus on the commands I've found to be most helpful 87 | when working with React and JavaScript. However, they apply more 88 | generally outside React. Are you ready? 89 |

90 | 91 |
92 |

93 | What You'll Learn 94 |

95 |
    96 | {topics.map((topic, index) => ( 97 |
  • {topic}
  • 98 | ))} 99 |
100 |
101 | 102 |
103 |

Course Benefits

104 |
    105 | {benefits.map((benefit, index) => ( 106 |
  • {benefit}
  • 107 | ))} 108 |
109 |
110 | 111 |
112 | 117 | Start Course 118 | 119 |
120 |
121 |
122 | ); 123 | } 124 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "next dev --turbo", 5 | "build": "next build", 6 | "start": "next start" 7 | }, 8 | "dependencies": { 9 | "@tailwindcss/postcss": "4.0.0-alpha.24", 10 | "@types/node": "^20", 11 | "@types/react": "^18", 12 | "@types/react-dom": "^18", 13 | "next": "15.0.0-canary.161", 14 | "react": "19.0.0-rc-e740d4b1-20240919", 15 | "react-dom": "19.0.0-rc-e740d4b1-20240919", 16 | "postcss": "^8", 17 | "tailwindcss": "4.0.0-alpha.24", 18 | "typescript": "^5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | '@tailwindcss/postcss': 12 | specifier: 4.0.0-alpha.24 13 | version: 4.0.0-alpha.24(postcss@8.4.47) 14 | '@types/node': 15 | specifier: ^20 16 | version: 20.16.5 17 | '@types/react': 18 | specifier: ^18 19 | version: 18.3.8 20 | '@types/react-dom': 21 | specifier: ^18 22 | version: 18.3.0 23 | next: 24 | specifier: 15.0.0-canary.161 25 | version: 15.0.0-canary.161(react-dom@19.0.0-rc-e740d4b1-20240919(react@19.0.0-rc-e740d4b1-20240919))(react@19.0.0-rc-e740d4b1-20240919) 26 | postcss: 27 | specifier: ^8 28 | version: 8.4.47 29 | react: 30 | specifier: 19.0.0-rc-e740d4b1-20240919 31 | version: 19.0.0-rc-e740d4b1-20240919 32 | react-dom: 33 | specifier: 19.0.0-rc-e740d4b1-20240919 34 | version: 19.0.0-rc-e740d4b1-20240919(react@19.0.0-rc-e740d4b1-20240919) 35 | tailwindcss: 36 | specifier: 4.0.0-alpha.24 37 | version: 4.0.0-alpha.24 38 | typescript: 39 | specifier: ^5 40 | version: 5.6.2 41 | 42 | packages: 43 | 44 | '@emnapi/runtime@1.2.0': 45 | resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==} 46 | 47 | '@img/sharp-darwin-arm64@0.33.5': 48 | resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} 49 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 50 | cpu: [arm64] 51 | os: [darwin] 52 | 53 | '@img/sharp-darwin-x64@0.33.5': 54 | resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} 55 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 56 | cpu: [x64] 57 | os: [darwin] 58 | 59 | '@img/sharp-libvips-darwin-arm64@1.0.4': 60 | resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} 61 | cpu: [arm64] 62 | os: [darwin] 63 | 64 | '@img/sharp-libvips-darwin-x64@1.0.4': 65 | resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} 66 | cpu: [x64] 67 | os: [darwin] 68 | 69 | '@img/sharp-libvips-linux-arm64@1.0.4': 70 | resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} 71 | cpu: [arm64] 72 | os: [linux] 73 | 74 | '@img/sharp-libvips-linux-arm@1.0.5': 75 | resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} 76 | cpu: [arm] 77 | os: [linux] 78 | 79 | '@img/sharp-libvips-linux-s390x@1.0.4': 80 | resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} 81 | cpu: [s390x] 82 | os: [linux] 83 | 84 | '@img/sharp-libvips-linux-x64@1.0.4': 85 | resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} 86 | cpu: [x64] 87 | os: [linux] 88 | 89 | '@img/sharp-libvips-linuxmusl-arm64@1.0.4': 90 | resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} 91 | cpu: [arm64] 92 | os: [linux] 93 | 94 | '@img/sharp-libvips-linuxmusl-x64@1.0.4': 95 | resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} 96 | cpu: [x64] 97 | os: [linux] 98 | 99 | '@img/sharp-linux-arm64@0.33.5': 100 | resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} 101 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 102 | cpu: [arm64] 103 | os: [linux] 104 | 105 | '@img/sharp-linux-arm@0.33.5': 106 | resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} 107 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 108 | cpu: [arm] 109 | os: [linux] 110 | 111 | '@img/sharp-linux-s390x@0.33.5': 112 | resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} 113 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 114 | cpu: [s390x] 115 | os: [linux] 116 | 117 | '@img/sharp-linux-x64@0.33.5': 118 | resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} 119 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 120 | cpu: [x64] 121 | os: [linux] 122 | 123 | '@img/sharp-linuxmusl-arm64@0.33.5': 124 | resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} 125 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 126 | cpu: [arm64] 127 | os: [linux] 128 | 129 | '@img/sharp-linuxmusl-x64@0.33.5': 130 | resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} 131 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 132 | cpu: [x64] 133 | os: [linux] 134 | 135 | '@img/sharp-wasm32@0.33.5': 136 | resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} 137 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 138 | cpu: [wasm32] 139 | 140 | '@img/sharp-win32-ia32@0.33.5': 141 | resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} 142 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 143 | cpu: [ia32] 144 | os: [win32] 145 | 146 | '@img/sharp-win32-x64@0.33.5': 147 | resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} 148 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 149 | cpu: [x64] 150 | os: [win32] 151 | 152 | '@next/env@15.0.0-canary.161': 153 | resolution: {integrity: sha512-MIcaDiNnHQphiuyFi4wQ3veXGFPDqzRg2JCXqHbZ6atZNZLLR65quuuPNQ9a7dynElskaH2XIyRM1WBtPoge4w==} 154 | 155 | '@next/swc-darwin-arm64@15.0.0-canary.161': 156 | resolution: {integrity: sha512-+NqqjUIlKS+6JZPycro5kWUj+4jZJnZQOFRjIubtksIpYXgI4vLI54Ainl3zeVn3a0Kg6l0d+4/hZ5kmDS4O4Q==} 157 | engines: {node: '>= 10'} 158 | cpu: [arm64] 159 | os: [darwin] 160 | 161 | '@next/swc-darwin-x64@15.0.0-canary.161': 162 | resolution: {integrity: sha512-oa2EZ5ceSVyMtBpeIdrw7uL2Rs92KgsZitmFZtsLvtvMPK7LEgWu6ZGEF09mQa0lOYXQLu/U6TZvVWNhYbQ2Tw==} 163 | engines: {node: '>= 10'} 164 | cpu: [x64] 165 | os: [darwin] 166 | 167 | '@next/swc-linux-arm64-gnu@15.0.0-canary.161': 168 | resolution: {integrity: sha512-TpceUcQ3mVLPnoE4eDI/PbbwrKzFyDxZMeusb10GLicxzo+w6CCGxxkL43q8lKgdf78q1jbdWBFlm1T5C0d7NQ==} 169 | engines: {node: '>= 10'} 170 | cpu: [arm64] 171 | os: [linux] 172 | 173 | '@next/swc-linux-arm64-musl@15.0.0-canary.161': 174 | resolution: {integrity: sha512-A1MW6/+vaz0BfNQ6M/ptrf+RRWCb9HaItcbTeRYYAmXyaRpVNmUjbnZ8ny93VueCiWU7OR1GgFhvuPik2xR/uQ==} 175 | engines: {node: '>= 10'} 176 | cpu: [arm64] 177 | os: [linux] 178 | 179 | '@next/swc-linux-x64-gnu@15.0.0-canary.161': 180 | resolution: {integrity: sha512-hOdh0KePlUf5bpdjzY6wCLfoB1tP1Czvjm6WlNakaTR5nSzgjY7XUSWs6UEj36kEHehwsam6wLHxNCgsTdCWdg==} 181 | engines: {node: '>= 10'} 182 | cpu: [x64] 183 | os: [linux] 184 | 185 | '@next/swc-linux-x64-musl@15.0.0-canary.161': 186 | resolution: {integrity: sha512-QugyWvDnB1dO8S5WpyXLtHoBSEV+T1kxo2A7rA8T0ZJD2SS8Uz6mti+e+OOSirHjLso05kaZWXPkzCknTYHsBw==} 187 | engines: {node: '>= 10'} 188 | cpu: [x64] 189 | os: [linux] 190 | 191 | '@next/swc-win32-arm64-msvc@15.0.0-canary.161': 192 | resolution: {integrity: sha512-myiUeg7OmSkYPfFV6jf0ce3zFGhQhsUB4W+LKdq1nWf5x4yYvISzx4EX0ZCVKEIWo68VqBO9ekjc+q0XDzLeeg==} 193 | engines: {node: '>= 10'} 194 | cpu: [arm64] 195 | os: [win32] 196 | 197 | '@next/swc-win32-ia32-msvc@15.0.0-canary.161': 198 | resolution: {integrity: sha512-NUbW/H5r3vRhRFCZPTcPK1nV3s9VfEAgHr2xNJp5TjMheJ7m9VsuZXodF4ayATu9nlsjCJI2v0TUtJjxRqs0FA==} 199 | engines: {node: '>= 10'} 200 | cpu: [ia32] 201 | os: [win32] 202 | 203 | '@next/swc-win32-x64-msvc@15.0.0-canary.161': 204 | resolution: {integrity: sha512-JftkCF/9A0OQnzauRItlACXVzu2gKyS1Oi5FnXXv5pjjc1D4ak5dZqrhmjrwVfZYXerWs1cVNSfBu0tocKF3PA==} 205 | engines: {node: '>= 10'} 206 | cpu: [x64] 207 | os: [win32] 208 | 209 | '@swc/counter@0.1.3': 210 | resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} 211 | 212 | '@swc/helpers@0.5.13': 213 | resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==} 214 | 215 | '@tailwindcss/node@4.0.0-alpha.24': 216 | resolution: {integrity: sha512-oDQRe/FtkqPCviVdyp0LEdu6sHj5iIQygjlmFpKmZe17BKOTymEwEafomZvf/sZNhhsFgXoEU7qHc9ABxSucJw==} 217 | 218 | '@tailwindcss/oxide-android-arm64@4.0.0-alpha.24': 219 | resolution: {integrity: sha512-oMsnLS+soTPaKqIf02y7Bz/RH0XYujeTszE1BA4UgIwXtrYlprShyHDNECC5UaIxFTanwQ910bH8VwceK88/Lg==} 220 | engines: {node: '>= 10'} 221 | cpu: [arm64] 222 | os: [android] 223 | 224 | '@tailwindcss/oxide-darwin-arm64@4.0.0-alpha.24': 225 | resolution: {integrity: sha512-GQ96fwvldhtEP1tJYrx4bChNeIRqU3TzrF0rhNTOYQrqBOP1vnImXZVpGyFYLtKqFvJqroffSiGjgKMEXjX72w==} 226 | engines: {node: '>= 10'} 227 | cpu: [arm64] 228 | os: [darwin] 229 | 230 | '@tailwindcss/oxide-darwin-x64@4.0.0-alpha.24': 231 | resolution: {integrity: sha512-37BjzedFksF1SI0qg+KR1deziKDJuN0O7604GLvoMiT8ru3UI5gxKUlRfPFoki2FTL66fHHWV5E0MdtwSBUMCQ==} 232 | engines: {node: '>= 10'} 233 | cpu: [x64] 234 | os: [darwin] 235 | 236 | '@tailwindcss/oxide-freebsd-x64@4.0.0-alpha.24': 237 | resolution: {integrity: sha512-81md1dyyKOnn/gCqO4doOwkx0yFoPS74pwzsYQTgF/Q1TEYqZc3NyBP16YKAWxYBI+eR9BmJr82NEWXQvQ26qg==} 238 | engines: {node: '>= 10'} 239 | cpu: [x64] 240 | os: [freebsd] 241 | 242 | '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.0-alpha.24': 243 | resolution: {integrity: sha512-IzrqUnIfuNPySrH+zqdUeb7x61mynhtrbS+RJFvz9lFBggfYfkDcpV1e9Thu3IfPiMA5U2ajjQLI/FuneAGMNQ==} 244 | engines: {node: '>= 10'} 245 | cpu: [arm] 246 | os: [linux] 247 | 248 | '@tailwindcss/oxide-linux-arm64-gnu@4.0.0-alpha.24': 249 | resolution: {integrity: sha512-HfQWZG0anbWNReaa0bYY7UGcch55kvjRdEeP+1ArVIKTJp5nYq1tsywfO7xBAa5eDkHKx7YGcVsPiNGx31dCLA==} 250 | engines: {node: '>= 10'} 251 | cpu: [arm64] 252 | os: [linux] 253 | 254 | '@tailwindcss/oxide-linux-arm64-musl@4.0.0-alpha.24': 255 | resolution: {integrity: sha512-zh16uVQk/7PgdX2EyYGUEAyOt5da2jfgbv0ZUCx5dXpVOEGYtakLlvlx4yx7ePGgZ2XE5sgrZpnlFlJlwwOfIQ==} 256 | engines: {node: '>= 10'} 257 | cpu: [arm64] 258 | os: [linux] 259 | 260 | '@tailwindcss/oxide-linux-x64-gnu@4.0.0-alpha.24': 261 | resolution: {integrity: sha512-qe4fIGFXa+MOeZDCRvaVgoss33IZwPNuEqjlWktzRHTTtRl+Q8eVoLkJwtg3zyq241DQy0zdBVjtxB/QNM2KvA==} 262 | engines: {node: '>= 10'} 263 | cpu: [x64] 264 | os: [linux] 265 | 266 | '@tailwindcss/oxide-linux-x64-musl@4.0.0-alpha.24': 267 | resolution: {integrity: sha512-obOxjL8nXwVxyYY8TfeOZ12IbhNF+NarjmLi5s/ugTaIEowAFedv/GWYBTeZpiJU5DmdsXYVKVP+D/sm6RaySA==} 268 | engines: {node: '>= 10'} 269 | cpu: [x64] 270 | os: [linux] 271 | 272 | '@tailwindcss/oxide-win32-x64-msvc@4.0.0-alpha.24': 273 | resolution: {integrity: sha512-WlZWFCIyUdDL5x/72JTBHnXQOIYgjCTOFMv4WZCkQaO+Kii30IuyfLUUBp26hk3/FNpsipbWGfc7tx7ioMTqBg==} 274 | engines: {node: '>= 10'} 275 | cpu: [x64] 276 | os: [win32] 277 | 278 | '@tailwindcss/oxide@4.0.0-alpha.24': 279 | resolution: {integrity: sha512-Q4SMbWdRKLPAGkbSmqh2k3qIT8DmYFxui1iwBfd3jzrIaKGw1ks7vbo5HDy0YPqA8yxDjiaXtOGeAegj1Obrkw==} 280 | engines: {node: '>= 10'} 281 | 282 | '@tailwindcss/postcss@4.0.0-alpha.24': 283 | resolution: {integrity: sha512-Zlbl1cncikfVHgCRapHASYA8eAWcJnGk7CTAkoCSH2n72UYfgIM04Am4rUJqzTXeGX08yMFf70hY/Dh6DiWWZA==} 284 | 285 | '@types/node@20.16.5': 286 | resolution: {integrity: sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==} 287 | 288 | '@types/prop-types@15.7.13': 289 | resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} 290 | 291 | '@types/react-dom@18.3.0': 292 | resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} 293 | 294 | '@types/react@18.3.8': 295 | resolution: {integrity: sha512-syBUrW3/XpnW4WJ41Pft+I+aPoDVbrBVQGEnbD7NijDGlVC+8gV/XKRY+7vMDlfPpbwYt0l1vd/Sj8bJGMbs9Q==} 296 | 297 | busboy@1.6.0: 298 | resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} 299 | engines: {node: '>=10.16.0'} 300 | 301 | caniuse-lite@1.0.30001662: 302 | resolution: {integrity: sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==} 303 | 304 | client-only@0.0.1: 305 | resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} 306 | 307 | color-convert@2.0.1: 308 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 309 | engines: {node: '>=7.0.0'} 310 | 311 | color-name@1.1.4: 312 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 313 | 314 | color-string@1.9.1: 315 | resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} 316 | 317 | color@4.2.3: 318 | resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} 319 | engines: {node: '>=12.5.0'} 320 | 321 | csstype@3.1.3: 322 | resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 323 | 324 | detect-libc@1.0.3: 325 | resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} 326 | engines: {node: '>=0.10'} 327 | hasBin: true 328 | 329 | detect-libc@2.0.3: 330 | resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} 331 | engines: {node: '>=8'} 332 | 333 | function-bind@1.1.2: 334 | resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 335 | 336 | hasown@2.0.2: 337 | resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 338 | engines: {node: '>= 0.4'} 339 | 340 | is-arrayish@0.3.2: 341 | resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} 342 | 343 | is-core-module@2.15.1: 344 | resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} 345 | engines: {node: '>= 0.4'} 346 | 347 | jiti@2.0.0-rc.1: 348 | resolution: {integrity: sha512-40BOLe5MVHVgtzjIB52uBqRxTCR07Ziecxx/LVmqRDV14TJaruFX6kKgS9iYhATGSUs04x3S19Kc8ErUKshMhA==} 349 | hasBin: true 350 | 351 | lightningcss-darwin-arm64@1.27.0: 352 | resolution: {integrity: sha512-Gl/lqIXY+d+ySmMbgDf0pgaWSqrWYxVHoc88q+Vhf2YNzZ8DwoRzGt5NZDVqqIW5ScpSnmmjcgXP87Dn2ylSSQ==} 353 | engines: {node: '>= 12.0.0'} 354 | cpu: [arm64] 355 | os: [darwin] 356 | 357 | lightningcss-darwin-x64@1.27.0: 358 | resolution: {integrity: sha512-0+mZa54IlcNAoQS9E0+niovhyjjQWEMrwW0p2sSdLRhLDc8LMQ/b67z7+B5q4VmjYCMSfnFi3djAAQFIDuj/Tg==} 359 | engines: {node: '>= 12.0.0'} 360 | cpu: [x64] 361 | os: [darwin] 362 | 363 | lightningcss-freebsd-x64@1.27.0: 364 | resolution: {integrity: sha512-n1sEf85fePoU2aDN2PzYjoI8gbBqnmLGEhKq7q0DKLj0UTVmOTwDC7PtLcy/zFxzASTSBlVQYJUhwIStQMIpRA==} 365 | engines: {node: '>= 12.0.0'} 366 | cpu: [x64] 367 | os: [freebsd] 368 | 369 | lightningcss-linux-arm-gnueabihf@1.27.0: 370 | resolution: {integrity: sha512-MUMRmtdRkOkd5z3h986HOuNBD1c2lq2BSQA1Jg88d9I7bmPGx08bwGcnB75dvr17CwxjxD6XPi3Qh8ArmKFqCA==} 371 | engines: {node: '>= 12.0.0'} 372 | cpu: [arm] 373 | os: [linux] 374 | 375 | lightningcss-linux-arm64-gnu@1.27.0: 376 | resolution: {integrity: sha512-cPsxo1QEWq2sfKkSq2Bq5feQDHdUEwgtA9KaB27J5AX22+l4l0ptgjMZZtYtUnteBofjee+0oW1wQ1guv04a7A==} 377 | engines: {node: '>= 12.0.0'} 378 | cpu: [arm64] 379 | os: [linux] 380 | 381 | lightningcss-linux-arm64-musl@1.27.0: 382 | resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==} 383 | engines: {node: '>= 12.0.0'} 384 | cpu: [arm64] 385 | os: [linux] 386 | 387 | lightningcss-linux-x64-gnu@1.27.0: 388 | resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==} 389 | engines: {node: '>= 12.0.0'} 390 | cpu: [x64] 391 | os: [linux] 392 | 393 | lightningcss-linux-x64-musl@1.27.0: 394 | resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==} 395 | engines: {node: '>= 12.0.0'} 396 | cpu: [x64] 397 | os: [linux] 398 | 399 | lightningcss-win32-arm64-msvc@1.27.0: 400 | resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==} 401 | engines: {node: '>= 12.0.0'} 402 | cpu: [arm64] 403 | os: [win32] 404 | 405 | lightningcss-win32-x64-msvc@1.27.0: 406 | resolution: {integrity: sha512-/OJLj94Zm/waZShL8nB5jsNj3CfNATLCTyFxZyouilfTmSoLDX7VlVAmhPHoZWVFp4vdmoiEbPEYC8HID3m6yw==} 407 | engines: {node: '>= 12.0.0'} 408 | cpu: [x64] 409 | os: [win32] 410 | 411 | lightningcss@1.27.0: 412 | resolution: {integrity: sha512-8f7aNmS1+etYSLHht0fQApPc2kNO8qGRutifN5rVIc6Xo6ABsEbqOr758UwI7ALVbTt4x1fllKt0PYgzD9S3yQ==} 413 | engines: {node: '>= 12.0.0'} 414 | 415 | nanoid@3.3.7: 416 | resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} 417 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 418 | hasBin: true 419 | 420 | next@15.0.0-canary.161: 421 | resolution: {integrity: sha512-oQlyijNvHviBhc0hXNAir4oby3jebkyqlLRPJtNsj72klUf2WTotoS+pIfvmYr35UK0qC2nAQBoFth7/k4zHpA==} 422 | engines: {node: '>=18.18.0'} 423 | hasBin: true 424 | peerDependencies: 425 | '@opentelemetry/api': ^1.1.0 426 | '@playwright/test': ^1.41.2 427 | babel-plugin-react-compiler: '*' 428 | react: 19.0.0-rc-e740d4b1-20240919 429 | react-dom: 19.0.0-rc-e740d4b1-20240919 430 | sass: ^1.3.0 431 | peerDependenciesMeta: 432 | '@opentelemetry/api': 433 | optional: true 434 | '@playwright/test': 435 | optional: true 436 | babel-plugin-react-compiler: 437 | optional: true 438 | sass: 439 | optional: true 440 | 441 | path-parse@1.0.7: 442 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 443 | 444 | picocolors@1.1.0: 445 | resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} 446 | 447 | pify@2.3.0: 448 | resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} 449 | engines: {node: '>=0.10.0'} 450 | 451 | postcss-import@16.1.0: 452 | resolution: {integrity: sha512-7hsAZ4xGXl4MW+OKEWCnF6T5jqBw80/EE9aXg1r2yyn1RsVEU8EtKXbijEODa+rg7iih4bKf7vlvTGYR4CnPNg==} 453 | engines: {node: '>=18.0.0'} 454 | peerDependencies: 455 | postcss: ^8.0.0 456 | 457 | postcss-value-parser@4.2.0: 458 | resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} 459 | 460 | postcss@8.4.31: 461 | resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} 462 | engines: {node: ^10 || ^12 || >=14} 463 | 464 | postcss@8.4.47: 465 | resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} 466 | engines: {node: ^10 || ^12 || >=14} 467 | 468 | react-dom@19.0.0-rc-e740d4b1-20240919: 469 | resolution: {integrity: sha512-4vvVhTQYf9CxJuTtBqoRbqswnt2MA3xVl5UOQP9jnqC5wfkBeWM2gNk/tH4avLFWv5k6YZP75EqhNphiW7EXnA==} 470 | peerDependencies: 471 | react: 19.0.0-rc-e740d4b1-20240919 472 | 473 | react@19.0.0-rc-e740d4b1-20240919: 474 | resolution: {integrity: sha512-lQRkQYhG+6xPI0KV3e5H3uIaRCe1zjaOYy4MqhDsL23a07gcnDD/dIR0zS+1QTr2r4Msu7TPimExQroK0ySNGw==} 475 | engines: {node: '>=0.10.0'} 476 | 477 | read-cache@1.0.0: 478 | resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} 479 | 480 | resolve@1.22.8: 481 | resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} 482 | hasBin: true 483 | 484 | scheduler@0.25.0-rc-e740d4b1-20240919: 485 | resolution: {integrity: sha512-PRZr9KJzwg8HQgZdmngBza+DrzG0sElnUTvJIDiZWdWpSb9kkdousacJFEKFtLbV/NCq3sTOaX6LaMGyhXl5ug==} 486 | 487 | semver@7.6.3: 488 | resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} 489 | engines: {node: '>=10'} 490 | hasBin: true 491 | 492 | sharp@0.33.5: 493 | resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} 494 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 495 | 496 | simple-swizzle@0.2.2: 497 | resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} 498 | 499 | source-map-js@1.2.1: 500 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 501 | engines: {node: '>=0.10.0'} 502 | 503 | streamsearch@1.1.0: 504 | resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} 505 | engines: {node: '>=10.0.0'} 506 | 507 | styled-jsx@5.1.6: 508 | resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} 509 | engines: {node: '>= 12.0.0'} 510 | peerDependencies: 511 | '@babel/core': '*' 512 | babel-plugin-macros: '*' 513 | react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' 514 | peerDependenciesMeta: 515 | '@babel/core': 516 | optional: true 517 | babel-plugin-macros: 518 | optional: true 519 | 520 | supports-preserve-symlinks-flag@1.0.0: 521 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 522 | engines: {node: '>= 0.4'} 523 | 524 | tailwindcss@4.0.0-alpha.24: 525 | resolution: {integrity: sha512-SOowAXp/sGer9VCOPO1HlnwbCmDz9nD/aXkdNzt4zkMRRlSAPdE1w6TD3I+RgR1qD0QDw5J9nkBA47mrFMjYPQ==} 526 | 527 | tslib@2.7.0: 528 | resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} 529 | 530 | typescript@5.6.2: 531 | resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} 532 | engines: {node: '>=14.17'} 533 | hasBin: true 534 | 535 | undici-types@6.19.8: 536 | resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} 537 | 538 | snapshots: 539 | 540 | '@emnapi/runtime@1.2.0': 541 | dependencies: 542 | tslib: 2.7.0 543 | optional: true 544 | 545 | '@img/sharp-darwin-arm64@0.33.5': 546 | optionalDependencies: 547 | '@img/sharp-libvips-darwin-arm64': 1.0.4 548 | optional: true 549 | 550 | '@img/sharp-darwin-x64@0.33.5': 551 | optionalDependencies: 552 | '@img/sharp-libvips-darwin-x64': 1.0.4 553 | optional: true 554 | 555 | '@img/sharp-libvips-darwin-arm64@1.0.4': 556 | optional: true 557 | 558 | '@img/sharp-libvips-darwin-x64@1.0.4': 559 | optional: true 560 | 561 | '@img/sharp-libvips-linux-arm64@1.0.4': 562 | optional: true 563 | 564 | '@img/sharp-libvips-linux-arm@1.0.5': 565 | optional: true 566 | 567 | '@img/sharp-libvips-linux-s390x@1.0.4': 568 | optional: true 569 | 570 | '@img/sharp-libvips-linux-x64@1.0.4': 571 | optional: true 572 | 573 | '@img/sharp-libvips-linuxmusl-arm64@1.0.4': 574 | optional: true 575 | 576 | '@img/sharp-libvips-linuxmusl-x64@1.0.4': 577 | optional: true 578 | 579 | '@img/sharp-linux-arm64@0.33.5': 580 | optionalDependencies: 581 | '@img/sharp-libvips-linux-arm64': 1.0.4 582 | optional: true 583 | 584 | '@img/sharp-linux-arm@0.33.5': 585 | optionalDependencies: 586 | '@img/sharp-libvips-linux-arm': 1.0.5 587 | optional: true 588 | 589 | '@img/sharp-linux-s390x@0.33.5': 590 | optionalDependencies: 591 | '@img/sharp-libvips-linux-s390x': 1.0.4 592 | optional: true 593 | 594 | '@img/sharp-linux-x64@0.33.5': 595 | optionalDependencies: 596 | '@img/sharp-libvips-linux-x64': 1.0.4 597 | optional: true 598 | 599 | '@img/sharp-linuxmusl-arm64@0.33.5': 600 | optionalDependencies: 601 | '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 602 | optional: true 603 | 604 | '@img/sharp-linuxmusl-x64@0.33.5': 605 | optionalDependencies: 606 | '@img/sharp-libvips-linuxmusl-x64': 1.0.4 607 | optional: true 608 | 609 | '@img/sharp-wasm32@0.33.5': 610 | dependencies: 611 | '@emnapi/runtime': 1.2.0 612 | optional: true 613 | 614 | '@img/sharp-win32-ia32@0.33.5': 615 | optional: true 616 | 617 | '@img/sharp-win32-x64@0.33.5': 618 | optional: true 619 | 620 | '@next/env@15.0.0-canary.161': {} 621 | 622 | '@next/swc-darwin-arm64@15.0.0-canary.161': 623 | optional: true 624 | 625 | '@next/swc-darwin-x64@15.0.0-canary.161': 626 | optional: true 627 | 628 | '@next/swc-linux-arm64-gnu@15.0.0-canary.161': 629 | optional: true 630 | 631 | '@next/swc-linux-arm64-musl@15.0.0-canary.161': 632 | optional: true 633 | 634 | '@next/swc-linux-x64-gnu@15.0.0-canary.161': 635 | optional: true 636 | 637 | '@next/swc-linux-x64-musl@15.0.0-canary.161': 638 | optional: true 639 | 640 | '@next/swc-win32-arm64-msvc@15.0.0-canary.161': 641 | optional: true 642 | 643 | '@next/swc-win32-ia32-msvc@15.0.0-canary.161': 644 | optional: true 645 | 646 | '@next/swc-win32-x64-msvc@15.0.0-canary.161': 647 | optional: true 648 | 649 | '@swc/counter@0.1.3': {} 650 | 651 | '@swc/helpers@0.5.13': 652 | dependencies: 653 | tslib: 2.7.0 654 | 655 | '@tailwindcss/node@4.0.0-alpha.24': 656 | dependencies: 657 | jiti: 2.0.0-rc.1 658 | 659 | '@tailwindcss/oxide-android-arm64@4.0.0-alpha.24': 660 | optional: true 661 | 662 | '@tailwindcss/oxide-darwin-arm64@4.0.0-alpha.24': 663 | optional: true 664 | 665 | '@tailwindcss/oxide-darwin-x64@4.0.0-alpha.24': 666 | optional: true 667 | 668 | '@tailwindcss/oxide-freebsd-x64@4.0.0-alpha.24': 669 | optional: true 670 | 671 | '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.0-alpha.24': 672 | optional: true 673 | 674 | '@tailwindcss/oxide-linux-arm64-gnu@4.0.0-alpha.24': 675 | optional: true 676 | 677 | '@tailwindcss/oxide-linux-arm64-musl@4.0.0-alpha.24': 678 | optional: true 679 | 680 | '@tailwindcss/oxide-linux-x64-gnu@4.0.0-alpha.24': 681 | optional: true 682 | 683 | '@tailwindcss/oxide-linux-x64-musl@4.0.0-alpha.24': 684 | optional: true 685 | 686 | '@tailwindcss/oxide-win32-x64-msvc@4.0.0-alpha.24': 687 | optional: true 688 | 689 | '@tailwindcss/oxide@4.0.0-alpha.24': 690 | optionalDependencies: 691 | '@tailwindcss/oxide-android-arm64': 4.0.0-alpha.24 692 | '@tailwindcss/oxide-darwin-arm64': 4.0.0-alpha.24 693 | '@tailwindcss/oxide-darwin-x64': 4.0.0-alpha.24 694 | '@tailwindcss/oxide-freebsd-x64': 4.0.0-alpha.24 695 | '@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.0-alpha.24 696 | '@tailwindcss/oxide-linux-arm64-gnu': 4.0.0-alpha.24 697 | '@tailwindcss/oxide-linux-arm64-musl': 4.0.0-alpha.24 698 | '@tailwindcss/oxide-linux-x64-gnu': 4.0.0-alpha.24 699 | '@tailwindcss/oxide-linux-x64-musl': 4.0.0-alpha.24 700 | '@tailwindcss/oxide-win32-x64-msvc': 4.0.0-alpha.24 701 | 702 | '@tailwindcss/postcss@4.0.0-alpha.24(postcss@8.4.47)': 703 | dependencies: 704 | '@tailwindcss/node': 4.0.0-alpha.24 705 | '@tailwindcss/oxide': 4.0.0-alpha.24 706 | lightningcss: 1.27.0 707 | postcss-import: 16.1.0(postcss@8.4.47) 708 | tailwindcss: 4.0.0-alpha.24 709 | transitivePeerDependencies: 710 | - postcss 711 | 712 | '@types/node@20.16.5': 713 | dependencies: 714 | undici-types: 6.19.8 715 | 716 | '@types/prop-types@15.7.13': {} 717 | 718 | '@types/react-dom@18.3.0': 719 | dependencies: 720 | '@types/react': 18.3.8 721 | 722 | '@types/react@18.3.8': 723 | dependencies: 724 | '@types/prop-types': 15.7.13 725 | csstype: 3.1.3 726 | 727 | busboy@1.6.0: 728 | dependencies: 729 | streamsearch: 1.1.0 730 | 731 | caniuse-lite@1.0.30001662: {} 732 | 733 | client-only@0.0.1: {} 734 | 735 | color-convert@2.0.1: 736 | dependencies: 737 | color-name: 1.1.4 738 | optional: true 739 | 740 | color-name@1.1.4: 741 | optional: true 742 | 743 | color-string@1.9.1: 744 | dependencies: 745 | color-name: 1.1.4 746 | simple-swizzle: 0.2.2 747 | optional: true 748 | 749 | color@4.2.3: 750 | dependencies: 751 | color-convert: 2.0.1 752 | color-string: 1.9.1 753 | optional: true 754 | 755 | csstype@3.1.3: {} 756 | 757 | detect-libc@1.0.3: {} 758 | 759 | detect-libc@2.0.3: 760 | optional: true 761 | 762 | function-bind@1.1.2: {} 763 | 764 | hasown@2.0.2: 765 | dependencies: 766 | function-bind: 1.1.2 767 | 768 | is-arrayish@0.3.2: 769 | optional: true 770 | 771 | is-core-module@2.15.1: 772 | dependencies: 773 | hasown: 2.0.2 774 | 775 | jiti@2.0.0-rc.1: {} 776 | 777 | lightningcss-darwin-arm64@1.27.0: 778 | optional: true 779 | 780 | lightningcss-darwin-x64@1.27.0: 781 | optional: true 782 | 783 | lightningcss-freebsd-x64@1.27.0: 784 | optional: true 785 | 786 | lightningcss-linux-arm-gnueabihf@1.27.0: 787 | optional: true 788 | 789 | lightningcss-linux-arm64-gnu@1.27.0: 790 | optional: true 791 | 792 | lightningcss-linux-arm64-musl@1.27.0: 793 | optional: true 794 | 795 | lightningcss-linux-x64-gnu@1.27.0: 796 | optional: true 797 | 798 | lightningcss-linux-x64-musl@1.27.0: 799 | optional: true 800 | 801 | lightningcss-win32-arm64-msvc@1.27.0: 802 | optional: true 803 | 804 | lightningcss-win32-x64-msvc@1.27.0: 805 | optional: true 806 | 807 | lightningcss@1.27.0: 808 | dependencies: 809 | detect-libc: 1.0.3 810 | optionalDependencies: 811 | lightningcss-darwin-arm64: 1.27.0 812 | lightningcss-darwin-x64: 1.27.0 813 | lightningcss-freebsd-x64: 1.27.0 814 | lightningcss-linux-arm-gnueabihf: 1.27.0 815 | lightningcss-linux-arm64-gnu: 1.27.0 816 | lightningcss-linux-arm64-musl: 1.27.0 817 | lightningcss-linux-x64-gnu: 1.27.0 818 | lightningcss-linux-x64-musl: 1.27.0 819 | lightningcss-win32-arm64-msvc: 1.27.0 820 | lightningcss-win32-x64-msvc: 1.27.0 821 | 822 | nanoid@3.3.7: {} 823 | 824 | next@15.0.0-canary.161(react-dom@19.0.0-rc-e740d4b1-20240919(react@19.0.0-rc-e740d4b1-20240919))(react@19.0.0-rc-e740d4b1-20240919): 825 | dependencies: 826 | '@next/env': 15.0.0-canary.161 827 | '@swc/counter': 0.1.3 828 | '@swc/helpers': 0.5.13 829 | busboy: 1.6.0 830 | caniuse-lite: 1.0.30001662 831 | postcss: 8.4.31 832 | react: 19.0.0-rc-e740d4b1-20240919 833 | react-dom: 19.0.0-rc-e740d4b1-20240919(react@19.0.0-rc-e740d4b1-20240919) 834 | styled-jsx: 5.1.6(react@19.0.0-rc-e740d4b1-20240919) 835 | optionalDependencies: 836 | '@next/swc-darwin-arm64': 15.0.0-canary.161 837 | '@next/swc-darwin-x64': 15.0.0-canary.161 838 | '@next/swc-linux-arm64-gnu': 15.0.0-canary.161 839 | '@next/swc-linux-arm64-musl': 15.0.0-canary.161 840 | '@next/swc-linux-x64-gnu': 15.0.0-canary.161 841 | '@next/swc-linux-x64-musl': 15.0.0-canary.161 842 | '@next/swc-win32-arm64-msvc': 15.0.0-canary.161 843 | '@next/swc-win32-ia32-msvc': 15.0.0-canary.161 844 | '@next/swc-win32-x64-msvc': 15.0.0-canary.161 845 | sharp: 0.33.5 846 | transitivePeerDependencies: 847 | - '@babel/core' 848 | - babel-plugin-macros 849 | 850 | path-parse@1.0.7: {} 851 | 852 | picocolors@1.1.0: {} 853 | 854 | pify@2.3.0: {} 855 | 856 | postcss-import@16.1.0(postcss@8.4.47): 857 | dependencies: 858 | postcss: 8.4.47 859 | postcss-value-parser: 4.2.0 860 | read-cache: 1.0.0 861 | resolve: 1.22.8 862 | 863 | postcss-value-parser@4.2.0: {} 864 | 865 | postcss@8.4.31: 866 | dependencies: 867 | nanoid: 3.3.7 868 | picocolors: 1.1.0 869 | source-map-js: 1.2.1 870 | 871 | postcss@8.4.47: 872 | dependencies: 873 | nanoid: 3.3.7 874 | picocolors: 1.1.0 875 | source-map-js: 1.2.1 876 | 877 | react-dom@19.0.0-rc-e740d4b1-20240919(react@19.0.0-rc-e740d4b1-20240919): 878 | dependencies: 879 | react: 19.0.0-rc-e740d4b1-20240919 880 | scheduler: 0.25.0-rc-e740d4b1-20240919 881 | 882 | react@19.0.0-rc-e740d4b1-20240919: {} 883 | 884 | read-cache@1.0.0: 885 | dependencies: 886 | pify: 2.3.0 887 | 888 | resolve@1.22.8: 889 | dependencies: 890 | is-core-module: 2.15.1 891 | path-parse: 1.0.7 892 | supports-preserve-symlinks-flag: 1.0.0 893 | 894 | scheduler@0.25.0-rc-e740d4b1-20240919: {} 895 | 896 | semver@7.6.3: 897 | optional: true 898 | 899 | sharp@0.33.5: 900 | dependencies: 901 | color: 4.2.3 902 | detect-libc: 2.0.3 903 | semver: 7.6.3 904 | optionalDependencies: 905 | '@img/sharp-darwin-arm64': 0.33.5 906 | '@img/sharp-darwin-x64': 0.33.5 907 | '@img/sharp-libvips-darwin-arm64': 1.0.4 908 | '@img/sharp-libvips-darwin-x64': 1.0.4 909 | '@img/sharp-libvips-linux-arm': 1.0.5 910 | '@img/sharp-libvips-linux-arm64': 1.0.4 911 | '@img/sharp-libvips-linux-s390x': 1.0.4 912 | '@img/sharp-libvips-linux-x64': 1.0.4 913 | '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 914 | '@img/sharp-libvips-linuxmusl-x64': 1.0.4 915 | '@img/sharp-linux-arm': 0.33.5 916 | '@img/sharp-linux-arm64': 0.33.5 917 | '@img/sharp-linux-s390x': 0.33.5 918 | '@img/sharp-linux-x64': 0.33.5 919 | '@img/sharp-linuxmusl-arm64': 0.33.5 920 | '@img/sharp-linuxmusl-x64': 0.33.5 921 | '@img/sharp-wasm32': 0.33.5 922 | '@img/sharp-win32-ia32': 0.33.5 923 | '@img/sharp-win32-x64': 0.33.5 924 | optional: true 925 | 926 | simple-swizzle@0.2.2: 927 | dependencies: 928 | is-arrayish: 0.3.2 929 | optional: true 930 | 931 | source-map-js@1.2.1: {} 932 | 933 | streamsearch@1.1.0: {} 934 | 935 | styled-jsx@5.1.6(react@19.0.0-rc-e740d4b1-20240919): 936 | dependencies: 937 | client-only: 0.0.1 938 | react: 19.0.0-rc-e740d4b1-20240919 939 | 940 | supports-preserve-symlinks-flag@1.0.0: {} 941 | 942 | tailwindcss@4.0.0-alpha.24: {} 943 | 944 | tslib@2.7.0: {} 945 | 946 | typescript@5.6.2: {} 947 | 948 | undici-types@6.19.8: {} 949 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | export default { 3 | plugins: { 4 | '@tailwindcss/postcss': {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/course.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | _ __ _ 3 | __ _(_)_ __ ___ / _| ___ _ __ _ __ ___ __ _ ___| |_ 4 | \ \ / / | '_ ` _ \ | |_ / _ \| '__| | '__/ _ \/ _` |/ __| __| 5 | \ V /| | | | | | | | _| (_) | | | | | __/ (_| | (__| |_ 6 | \_/_|_|_| |_| |_| |_| \___/|_| |_| \___|\__,_|\___|\__| 7 | __| | _____ _____| | ___ _ __ ___ _ __ ___ 8 | / _` |/ _ \ \ / / _ \ |/ _ \| '_ \ / _ \ '__/ __| 9 | | (_| | __/\ V / __/ | (_) | |_) | __/ | \__ \ 10 | \__,_|\___| \_/ \___|_|\___/| .__/ \___|_| |___/ 11 | |_| 12 | 13 | by leerob (leerob.com). 14 | 15 | This is a mini-course for React developers to learn Vim commands and apply them 16 | to real-world scenarios by fixing and editing React code. You'll go through a 17 | series of tasks, each with broken or missing code, that you will fix using 18 | specific Vim motions. 19 | 20 | This course will not teach every possible Vim motion. Instead, it will focus on 21 | the commands I've found to be most helpful when working with React and 22 | JavaScript. However, they apply more generally outside React. Let's dive in! 23 | 24 | Estimated time to complete the setup and course: 1 hour. 25 | 26 | NOTE: Prerequisites 27 | 28 | Before starting this course, you should have Neovim installed and configured: 29 | 30 | - 1) Install Neovim and its dependencies 31 | - macOS: `brew install nvim fzf ripgrep` 32 | - Windows: `choco install -y neovim git ripgrep wget fd unzip gzip mingw make` 33 | - 2) Follow this quickstart: https://github.com/nvim-lua/kickstart.nvim 34 | - Don't feel you need to understand the entire Lua file 35 | - At the completion of the course, we'll talk about plugins and configuration 36 | - We'll also link some additional resources if you want to learn more 37 | - 3) That's it! Download this file and start Neovim with `nvim course.tsx` 38 | 39 | I recommend using Kickstart to quickly get started with a productive Neovim setup. 40 | Kickstart is not a Neovim distribution (like Lazyvim) where everything is bundled. 41 | Instead, it's a starting point for building your own config, with all of the best 42 | defaults in the ecosystem. Think of a Neovim distro like Create React App (CRA), 43 | and Kickstart like ejecting from CRA, if that helps. 44 | 45 | NOTE: Introduction to Vim 46 | 47 | Vim is a highly configurable text editor built to enable efficient text editing. 48 | It's based on the concept of modal editing, where the editor operates in 49 | different modes, each designed for specific tasks. This approach allows you to 50 | perform complex text manipulations with minimal keystrokes, increasing your 51 | productivity. 52 | 53 | In traditional text editors, you have one mode where all keys are used for 54 | typing text. Vim, however, separates the tasks of navigating and editing text 55 | from inserting text. This separation is what makes Vim so powerful and 56 | efficient once you get the hang of it. 57 | 58 | By thinking in terms of actions (what you want to do) and motions (where you 59 | want to do it), you can perform complex edits swiftly. 60 | 61 | NOTE: Vim Basics 62 | 63 | ## Modes 64 | 65 | Vim has several modes, but we'll focus on the primary ones: 66 | 67 | - Normal Mode: The default mode for navigating and executing commands. 68 | - Insert Mode: Where you insert or modify text. 69 | - Visual Mode: For selecting blocks of text. 70 | - Command Mode: For executing extended commands (accessed by pressing `:`). 71 | 72 | ## Switching Between Modes 73 | 74 | - To Normal Mode: 75 | - `Esc`: Return to Normal Mode. 76 | - To Insert Mode: 77 | - `i`: Insert before the cursor. 78 | - `I`: Insert at the beginning of the line. 79 | - `a`: Append after the cursor. 80 | - `A`: Append at the end of the line. 81 | - To Visual Mode: 82 | - `v`: Start visual selection (character-wise). 83 | - `V`: Start visual selection (line-wise). 84 | - `Ctrl+v`: Start visual block selection. 85 | - To Command Mode: Press `:` from Normal Mode. 86 | 87 | NOTE: Navigating in Vim 88 | 89 | Vim allows you to navigate efficiently without leaving the home row. 90 | 91 | ## Basic Movements 92 | 93 | You can navigate in normal mode with left (`h`), down (`j`), up (`k`), and right (`l`). 94 | 95 | k 96 | ↑ 97 | h ← → l 98 | ↓ 99 | j 100 | 101 | It's okay (and expected) if your right pointer finger rests on `j`. 102 | Don't worry if Vim movements feel strange at first. They take time to get used to. 103 | If you still occassionally use your arrow keys or mouse, that's okay — don't put 104 | too much pressure on yourself to have it perfect at the start. 105 | 106 | ## Word Movements 107 | 108 | - Forward by Word: `w` (move to the beginning of the next word). 109 | - Backward by Word: `b` (move to the beginning of the previous word). 110 | - End of Word: `e` (move to the end of the word). 111 | 112 | I recommend trying to use `w` and `b` the majority of the time, instead of `h` and `l`. 113 | 114 | ## Line Movements 115 | 116 | - Start of Line: `0` (zero). 117 | - Start of Non-Whitespace Character: `^`. 118 | - End of Line: `$`. 119 | 120 | ## Scrolling 121 | 122 | - Half-Page Down: `Ctrl+d`. 123 | - Half-Page Up: `Ctrl+u`. 124 | 125 | ## Jumping to Specific Lines 126 | 127 | - Go to Line Number: `:` and press `Enter` (e.g., `:10`). 128 | - Top of File: `gg`. 129 | - Bottom of File: `G`. 130 | 131 | NOTE: Actions and Text Objects 132 | 133 | This is where Vim really starts to get powerful. You can combine different 134 | keys together to make something that feels like a cheat code. For example, 135 | let’s inspect the `ciw` command. There are three parts: 136 | 137 | 1. Action: `c` (change) 138 | 2. Motion: `i` (in) 139 | 3. Object: `w` (word) 140 | 141 | It’s helpful to read this like a sentence — “change in word”. There are multiple 142 | actions, motions, and objects. For example, what if we changed the object from 143 | a word to a paragraph? `cip` or “change in paragraph”. Let’s inspect another command: 144 | 145 | 1. Action: `v` (select) 146 | 2. Motion: `a` (around) 147 | 3. Object: `b` (brackets) 148 | 149 | For more examples, see the cheat sheet in the README. 150 | 151 | NOTE: Undo and Redo 152 | 153 | - Undo: `u`. 154 | - Redo: `Ctrl+r`. 155 | 156 | Okay, let's get started! Feel free to open and reference the cheat sheet 157 | in the repo README as you make your way through the course. 158 | 159 | Cheat sheet: https://github.com/leerob/vim-for-react-devs 160 | 161 | */ 162 | 163 | // NOTE: ✦ Lesson: Basic Motions 164 | 165 | // First, let's make sure you know how to exit Vim. 166 | // Steps: 167 | // 1. Press `:` to enter command mode. 168 | // 2. Press `q` and the `Enter`. 169 | // 3. Re-open the course with `nvim course.tsx`. 170 | // You can also use `ZZ` to quickly save and close. 171 | 172 | // That's it! Now that we completed the hardest part... on to the rest. 173 | 174 | // Steps: 175 | // 1. Place your cursor below the TODO line. 176 | // 2. Press `i` to enter insert mode. 177 | // 3. Type `function App() { }`. 178 | // 4. Press `Esc` to exit insert mode. 179 | // Note: At the end of the course, we'll recommend plugins to 180 | // automatically close tags and brackets for you. 181 | 182 | // TODO: Create a simple functional component called `App`. 183 | 184 | // Expected result: 185 | // function App() { } 186 | 187 | // Now, let's add a return statement inside the `App` function. 188 | 189 | // Steps: 190 | // 1. Place your cursor between the curly braces `{ }`. 191 | // 2. Press `i` to enter insert mode. 192 | // 3. Type the following code: 193 | // ``` 194 | // return ( 195 | //
196 | //
197 | // ); 198 | // ``` 199 | // 4. Press `Esc` to exit insert mode. 200 | 201 | // TODO: Inside the `App` function, add a `return` statement that returns a `
`. 202 | 203 | function App() { } 204 | 205 | // Expected result: 206 | // function App() { 207 | // return ( 208 | //
209 | //
210 | // ); 211 | // } 212 | 213 | // Let's add a `

` tag inside the `

`. 214 | 215 | // Steps: 216 | // 1. Place your cursor anywhere on the `
` line. 217 | // 2. Press `o` to create a new line below and enter insert mode. 218 | // 3. Type `

Hello

` and press `Esc`. 219 | // Bonus tip: you can also use `O` to create a new line above, rather than below. 220 | 221 | // TODO: Add a `

` tag with the text "Hello" inside the `

`. 222 | 223 | function App() { 224 | return ( 225 |
226 |
227 | ); 228 | } 229 | 230 | // Expected result: 231 | // function App() { 232 | // return ( 233 | //
234 | //

Hello

235 | //
236 | // ); 237 | // } 238 | 239 | // Now, let's practice moving to the start and end of lines. 240 | 241 | // Steps: 242 | // 1. Place your cursor anywhere on the `

` line. 243 | // 2. Press `0` (zero) to move to the start of the line. 244 | // 3. Alternatively, to enter insert mode at the beginning of the line, press `I` (capital i). 245 | // 4. Notice how `I` will also match the indentation level – nice! 246 | // 5. When navigating back to the start, you can also use `^` to go to non-whitespace chars. 247 | 248 | // TODO: Move to the beginning of the `

` line. 249 | 250 | function App() { 251 | return ( 252 |

253 |

Hello

254 |
255 | ); 256 | } 257 | 258 | // Now, let's do the opposite. 259 | 260 | // Steps: 261 | // 1. Press `$` to move to the end of the

line. 262 | // 2. Alternatively, to enter insert mode at the end of the line, press `A` (capital a). 263 | // 3. Remember to exit insert mode by pressing `Esc`. 264 | // 4. Try going back and forth with `0` and `$` a few times. 265 | 266 | // TODO: Move to the end of the `

` line. 267 | 268 | function App() { 269 | return ( 270 |

271 |

Hello

272 |
273 | ); 274 | } 275 | 276 | // Now, we want to change "Hello" to "World". 277 | 278 | // Steps: 279 | // 1. Use `w` (move forward by word) to move the cursor to "Hello". 280 | // 2. Press `cw` (change word) to change the word. (`cw` stands for "change word"). 281 | // 3. Type `World` and press `Esc`. 282 | 283 | // TODO: Change "Hello" to "World". 284 | 285 | function App() { 286 | return ( 287 |
288 |

Hello

289 |
290 | ); 291 | } 292 | 293 | // Expected result: 294 | // function App() { 295 | // return ( 296 | //
297 | //

World

298 | //
299 | // ); 300 | // } 301 | 302 | // Let's add another paragraph below. 303 | 304 | // Steps: 305 | // 1. From normal mode, navigate to the end of the `

World

` line. 306 | // 2. Press `o` to create a new line below and enter insert mode. 307 | // 3. Type `

Welcome to the Vim course.

` and press `Esc`. 308 | 309 | // TODO: Add another `

` tag with the text "Welcome to the Vim course." 310 | 311 | function App() { 312 | return ( 313 |

314 |

World

315 |
316 | ); 317 | } 318 | 319 | // Expected result: 320 | // function App() { 321 | // return ( 322 | //
323 | //

World

324 | //

Welcome to the Vim course.

325 | //
326 | // ); 327 | // } 328 | 329 | // --- End of Basic Motions Lesson --- 330 | 331 | // NOTE: ✦ Lesson: Search and Replace 332 | 333 | // Let's learn how to search for a keyword and replace it. 334 | 335 | // Steps: 336 | // 1. Suppose we have multiple instances of the word "World" in our code. 337 | // 2. Press `/World` and press `Enter` to search for "World". 338 | // 3. Press `n` to go to the next occurrence, and `N` to go back. 339 | // 4. Once you're on the instance you want to change, press `cw` (change word). 340 | // 5. Type `Developer` and press `Esc`. 341 | 342 | // TODO: Replace the word "World" with "Developer" using search. 343 | 344 | // Add another instance of "World" for practice. 345 | 346 | function App() { 347 | return ( 348 |
349 |

World

{/* Change me */} 350 |

Welcome to the Vim course.

351 |

Hello World!

352 |
353 | ); 354 | } 355 | 356 | // Expected result: 357 | //

Developer

358 | //

Welcome to the Vim course.

359 | //

Hello World!

360 | 361 | // --- End of Search and Replace Lesson --- 362 | 363 | // NOTE: ✦ Lesson: Refactoring 364 | 365 | // Inside our `App` function, let's add local state with `useState`. 366 | 367 | // Steps: 368 | // 1. Move inside the `App` function, after the opening `{`. 369 | // 2. Press `o` to create a new line below and enter insert mode. 370 | // 3. Type `const [name, setName] = useState();` and press `Esc`. 371 | 372 | // TODO: Add a new `useState` declaration for `name`. 373 | 374 | function App() { 375 | return ( 376 |
377 |

Developer

378 |

Welcome to the Vim course.

379 |

Hello World!

380 |
381 | ); 382 | } 383 | 384 | // Expected result: 385 | // const [name, setName] = useState(); 386 | 387 | // Let's add a `` and press `Esc`. 393 | 394 | // TODO: Add a ` 410 | 411 | // Let's refactor the `onClick` handler into a separate function. 412 | 413 | // Steps: 414 | // 1. Place your cursor at the start of the line with ` 439 |
440 | ); 441 | } 442 | 443 | // Expected result: 444 | // function App() { 445 | // const [name, setName] = useState(); 446 | // 447 | // const handleClick = () => setName('Lee') 448 | // 449 | // return ( 450 | //
451 | //

Developer

452 | //

Welcome to the Vim course.

453 | //

Hello World!

454 | // 455 | //
456 | // ); 457 | // } 458 | 459 | // What if you don't want to replace the entire `onClick` content? 460 | 461 | // Steps: 462 | // 1. Place your cursor on the ` 479 |
480 | ); 481 | } 482 | 483 | // Expected result: 484 | // 485 | 486 | // --- End of Refactoring Lesson --- 487 | 488 | // NOTE: ✦ Lesson: Text Objects 489 | 490 | // Let's practice using text objects to manipulate text inside quotes. 491 | 492 | // Steps: 493 | // 1. Place your cursor anywhere on the line with `'Click Me'` before the string. 494 | // 2. Press `ci'` (change inside quotes) to delete the content inside the quotes and enter insert mode. 495 | // 3. Type `Update Name` and press `Esc` to exit insert mode. 496 | 497 | // TODO: Change the text inside the quotes of `'Click Me'` to `'Update Name'`. 498 | 499 | function App() { 500 | const [name, setName] = useState(); 501 | 502 | const handleClick = () => setName('Lee'); 503 | 504 | return ( 505 |
506 |

Developer

507 |

Welcome to the Vim course.

508 |

Hello World!

509 | 510 |
511 | ); 512 | } 513 | 514 | // Expected result: 515 | // {'Update Name'} 516 | 517 | // Now, let's try changing from `'` to a string template, using `caq`. 518 | // "Change around quotes" is even better than a specific character, as it 519 | // will handle `"`, `'`, and even `` ` ``. `caq` comes from the `mini.ai` 520 | // plugin, which the Neovim Quickstart sets up for us. 521 | 522 | // Steps: 523 | // 1. Place your cursor anywhere on the line with `{'Update Name'}`. 524 | // 2. Press `caq` to delete the string and enter insert mode. 525 | // 3. Type `Name: ${name}` and press `Esc` to exit insert mode. 526 | 527 | // TODO: Modify the button text to be a string template literal. 528 | 529 | function App() { 530 | const [name, setName] = useState(); 531 | 532 | const handleClick = () => setName('Lee'); 533 | 534 | return ( 535 |
536 |

Developer

537 |

Welcome to the Vim course.

538 |

Hello World!

539 | 540 |
541 | ); 542 | } 543 | 544 | // Expected result: 545 | // 546 | 547 | // --- End of Text Objects Lesson --- 548 | 549 | // NOTE: ✦ Lesson: More Refactoring 550 | 551 | // Let's replace the contents inside the `
` tag. 552 | 553 | // Steps: 554 | // 1. Navigate anywhere inside of the `
` tag in the return statement. 555 | // 2. Press `cit` (change inside tag) to delete all children of `
` and enter insert mode. 556 | // 3. Type `

This is the new content.

` and press `Esc` to exit insert mode. 557 | 558 | // TODO: Replace the contents inside the `
` tag with `

This is the new content.

`. 559 | 560 | function App() { 561 | return ( 562 |
563 | {/* Existing content */} 564 |
565 | ); 566 | } 567 | 568 | // Expected result: 569 | // function App() { 570 | // return ( 571 | //
572 | //

This is the new content.

573 | //
574 | // ); 575 | // } 576 | 577 | // ✦ Lesson: Quick Deletes 578 | 579 | // Let's delete everything inside parentheses, commonly used for React props. 580 | 581 | // Steps: 582 | // 1. Place your cursor anywhere on the line with `function Component({ foo: 'bar' }) {`. 583 | // 2. Press `di(` (delete inside parentheses) to delete the content inside `()`. 584 | 585 | // TODO: Delete the props inside the component definition. 586 | 587 | function Component({ foo: 'bar' }) { 588 | // ... 589 | } 590 | 591 | // Expected result: 592 | // function Component() { 593 | // // ... 594 | // } 595 | 596 | // If you need to change the props, enter insert mode with `ci(`. 597 | 598 | // TODO: Try using `ci(` to delete the props and start typing immediately. 599 | 600 | function Component({ foo: 'bar' }) { 601 | // ... 602 | } 603 | 604 | // --- End of More Refactoring Lesson --- 605 | 606 | // NOTE: ✦ Lesson: Deleting Lines and Repeating Commands 607 | 608 | // Steps: 609 | // 1. Place your cursor on the first `

` line inside the `

`. 610 | // 2. Press `dd` to delete the line. 611 | // 3. Press `.` (dot) to repeat the last command. 612 | // 4. Repeat pressing `.` until you've deleted all the `

` lines. 613 | 614 | // TODO: Delete multiple lines using `dd` and repeat with `.`. 615 | 616 | function App() { 617 | return ( 618 |

619 |

Line 1

620 |

Line 2

621 |

Line 3

622 |

Line 4

623 |

Line 5

624 |
625 | ); 626 | } 627 | 628 | // Expected result: 629 | // (All `

` lines are deleted) 630 | // function App() { 631 | // return ( 632 | //

633 | //
634 | // ); 635 | // } 636 | 637 | // Now, to delete and change a line. 638 | 639 | // Steps: 640 | // 1. Place your cursor on the `
` line. 641 | // 2. Press `S` (capital S) to delete the line and enter insert mode at the start. 642 | // 3. Type `
`, fill the tag with "New", and press `Esc` to exit insert mode. 643 | // Reminder, you could also do `cit` if you wanted to keep the `div`, but just replace the content. 644 | 645 | // This is more efficient than doing `dd` followed by `O`. 646 | 647 | // TODO: Replace the `
` tag with `
`. 648 | 649 | function App() { 650 | return ( 651 |
Delete Me
652 | ); 653 | } 654 | 655 | // Expected result: 656 | // function App() { 657 | // return ( 658 | //
New
659 | // ); 660 | // } 661 | 662 | // --- End of Deleting Lines Lesson --- 663 | 664 | // NOTE: ✦ Lesson: Find and Replace 665 | 666 | // Let's perform a find and replace operation. 667 | 668 | // Steps: 669 | // 1. Press `:` to enter command mode. 670 | // 2. Type `:%s/unique/common/gc` and press `Enter`. 671 | // 3. Press `y` or `n` to confirm replacing found matches. 672 | 673 | // This command means: 674 | 675 | // - `%`: Apply to all lines in the file. 676 | // - `s`: Substitute. 677 | // - `unique`: The search pattern. 678 | // - `common`: The replacement string. 679 | // - `g`: Global (replace all occurrences in each line). 680 | // - `c`: Prompt for confirmation on each change. 681 | 682 | // TODO: Only replace instances of `unique` with `common` inside the component. 683 | 684 | function App() { 685 | return ( 686 |
687 |

unique

688 |

unique

689 |
690 | ); 691 | } 692 | 693 | // Then, hit `u` to undo these changes. 694 | 695 | // --- End of Find and Replace Lesson --- 696 | 697 | // NOTE: ✦ Lesson: Visual Mode 698 | 699 | // It's helpful to learn different ways to visually select text. Once text 700 | // is selected, you can then copy it (yank `y`), delete it (`d`), and more. 701 | 702 | // Steps: 703 | // 1. Place your cursor on the `return` line. 704 | // 2. Type `vi(` (or `vab`) to select all content inside parenthesis. 705 | // 3. You currently have the exact text selected (try `c` and see where you cursor lands) 706 | // 4. `Esc`, undo (`u`) the last change, and re-select with `vi(`. 707 | // 5. Type `va(` to select all content around the parenthesis. 708 | // 6. Type 'V' to change to line selection. Now `return` is also selected. 709 | // 7. Type `d` to delete and cut the entire `return` statement. 710 | 711 | // TODO: Try selecting and modifying the text below. 712 | 713 | function App() { 714 | return ( 715 |
716 |

Select Me

717 |
718 | ); 719 | } 720 | 721 | // Expected result: 722 | // function App() { 723 | // } 724 | 725 | // When you have content selected, you can also use `>` to indent to the right, 726 | // and `<` to shift content back left. 727 | 728 | // --- End of Course --- 729 | 730 | // NOTE: Congratulations! You've completed the course. 731 | 732 | // Now, to make this stick, start building using Vim and reference the cheat sheet. 733 | // You will make mistakes — this is okay! If you use the wrong command or mess up, 734 | // stop, undo (`u`), and try again. Try moving slower and think carefully 735 | // about the sequence of commands you'll use before you start typing. Enjoy! 736 | 737 | // Cheat sheet: https://github.com/leerob/vim-for-react-devs 738 | 739 | // NOTE: ✦ Recommended Neovim Plugins 740 | 741 | // Here are the plugins that I recommend for React and TypeScript development: 742 | 743 | // Included in the Neovim Kickstart: 744 | // - Telescope (find and open files, search across your directory) 745 | // - nvim-autopairs (Automatically close characters like `(`, `{`, `[`, `'`, `<`, and more) 746 | // - mini.ai (despite the name, not related to AI – this enables `dit`, `ciq`, and more) 747 | 748 | // Other Recommended Plugins 749 | // - neo-tree (Press `\` to toggle file system GUI, similar to VSCode) 750 | // - This is in the Kickstart, but you need to uncomment it 751 | // - lazygit (easy GUI for git commands) 752 | // - macOS: brew install lazygit 753 | // - Windows: choco install -y lazygit 754 | // - lazygit.nvim (open lazygit directly from Neovim with lg) 755 | // - defaults to `space` with Quickstart 756 | // - https://github.com/kdheepak/lazygit.nvim 757 | // - Avante (AI-driven code suggestions, similar to Cursor) 758 | // - https://github.com/yetone/avante.nvim 759 | // - nvim-ts-autotag (automatically close HTML tags, even from JSX) 760 | // - https://github.com/windwp/nvim-ts-autotag 761 | // - alpha-nvim (nice startup screen which shows recently used files) 762 | // - https://github.com/goolord/alpha-nvim 763 | // - inc-rename (rename all instances of a variable in the file) 764 | // - With a keymap, this can be faster than :%s/ 765 | // - https://github.com/smjonas/inc-rename.nvim 766 | // - mini.comment (`gc` to comment selection, `gcc` to comment line) 767 | // - https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-comment.md 768 | // - Paired with https://github.com/JoosepAlviste/nvim-ts-context-commentstring/wiki/Integrations#minicomment 769 | // - mini.move (select blocks of text and move them up/down with `j`/`k`) 770 | // - https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-move.md 771 | 772 | // NOTE: ✦ Neovim > VS Code 773 | 774 | // If you've made it this far, why not try to make Neovim your default editor? 775 | // You might be wondering, can I do everything in Neovim that I could in VS Code? 776 | // 777 | // - Jumping into function definitions (e.g. read the source for `useState`) — yes! 778 | // - `gd` allows you to "Go To Definition" 779 | // - Fuzzy find search for filenames (e.g. search `page` for `app/page.tsx`) — yes! 780 | // - Kickstart includes Telescope for this (this is also why we installed `fzf` and `ripgrep`) 781 | // - sf – Search Files (I remapped to , i.e. Cmd+P, to match VS Code) 782 | // - sg - Search across directory 783 | // - Quickly navigate to errors — yes! 784 | // - Neovim calls these errors "diagnostics" 785 | // - Again, Telescope makes this _really_ easy 786 | // - sd - "Search Diagnostics" 787 | // - While in a file, you can move between diagnostics with `[d` and `]d` 788 | // - This is helpful to quick move through TypeScript errors, for example 789 | // - Extensions — yes! 790 | // - Many of the extensions you love in VS Code are "Language Server Protocol" integrations 791 | // - Neovim can integrate with any LSP you need, e.g. TypeScript, Tailwind CSS, and more 792 | // - AI Workflows — yes! 793 | // - Check out the avante.nvim plugin above 794 | // - Navigate forward and backward between files — yes! 795 | // - `Ctrl + o`: Back 796 | // - `Ctrl + i`: Forward 797 | 798 | // NOTE: Additional Learning Resources 799 | 800 | // - Vim Tutor (run `:Tutor` inside Neovim) 801 | // - Understand Kickstart: https://www.youtube.com/watch?v=m8C0Cq9Uv9o 802 | // - Understand Neovim Configs: https://www.youtube.com/playlist?list=PLsz00TDipIffreIaUNk64KxTIkQaGguqn 803 | 804 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "noEmit": true, 13 | "esModuleInterop": true, 14 | "module": "esnext", 15 | "moduleResolution": "bundler", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "jsx": "preserve", 19 | "incremental": true, 20 | "plugins": [ 21 | { 22 | "name": "next" 23 | } 24 | ], 25 | "paths": { 26 | "@/*": [ 27 | "./*" 28 | ] 29 | } 30 | }, 31 | "include": [ 32 | "next-env.d.ts", 33 | "**/*.ts", 34 | "**/*.tsx", 35 | ".next/types/**/*.ts" 36 | ], 37 | "exclude": [ 38 | "node_modules", 39 | "public" 40 | ] 41 | } 42 | --------------------------------------------------------------------------------