├── vite.config.js
├── src
├── App.jsx
├── main.jsx
├── ThemeToggle.jsx
├── SearchForm.jsx
├── context.jsx
├── Gallery.jsx
├── assets
│ └── react.svg
└── index.css
├── .gitignore
├── index.html
├── package.json
├── public
└── vite.svg
└── README.md
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import Gallery from "./Gallery";
2 | import SearchForm from "./SearchForm";
3 | import ThemeToggle from "./ThemeToggle";
4 | const App = () => {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 | );
12 | };
13 | export default App;
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 | .env
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Unsplash Images Starter
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App";
4 | import "./index.css";
5 | import { AppProvider } from "./context";
6 | import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
7 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
8 |
9 | const queryCLient = new QueryClient();
10 |
11 | ReactDOM.createRoot(document.getElementById("root")).render(
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 |
--------------------------------------------------------------------------------
/src/ThemeToggle.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { BsFillSunFill, BsFillMoonFill } from "react-icons/bs";
3 | import { useGLobalContext } from "./context";
4 |
5 | const ThemeToggle = () => {
6 | const { isDarkTheme, toggleDarkTheme } = useGLobalContext();
7 | return (
8 |
9 |
16 |
17 | );
18 | };
19 |
20 | export default ThemeToggle;
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stock-images",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@tanstack/react-query": "^4.28.0",
13 | "@tanstack/react-query-devtools": "^4.36.1",
14 | "axios": "^1.3.4",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0",
17 | "react-icons": "^4.8.0"
18 | },
19 | "devDependencies": {
20 | "@types/react": "^18.0.28",
21 | "@types/react-dom": "^18.0.11",
22 | "@vitejs/plugin-react": "^3.1.0",
23 | "vite": "^4.2.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/SearchForm.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useGLobalContext } from "./context";
3 |
4 | const SearchForm = () => {
5 | const { setSearchTerm } = useGLobalContext();
6 | const handleSubmit = (e) => {
7 | e.preventDefault();
8 | const searchValue = e.target.elements.search.value;
9 | if (!searchValue) return;
10 | setSearchTerm(searchValue);
11 | e.target.elements.search.value = "";
12 | };
13 |
14 | return (
15 |
16 | unsplash images
17 |
28 |
29 | );
30 | };
31 |
32 | export default SearchForm;
33 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/context.jsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useState, useEffect } from "react";
2 |
3 | const AppContext = createContext();
4 |
5 | export const AppProvider = ({ children }) => {
6 | // Retrieve the theme from local storage or default to light theme
7 | const storedDarkTheme = localStorage.getItem("darkTheme");
8 | const [isDarkTheme, setIsDarkTheme] = useState(
9 | storedDarkTheme ? JSON.parse(storedDarkTheme) : false
10 | );
11 | const [searchTerm, setSearchTerm] = useState("cat");
12 |
13 | // Apply the theme on initial mount
14 | useEffect(() => {
15 | const body = document.querySelector("body");
16 | if (isDarkTheme) {
17 | body.classList.add("dark-theme");
18 | } else {
19 | body.classList.remove("dark-theme");
20 | }
21 | }, [isDarkTheme]);
22 |
23 | const toggleDarkTheme = () => {
24 | const newDarkTheme = !isDarkTheme;
25 | setIsDarkTheme(newDarkTheme);
26 |
27 | const body = document.querySelector("body");
28 | if (newDarkTheme) {
29 | body.classList.add("dark-theme");
30 | localStorage.setItem("darkTheme", true);
31 | } else {
32 | body.classList.remove("dark-theme");
33 | localStorage.setItem("darkTheme", false);
34 | }
35 | };
36 |
37 | return (
38 |
41 | {children}
42 |
43 | );
44 | };
45 |
46 | export const useGLobalContext = () => useContext(AppContext);
47 |
--------------------------------------------------------------------------------
/src/Gallery.jsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import axios from "axios";
3 | import { useGLobalContext } from "./context";
4 |
5 | const url = `https://api.unsplash.com/search/photos?client_id=${
6 | import.meta.env.VITE_API_KEY
7 | }`;
8 |
9 | const Gallery = () => {
10 | const { searchTerm } = useGLobalContext();
11 | const response = useQuery({
12 | queryKey: ["images", searchTerm],
13 | queryFn: async () => {
14 | const { data } = await axios.get(`${url}&query=${searchTerm}`);
15 | return data;
16 | },
17 | });
18 |
19 | if (response.isLoading) {
20 | return (
21 |
24 | );
25 | }
26 | if (response.isError) {
27 | return (
28 |
29 | There was an error...
30 |
31 | );
32 | }
33 |
34 | const results = response.data.results;
35 | if (results.length < 1) {
36 | return (
37 |
38 | No results found...
39 |
40 | );
41 | }
42 |
43 | return (
44 |
45 | {results.map((item) => {
46 | const url = item?.urls?.regular;
47 | return (
48 |
54 | );
55 | })}
56 |
57 | );
58 | };
59 | export default Gallery;
60 |
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | /* ============= GLOBAL CSS =============== */
2 |
3 | *,
4 | ::after,
5 | ::before {
6 | margin: 0;
7 | padding: 0;
8 | box-sizing: border-box;
9 | }
10 |
11 | html {
12 | font-size: 100%;
13 | } /*16px*/
14 |
15 | :root {
16 | /* colors */
17 | --primary-100: #e2e0ff;
18 | --primary-200: #c1beff;
19 | --primary-300: #a29dff;
20 | --primary-400: #837dff;
21 | --primary-500: #645cff;
22 | --primary-600: #504acc;
23 | --primary-700: #3c3799;
24 | --primary-800: #282566;
25 | --primary-900: #141233;
26 |
27 | /* grey */
28 | --grey-50: #f8fafc;
29 | --grey-100: #f1f5f9;
30 | --grey-200: #e2e8f0;
31 | --grey-300: #cbd5e1;
32 | --grey-400: #94a3b8;
33 | --grey-500: #64748b;
34 | --grey-600: #475569;
35 | --grey-700: #334155;
36 | --grey-800: #1e293b;
37 | --grey-900: #0f172a;
38 | /* rest of the colors */
39 | --black: #222;
40 | --white: #fff;
41 | --red-light: #f8d7da;
42 | --red-dark: #842029;
43 | --green-light: #d1e7dd;
44 | --green-dark: #0f5132;
45 |
46 | --small-text: 0.875rem;
47 | --extra-small-text: 0.7em;
48 | /* rest of the vars */
49 | --borderRadius: 0.25rem;
50 | --letterSpacing: 1px;
51 | --transition: 0.3s ease-in-out all;
52 | --max-width: 1120px;
53 | --fixed-width: 600px;
54 | --view-width: 90vw;
55 | /* box shadow*/
56 | --shadow-1: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
57 | --shadow-2: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
58 | 0 2px 4px -1px rgba(0, 0, 0, 0.06);
59 | --shadow-3: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
60 | 0 4px 6px -2px rgba(0, 0, 0, 0.05);
61 | --shadow-4: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
62 | 0 10px 10px -5px rgba(0, 0, 0, 0.04);
63 | /* dark mode setup */
64 | --dark-mode-bg-color: #333;
65 | --dark-mode-text-color: #f0f0f0;
66 | --backgroundColor: var(--grey-50);
67 | --textcolor: var(--grey-900);
68 | --darkModeTransition: color 0.3s ease-in-out,
69 | background-color 0.3s ease-in-out;
70 | }
71 | .dark-theme {
72 | --textColor: var(--dark-mode-text-color);
73 | --backgroundColor: var(--dark-mode-bg-color);
74 | }
75 |
76 | body {
77 | background-color: var(--backgroundColor);
78 | color: var(--textColor);
79 | transition: var(--darkModeTransition);
80 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
81 | Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
82 | font-weight: 400;
83 | line-height: 1;
84 | }
85 | p {
86 | margin: 0;
87 | }
88 | h1,
89 | h2,
90 | h3,
91 | h4,
92 | h5 {
93 | margin: 0;
94 | font-family: var(--headingFont);
95 | font-weight: 400;
96 | line-height: 1;
97 | text-transform: capitalize;
98 | letter-spacing: var(--letterSpacing);
99 | }
100 |
101 | h1 {
102 | font-size: clamp(2rem, 5vw, 5rem); /* Large heading */
103 | }
104 |
105 | h2 {
106 | font-size: clamp(1.5rem, 3vw, 3rem); /* Medium heading */
107 | }
108 |
109 | h3 {
110 | font-size: clamp(1.25rem, 2.5vw, 2.5rem); /* Small heading */
111 | }
112 |
113 | h4 {
114 | font-size: clamp(1rem, 2vw, 2rem); /* Extra small heading */
115 | }
116 |
117 | h5 {
118 | font-size: clamp(0.875rem, 1.5vw, 1.5rem); /* Tiny heading */
119 | }
120 |
121 | /* BIGGER FONTS */
122 | /* h1 {
123 | font-size: clamp(3rem, 6vw, 6rem);
124 | }
125 |
126 | h2 {
127 | font-size: clamp(2.5rem, 5vw, 5rem);
128 | }
129 |
130 | h3 {
131 | font-size: clamp(2rem, 4vw, 4rem);
132 | }
133 |
134 | h4 {
135 | font-size: clamp(1.5rem, 3vw, 3rem);
136 | }
137 |
138 | h5 {
139 | font-size: clamp(1rem, 2vw, 2rem);
140 | }
141 | */
142 |
143 | .text {
144 | margin-bottom: 1.5rem;
145 | max-width: 40em;
146 | }
147 |
148 | small,
149 | .text-small {
150 | font-size: var(--small-text);
151 | }
152 |
153 | a {
154 | text-decoration: none;
155 | }
156 | ul {
157 | list-style-type: none;
158 | padding: 0;
159 | }
160 |
161 | .img {
162 | width: 100%;
163 | display: block;
164 | object-fit: cover;
165 | }
166 | /* buttons */
167 |
168 | .btn {
169 | cursor: pointer;
170 | color: var(--white);
171 | background: var(--primary-500);
172 | border: transparent;
173 | border-radius: var(--borderRadius);
174 | letter-spacing: var(--letterSpacing);
175 | padding: 0.375rem 0.75rem;
176 | box-shadow: var(--shadow-1);
177 | transition: var(--transition);
178 | text-transform: capitalize;
179 | display: inline-block;
180 | }
181 | .btn:hover {
182 | background: var(--primary-700);
183 | box-shadow: var(--shadow-3);
184 | }
185 | .btn-hipster {
186 | color: var(--primary-500);
187 | background: var(--primary-200);
188 | }
189 | .btn-hipster:hover {
190 | color: var(--primary-200);
191 | background: var(--primary-700);
192 | }
193 | .btn-block {
194 | width: 100%;
195 | }
196 |
197 | /* alerts */
198 | .alert {
199 | padding: 0.375rem 0.75rem;
200 | margin-bottom: 1rem;
201 | border-color: transparent;
202 | border-radius: var(--borderRadius);
203 | }
204 |
205 | .alert-danger {
206 | color: var(--red-dark);
207 | background: var(--red-light);
208 | }
209 | .alert-success {
210 | color: var(--green-dark);
211 | background: var(--green-light);
212 | }
213 | /* form */
214 |
215 | .form {
216 | width: 90vw;
217 | max-width: var(--fixed-width);
218 | background: var(--white);
219 | border-radius: var(--borderRadius);
220 | box-shadow: var(--shadow-2);
221 | padding: 2rem 2.5rem;
222 | margin: 3rem auto;
223 | }
224 | .form-label {
225 | display: block;
226 | font-size: var(--small-text);
227 | margin-bottom: 0.5rem;
228 | text-transform: capitalize;
229 | letter-spacing: var(--letterSpacing);
230 | }
231 | .form-input,
232 | .form-textarea {
233 | width: 100%;
234 | padding: 0.375rem 0.75rem;
235 | border-radius: var(--borderRadius);
236 | background: var(--backgroundColor);
237 | border: 1px solid var(--grey-200);
238 | }
239 |
240 | .form-row {
241 | margin-bottom: 1rem;
242 | }
243 |
244 | .form-textarea {
245 | height: 7rem;
246 | }
247 | ::placeholder {
248 | font-family: inherit;
249 | color: var(--grey-400);
250 | }
251 | .form-alert {
252 | color: var(--red-dark);
253 | letter-spacing: var(--letterSpacing);
254 | text-transform: capitalize;
255 | }
256 | /* alert */
257 |
258 | @keyframes spinner {
259 | to {
260 | transform: rotate(360deg);
261 | }
262 | }
263 |
264 | .loading {
265 | width: 6rem;
266 | height: 6rem;
267 | border: 5px solid var(--grey-400);
268 | border-radius: 50%;
269 | border-top-color: var(--primary-500);
270 | animation: spinner 0.6s linear infinite;
271 | margin: 0 auto;
272 | }
273 |
274 | /* title */
275 |
276 | .title {
277 | text-align: center;
278 | }
279 |
280 | .title-underline {
281 | background: var(--primary-500);
282 | width: 7rem;
283 | height: 0.25rem;
284 | margin: 0 auto;
285 | margin-top: 1rem;
286 | }
287 |
288 | /*
289 | ========
290 | PROJECT CSS
291 | ========
292 | */
293 |
294 | /*
295 | ========
296 | Toggle Container
297 | ========
298 | */
299 |
300 | .toggle-container {
301 | width: var(--view-width);
302 | max-width: var(--max-width);
303 | padding: 1rem 0;
304 | margin: 0 auto;
305 | display: flex;
306 | justify-content: end;
307 | }
308 |
309 | .dark-toggle {
310 | background: transparent;
311 | border-color: transparent;
312 | width: 5rem;
313 | height: 2rem;
314 | display: grid;
315 | place-items: center;
316 | cursor: pointer;
317 | }
318 | .toggle-icon {
319 | font-size: 1.5rem;
320 | color: var(--textColor);
321 | }
322 | /*
323 | ========
324 | Search Form
325 | ========
326 | */
327 | .title {
328 | color: var(--primary-500);
329 | }
330 | .search-form {
331 | width: var(--view-width);
332 | max-width: var(--fixed-width);
333 | margin: 0 auto;
334 | margin-top: 2rem;
335 | display: grid;
336 | grid-template-columns: 1fr auto;
337 | }
338 |
339 | .search-input {
340 | border-color: var(--grey-300);
341 | transition: var(--darkModeTransition);
342 | color: var(--textColor);
343 | border-radius: 0;
344 |
345 | /* border-right: none; */
346 | }
347 |
348 | .search-form .btn {
349 | border-radius: 0;
350 | }
351 | .search-form .btn:hover {
352 | background: var(--primary-300);
353 | color: var(--black);
354 | }
355 |
356 | /*
357 | ========
358 | Gallery
359 | ========
360 | */
361 |
362 | .image-container {
363 | width: var(--view-width);
364 | max-width: var(--max-width);
365 | margin: 3rem auto;
366 | display: grid;
367 | gap: 2rem;
368 | }
369 |
370 | .image-container .img {
371 | height: 10rem;
372 | }
373 |
374 | @media (min-width: 768px) {
375 | .image-container {
376 | grid-template-columns: 1fr 1fr;
377 | }
378 | }
379 | @media (min-width: 992px) {
380 | .image-container {
381 | grid-template-columns: 1fr 1fr 1fr;
382 | }
383 | }
384 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Figma URL
2 |
3 | [Unsplash Images](https://www.figma.com/file/O2MaAAlr4nznh7m53azatL/Unsplash-images?node-id=0%3A1&t=cYDOCgqOs9IX2If0-1)
4 |
5 | ## Steps
6 |
7 | #### Setup
8 |
9 | - npm install
10 | - npm run dev
11 |
12 | #### Initial Structure and Global Context
13 |
14 | Create three components - ThemeToggle, SearchForm and Gallery. Render all of them in App.jsx, and setup global context.
15 |
16 | #### Dark Theme - Initial Setup
17 |
18 | In the context of creating a state value called 'isDarkTheme' (boolean) and a helper function called 'toggleDarkTheme', set 'isDarkTheme' to the opposite value when 'toggleDarkTheme' is invoked. Pass 'isDarkTheme' and 'toggleDarkTheme' down to 'ThemeToggle'. In 'ThemeToggle', import two icons from the React Icons library (moon and sun) and create a button. When the button is clicked, invoke 'toggleDarkTheme', and display the appropriate icon based on the value of 'isDarkTheme' inside of the button.
19 |
20 | #### Dark Theme Class
21 |
22 | In the toggleDarkTheme add logic to add and remove .dark-theme class to body element based on isDarkTheme value.
23 |
24 | #### Dark Theme - CSS
25 |
26 | Create CSS variables for the background color and text color for both dark mode and normal mode, as well as a CSS variable for the dark mode transition.
27 |
28 | ```css
29 | :root {
30 | /* dark mode setup */
31 | --dark-mode-bg-color: #333;
32 | --dark-mode-text-color: #f0f0f0;
33 | --backgroundColor: var(--grey-50);
34 | --textColor: var(--grey-900);
35 |
36 | --darkModeTransition: color 0.3s ease-in-out, background-color 0.3s
37 | ease-in-out;
38 | }
39 |
40 | .dark-theme {
41 | --textColor: var(--dark-mode-text-color);
42 | --backgroundColor: var(--dark-mode-bg-color);
43 | }
44 |
45 | body {
46 | transition: var(--darkModeTransition);
47 | background: var(--backgroundColor);
48 | color: var(--textColor);
49 | }
50 | ```
51 |
52 | #### User Prefers Dark Mode
53 |
54 | ```css
55 | @media (prefers-color-scheme: dark) {
56 | :root {
57 | --textColor: var(--dark-mode-text-color);
58 | --backgroundColor: var(--dark-mode-bg-color);
59 | }
60 | }
61 | ```
62 |
63 | #### SearchForm Structure
64 |
65 | Create a form with an input and a submit button. The input should have the following attributes: type='text', name='search', placeholder='cat', and className='form-input search-input'. When the user submits the form, access (for now log)the input value.
66 |
67 | #### Unsplash Info
68 |
69 | Unsplash is a website that provides a large collection of high-quality stock photos that are free to use. The Unsplash API is a service that allows developers to access and use Unsplash's collection of photos and related data in their own applications. The API allows developers to search, download, and use the photos in a variety of ways, such as creating photo galleries or integrating them into social media applications. The Unsplash API is widely used by developers to enhance the visual content of their applications or websites.
70 |
71 | [Unsplash Website](https://unsplash.com/)
72 |
73 | #### Sign Up for an Unsplash Account
74 |
75 | [Unsplash API](https://unsplash.com/developers)
76 |
77 | In order to use the Unsplash API to fetch images for your application, you will need to sign up for an account with Unsplash. This will allow you to obtain an API key that you can use to authenticate your requests.
78 |
79 | #### Find the API Key and Correct URL for Searching Images
80 |
81 | - register an app
82 | - authorization (hint : public authentication)
83 | - search functionality (hint : search photos)
84 |
85 | After signing up for an Unsplash account, you will need to locate your API key and the correct URL to use when searching for images using the Unsplash API. This information can be found in the API documentation provided by Unsplash.
86 |
87 | #### Test the URL Using Thunder Client VS Code Extension
88 |
89 | Before implementing the API in your application, it is a good practice to test the URL using a tool like Thunder Client VS Code Extension. This will allow you to verify that the URL is correct and that you are able to successfully retrieve images using the API.
90 |
91 | #### Install and Setup React Query in Gallery Component
92 |
93 | React Query is a library that can be used to handle API requests in React applications. To fetch images from the Unsplash API, you will need to install and set up React Query in your Gallery component.
94 |
95 | #### Install and Setup React Query Dev Tools
96 |
97 | React Query dev tools provide a way to inspect and debug React Query data and caching behavior. To use this tool, you will need to install and set up the React Query dev tools in your application.
98 |
99 | #### Create a searchValue State Value in Context.jsx
100 |
101 | In order to implement search functionality in your application, you will need to create a state value to store the user's search input. This can be done in a context file, such as context.jsx.
102 |
103 | #### Fix the useQuery
104 |
105 | After setting up React Query and creating the searchValue state value, you will need to modify the useQuery function to fetch images based on the user's search input.
106 |
107 | #### Check Whether User Prefers Dark Mode with JavaScript
108 |
109 | To provide a better user experience, you can check whether the user prefers dark mode using JavaScript. This can be done by accessing the user's system preferences and setting the theme of your application accordingly.
110 |
111 | #### Setup Local Storage to Store isDarkTheme State Value
112 |
113 | To persist the user's preferred theme across sessions, you can store the isDarkTheme state value in local storage. This will allow the theme to be preserved even if the user closes and reopens the application.
114 |
115 | #### Setup ENV Variables in VITE
116 |
117 | Environment variables can be used to store sensitive information, such as your Unsplash API key. In order to use environment variables in your application, you will need to set them up in VITE, a build tool for modern web development.
118 |
119 | #### Deploy the Application to Netlify and Setup ENV Variables
120 |
121 | Once your application is complete, you can deploy it to a hosting platform such as Netlify. When deploying to Netlify, you will need to set up your environment variables to ensure that your application can access your Unsplash API key.
122 |
123 | #### Add CSS
124 |
125 | Finally, you can add CSS to your application to style the components and provide a polished user interface.
126 |
127 | ## Additional Info
128 |
129 | #### Dark Theme Class - Code
130 |
131 | ```js
132 | const body = document.querySelector('body');
133 | body.classList.toggle('dark-theme', newDarkTheme);
134 |
135 | // alternative setup
136 | document.body.classList.toggle('dark-theme', newDarkTheme);
137 | ```
138 |
139 | const body = document.querySelector('body'); - This line selects the body element of the current document using the document.querySelector() method, which returns the first element that matches the specified selector. In this case, it is selecting the body element.
140 |
141 | body.classList.toggle('dark-theme', isDarkTheme); - This line toggles the dark-theme class of the body element. The classList property is a read-only list that contains the classes of an element. The toggle() method adds a class to the element if it does not have it, or removes it if it does. In this case, it adds the dark-theme class if isDarkTheme is true, and removes it if isDarkTheme is false.
142 |
143 | #### Elements Property
144 |
145 | The elements property of the event.target object in the handleSubmit function refers to an HTMLCollection containing all the form controls (i.e., input, select, textarea, button, etc.) inside the