├── .distignore
├── .editorconfig
├── .gitattributes
├── .github
└── workflows
│ └── create-release-and-deploy.yml
├── .gitignore
├── .wordpress-org
├── banner-1544x500.png
├── banner-772x250.png
├── icon-128x128.png
└── icon-256x256.png
├── README.md
├── dark-mode-toggle-block.php
├── package-lock.json
├── package.json
├── readme.txt
└── src
├── block.json
├── deprecated.js
├── edit.js
├── icons.js
├── icons
├── index.js
└── library
│ ├── circle-dark.js
│ ├── circle-light.js
│ ├── eye-dark.js
│ ├── eye-light.js
│ ├── filled-dark.js
│ ├── filled-light.js
│ ├── stroke-dark.js
│ └── stroke-light.js
├── index.js
├── save.js
├── style.scss
└── view.js
/.distignore:
--------------------------------------------------------------------------------
1 | # Directories
2 | /.git
3 | /.github
4 | /.wordpress-org
5 | /node_modules
6 | /src
7 |
8 | # Files
9 | *.log
10 | *.swp
11 | *.tar.gz
12 | *.zip
13 | .DS_Store
14 | .distignore
15 | .editorconfig
16 | .eslintignore
17 | .eslintrc.js
18 | .eslintrc.json
19 | .git
20 | .gitattributes
21 | .gitignore
22 | .npmignore
23 | .prettierrc.js
24 | .stylelintignore
25 | .stylelintrc.json
26 | LICENSE.md
27 | behat.yml
28 | circle.yml
29 | composer.json
30 | composer.lock
31 | package-lock.json
32 | package.json
33 | phpcs.xml.dist
34 | webpack.config.js
35 | README.md
36 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 |
4 | # WordPress Coding Standards
5 | # https://make.wordpress.org/core/handbook/coding-standards/
6 |
7 | root = true
8 |
9 | [*]
10 | charset = utf-8
11 | end_of_line = lf
12 | insert_final_newline = true
13 | trim_trailing_whitespace = true
14 | indent_style = tab
15 |
16 | [*.yml]
17 | indent_style = space
18 | indent_size = 2
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
22 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/workflows/create-release-and-deploy.yml:
--------------------------------------------------------------------------------
1 | name: Create Release and Deploy to WordPress.org
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | release-and-deploy:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v2
13 |
14 | - name: Set up Node.js
15 | uses: actions/setup-node@v2
16 | with:
17 | node-version: '20.11.0'
18 |
19 | - name: Install dependencies
20 | run: npm install
21 |
22 | - name: Build plugin zip
23 | run: npm run plugin-zip
24 |
25 | - name: Create Release
26 | id: create_release
27 | uses: actions/create-release@v1
28 | env:
29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30 | with:
31 | tag_name: ${{ github.ref }}
32 | release_name: ${{ github.ref }}
33 | draft: false
34 | prerelease: false
35 |
36 | - name: Upload Release Asset
37 | uses: actions/upload-release-asset@v1
38 | env:
39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40 | with:
41 | upload_url: ${{ steps.create_release.outputs.upload_url }}
42 | asset_path: ./dark-mode-toggle-block.zip
43 | asset_name: dark-mode-toggle-block.zip
44 | asset_content_type: application/zip
45 |
46 | - name: Deploy to WordPress.org
47 | uses: 10up/action-wordpress-plugin-deploy@stable
48 | with:
49 | generate-zip: true
50 | dry-run: false
51 | env:
52 | SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
53 | SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
54 | SLUG: dark-mode-toggle-block
55 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # System
9 | .DS_Store
10 |
11 | # Coverage directory used by tools like istanbul
12 | coverage
13 |
14 | # Compiled binary addons (https://nodejs.org/api/addons.html)
15 | build/
16 |
17 | # Dependency directories
18 | node_modules/
19 |
20 | # Optional npm cache directory
21 | .npm
22 |
23 | # Optional eslint cache
24 | .eslintcache
25 |
26 | # Output of `npm pack`
27 | *.tgz
28 |
29 | # Output of `wp-scripts plugin-zip`
30 | *.zip
31 |
32 | # dotenv environment variables file
33 | .env
34 |
--------------------------------------------------------------------------------
/.wordpress-org/banner-1544x500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/richtabor/dark-mode-toggle-block/6dcb7d1ea18762c65246ec5b05815e031967156b/.wordpress-org/banner-1544x500.png
--------------------------------------------------------------------------------
/.wordpress-org/banner-772x250.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/richtabor/dark-mode-toggle-block/6dcb7d1ea18762c65246ec5b05815e031967156b/.wordpress-org/banner-772x250.png
--------------------------------------------------------------------------------
/.wordpress-org/icon-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/richtabor/dark-mode-toggle-block/6dcb7d1ea18762c65246ec5b05815e031967156b/.wordpress-org/icon-128x128.png
--------------------------------------------------------------------------------
/.wordpress-org/icon-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/richtabor/dark-mode-toggle-block/6dcb7d1ea18762c65246ec5b05815e031967156b/.wordpress-org/icon-256x256.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Dark Mode Toggle Block
2 |
3 | [](https://github.com/richtabor/dark-mode-toggle-block/actions/workflows/create-release-and-deploy.yml)
4 |
5 | A WordPress block to add a toggle between light and dark appearances, as seen on [my blog](https://rich.blog). Adds a `theme-dark` class to the body element, when toggled on. The user's preference is then saved in local storage.
6 |
7 | [Read about it on my blog →](https://rich.blog/dark-mode-toggle-block/)
8 |
9 | ### Visual
10 |
11 | https://github.com/richtabor/dark-mode-toggle-block/assets/1813435/f7255865-6328-4f54-8284-6bb2432d8ab2
12 |
13 | ### How it works
14 | When toggled, the block will add a `.theme-dark` class to the body of the site. You can add CSS variables to target dark styles.
15 |
16 | I did it this way on [my blog](https://rich.blog), which uses the theme.json `settings.custom.color` values for each color, unless there is a color created within the Site Editor with corresponding slug (i.e. `theme-1-dark`). I used this method so that a user could manipulate any given color without having to modify theme.json.
17 |
18 | ```
19 | /* Dark styles */
20 | .theme-dark body {
21 | --wp--preset--color--theme-1: var(--wp--preset--color--custom-theme-1-dark, var(--wp--custom--color--theme-1-dark));
22 | --wp--preset--color--theme-2: var(--wp--preset--color--custom-theme-2-dark, var(--wp--custom--color--theme-2-dark));
23 | --wp--preset--color--theme-3: var(--wp--preset--color--custom-theme-3-dark, var(--wp--custom--color--theme-3-dark));
24 | --wp--preset--color--theme-4: var(--wp--preset--color--custom-theme-4-dark, var(--wp--custom--color--theme-4-dark));
25 | --wp--preset--color--theme-5: var(--wp--preset--color--custom-theme-5-dark, var(--wp--custom--color--theme-5-dark));
26 | --wp--preset--color--theme-6: var(--wp--preset--color--custom-theme-6-dark, var(--wp--custom--color--theme-6-dark));
27 | }
28 | ```
29 |
30 | ### Development
31 |
32 | 1. Clone the repository into your WordPress plugins directory.
33 | 2. Run `npm install` to install dependencies.
34 | 3. Run `npm start` to start the development server.
35 | 4. Activate the plugin on your local WordPress site.
36 | 5. Add the block to a post or page.
37 |
--------------------------------------------------------------------------------
/dark-mode-toggle-block.php:
--------------------------------------------------------------------------------
1 | {
26 | const { className, icon, size } = attributes;
27 | const colorProps = getColorClassesAndStyles( attributes );
28 | const borderProps = getBorderClassesAndStyles( attributes );
29 | const spacingProps = getSpacingClassesAndStyles( attributes );
30 |
31 | // Dynamically determine which icon to use for each style.
32 | const LightIcon = Icons[ icon ]?.light || Icons.filled?.light;
33 | const DarkIcon = Icons[ icon ]?.dark || Icons.filled?.dark;
34 |
35 | const classes = classnames( className, {
36 | [ `is-${ size }` ]: size,
37 | } );
38 |
39 | return (
40 |
41 |
87 |
88 | );
89 | },
90 | },
91 | ];
92 |
93 | export default deprecated;
94 |
--------------------------------------------------------------------------------
/src/edit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import classnames from 'classnames';
5 |
6 | /**
7 | * WordPress dependencies
8 | */
9 | import {
10 | useBlockProps,
11 | InspectorControls,
12 | __experimentalUseColorProps as useColorProps,
13 | __experimentalUseBorderProps as useBorderProps,
14 | __experimentalGetSpacingClassesAndStyles as useSpacingProps,
15 | } from '@wordpress/block-editor';
16 | import {
17 | Disabled,
18 | Icon,
19 | __experimentalToggleGroupControl as ToggleGroupControl,
20 | __experimentalToggleGroupControlOption as ToggleGroupControlOption,
21 | __experimentalToolsPanel as ToolsPanel,
22 | __experimentalToolsPanelItem as ToolsPanelItem,
23 | } from '@wordpress/components';
24 | import { useEffect } from '@wordpress/element';
25 | import { __ } from '@wordpress/i18n';
26 |
27 | /**
28 | * Internal dependencies
29 | */
30 | import { Icons } from './icons';
31 |
32 | /**
33 | * The edit function describes the structure of your block in the context of the
34 | * editor. This represents what the editor will render when the block is used.
35 | *
36 | * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit
37 | */
38 | export default function Edit( { attributes, setAttributes } ) {
39 | const { className, icon, size } = attributes;
40 | const blockProps = useBlockProps( {
41 | className: classnames( {
42 | [ `is-${ size }` ]: size,
43 | } ),
44 | } );
45 | const colorProps = useColorProps( attributes );
46 | const borderProps = useBorderProps( attributes );
47 | const spacingProps = useSpacingProps( attributes );
48 |
49 | // Dynamically determine which icon to use for each style.
50 | const LightIcon = Icons[ icon ]?.light || Icons.filled?.light;
51 | const DarkIcon = Icons[ icon ]?.dark || Icons.filled?.dark;
52 |
53 | useEffect( () => {
54 | // Map class names to icon attribute.
55 | const styleToIcons = {
56 | 'is-style-stroke': 'stroke',
57 | 'is-style-circle': 'circle',
58 | 'is-style-eye': 'eye',
59 | };
60 |
61 | // Find the first matching style and set the corresponding icon.
62 | const iconString = Object.keys( styleToIcons ).find( ( style ) =>
63 | className?.includes( style )
64 | );
65 |
66 | if ( iconString ) {
67 | setAttributes( { icon: styleToIcons[ iconString ] } );
68 | } else {
69 | // Reset or handle the attribute when no styles are matched.
70 | setAttributes( { icon: undefined } );
71 | }
72 | }, [ className, setAttributes ] );
73 |
74 | return (
75 | <>
76 |
77 |
78 | !! size }
82 | onDeselect={ () =>
83 | setAttributes( { size: undefined } )
84 | }
85 | >
86 | {
91 | setAttributes( {
92 | size: value,
93 | } );
94 | } }
95 | isBlock
96 | size={ '__unstable-large' }
97 | __nextHasNoMarginBottom
98 | >
99 |
108 |
117 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
175 |
176 |
177 | >
178 | );
179 | }
180 |
--------------------------------------------------------------------------------
/src/icons.js:
--------------------------------------------------------------------------------
1 | import * as Icon from './icons/index.js';
2 |
3 | export const Icons = {
4 | filled: {
5 | light: Icon.FilledLight,
6 | dark: Icon.FilledDark,
7 | },
8 | stroke: {
9 | light: Icon.StrokeLight,
10 | dark: Icon.StrokeDark,
11 | },
12 | circle: {
13 | light: Icon.CircleLight,
14 | dark: Icon.CircleDark,
15 | },
16 | eye: {
17 | light: Icon.EyeLight,
18 | dark: Icon.EyeDark,
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/src/icons/index.js:
--------------------------------------------------------------------------------
1 | export { default as FilledLight } from './library/filled-light';
2 | export { default as FilledDark } from './library/filled-dark';
3 |
4 | export { default as StrokeLight } from './library/stroke-light';
5 | export { default as StrokeDark } from './library/stroke-dark';
6 |
7 | export { default as CircleLight } from './library/circle-light';
8 | export { default as CircleDark } from './library/circle-dark';
9 |
10 | export { default as EyeLight } from './library/eye-light';
11 | export { default as EyeDark } from './library/eye-dark';
12 |
--------------------------------------------------------------------------------
/src/icons/library/circle-dark.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { Path, SVG } from '@wordpress/components';
5 |
6 | const svg = (
7 |
21 | );
22 |
23 | export default svg;
24 |
--------------------------------------------------------------------------------
/src/icons/library/circle-light.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { Path, SVG } from '@wordpress/components';
5 |
6 | const svg = (
7 |
21 | );
22 |
23 | export default svg;
24 |
--------------------------------------------------------------------------------
/src/icons/library/eye-dark.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { Path, SVG } from '@wordpress/components';
5 |
6 | const svg = (
7 |
19 | );
20 |
21 | export default svg;
22 |
--------------------------------------------------------------------------------
/src/icons/library/eye-light.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { Path, SVG } from '@wordpress/components';
5 |
6 | const svg = (
7 |
21 | );
22 |
23 | export default svg;
24 |
--------------------------------------------------------------------------------
/src/icons/library/filled-dark.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { Path, SVG } from '@wordpress/components';
5 |
6 | const svg = (
7 |
19 | );
20 |
21 | export default svg;
22 |
--------------------------------------------------------------------------------
/src/icons/library/filled-light.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { Path, SVG } from '@wordpress/components';
5 |
6 | const svg = (
7 |
19 | );
20 |
21 | export default svg;
22 |
--------------------------------------------------------------------------------
/src/icons/library/stroke-dark.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { Path, SVG } from '@wordpress/components';
5 |
6 | const svg = (
7 |
21 | );
22 |
23 | export default svg;
24 |
--------------------------------------------------------------------------------
/src/icons/library/stroke-light.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { Path, SVG } from '@wordpress/components';
5 |
6 | const svg = (
7 |
21 | );
22 |
23 | export default svg;
24 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Registers a new block provided a unique name and an object defining its behavior.
3 | *
4 | * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
5 | */
6 | import { registerBlockType } from '@wordpress/blocks';
7 |
8 | /**
9 | * WordPress dependencies
10 | */
11 | import { Path, SVG } from '@wordpress/components';
12 |
13 | /**
14 | * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
15 | * All files containing `style` keyword are bundled together. The code used
16 | * gets applied both to the front of your site and to the editor.
17 | *
18 | * @see https://www.npmjs.com/package/@wordpress/scripts#using-css
19 | */
20 | import './style.scss';
21 |
22 | /**
23 | * Internal dependencies
24 | */
25 | import edit from './edit';
26 | import metadata from './block.json';
27 | import save from './save';
28 | import deprecated from './deprecated';
29 |
30 | /**
31 | * Every block starts by registering a new block type definition.
32 | *
33 | * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/
34 | */
35 | registerBlockType( metadata.name, {
36 | icon: (
37 |
49 | ),
50 | example: {
51 | viewportWidth: 300,
52 | attributes: {
53 | size: 'large',
54 | },
55 | },
56 | deprecated,
57 | edit,
58 | save,
59 | } );
60 |
--------------------------------------------------------------------------------
/src/save.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import classnames from 'classnames';
5 |
6 | /**
7 | * WordPress dependencies
8 | */
9 | import {
10 | useBlockProps,
11 | __experimentalGetBorderClassesAndStyles as getBorderClassesAndStyles,
12 | __experimentalGetColorClassesAndStyles as getColorClassesAndStyles,
13 | __experimentalGetSpacingClassesAndStyles as getSpacingClassesAndStyles,
14 | } from '@wordpress/block-editor';
15 | import { __ } from '@wordpress/i18n';
16 |
17 | /**
18 | * Internal dependencies
19 | */
20 | import { Icons } from './icons';
21 |
22 | export default function Save( { attributes } ) {
23 | const { className, icon, size } = attributes;
24 | const colorProps = getColorClassesAndStyles( attributes );
25 | const borderProps = getBorderClassesAndStyles( attributes );
26 | const spacingProps = getSpacingClassesAndStyles( attributes );
27 |
28 | // Dynamically determine which icon to use for each style.
29 | const LightIcon = Icons[ icon ]?.light || Icons.filled?.light;
30 | const DarkIcon = Icons[ icon ]?.dark || Icons.filled?.dark;
31 |
32 | const classes = classnames( className, {
33 | [ `is-${ size }` ]: size,
34 | } );
35 |
36 | return (
37 |
38 |
83 |
84 | );
85 | }
86 |
--------------------------------------------------------------------------------
/src/style.scss:
--------------------------------------------------------------------------------
1 | .wp-block-tabor-dark-mode-toggle {
2 | --icon-size: 16px;
3 |
4 | &.is-medium {
5 | --icon-size: 18px;
6 | }
7 |
8 | &.is-large {
9 | --icon-size: 20px;
10 | }
11 | }
12 |
13 | .wp-block-tabor-dark-mode-toggle__label {
14 | align-items: center;
15 | display: flex;
16 | margin: 0;
17 | }
18 |
19 | .wp-block-tabor-dark-mode-toggle__input {
20 | clip-path: inset(50%);
21 | clip: rect(0 0 0 0);
22 | height: 1px;
23 | overflow: hidden;
24 | position: absolute;
25 | white-space: nowrap;
26 | width: 1px;
27 | }
28 |
29 | .wp-block-tabor-dark-mode-toggle__track {
30 | align-items: center;
31 | border-radius: 100px;
32 | cursor: pointer;
33 | display: flex;
34 | height: calc(var(--icon-size) * 1.75);
35 | line-height: 1;
36 | padding: 0 calc(var(--icon-size) * 0.25);
37 | position: relative;
38 | transition: background-color var(--wp--custom--transition--duration, 200ms) ease-out;
39 | width: calc(var(--icon-size) * 2.5);
40 |
41 | &:not(.has-background) {
42 | background: rgba(195, 195, 195, 0.3);
43 | }
44 |
45 | &:not(.has-background):hover {
46 | background: rgba(195, 195, 195, 0.5);
47 | }
48 | }
49 |
50 | .wp-block-tabor-dark-mode-toggle__selector {
51 | align-items: center;
52 | border-radius: 100%;
53 | display: flex;
54 | height: calc(var(--icon-size) * 1.25);
55 | justify-content: center;
56 | transition: transform var(--wp--custom--transition--duration, 200ms) ease-out;
57 | width: calc(var(--icon-size) * 1.25);
58 | will-change: transform;
59 | }
60 |
61 | .wp-block-tabor-dark-mode-toggle__icon {
62 | backface-visibility: hidden;
63 | border-radius: 100%;
64 | color: currentcolor;
65 | opacity: 1;
66 | position: absolute;
67 |
68 | &,
69 | svg {
70 | width: var(--icon-size);
71 | height: var(--icon-size);
72 | }
73 | }
74 |
75 | .theme-dark .wp-block-tabor-dark-mode-toggle__input + .wp-block-tabor-dark-mode-toggle__track .wp-block-tabor-dark-mode-toggle__selector {
76 | transform: translateX(calc(var(--icon-size) * 1.25));
77 | }
78 |
79 | .wp-block-tabor-dark-mode-toggle__icon--dark {
80 | visibility: hidden;
81 | }
82 |
83 | .theme-dark {
84 |
85 | .wp-block-tabor-dark-mode-toggle__icon--light {
86 | visibility: hidden;
87 | }
88 |
89 | .wp-block-tabor-dark-mode-toggle__icon--dark {
90 | visibility: visible;
91 | }
92 | }
93 |
94 | // Update to use :focus-visible instead of :focus
95 | .wp-block-tabor-dark-mode-toggle__input:focus-visible + .wp-block-tabor-dark-mode-toggle__track {
96 | outline: -webkit-focus-ring-color auto 1px;
97 | }
98 |
--------------------------------------------------------------------------------
/src/view.js:
--------------------------------------------------------------------------------
1 | import { __ } from '@wordpress/i18n';
2 |
3 | // Cache the document element outside of the function to avoid repeated DOM queries.
4 | const body = document.documentElement;
5 | const darkModeToggles = document.querySelectorAll('.wp-block-tabor-dark-mode-toggle__input');
6 |
7 | function updateToggleState(toggle, isDark) {
8 | toggle.checked = isDark;
9 | toggle.setAttribute('aria-checked', isDark.toString());
10 | toggle.setAttribute(
11 | 'aria-label',
12 | isDark
13 | ? __('Switch to light mode, currently dark', 'dark-mode-toggle-block')
14 | : __('Switch to dark mode, currently light', 'dark-mode-toggle-block')
15 | );
16 | }
17 |
18 | function toggleTheme() {
19 | // Toggle the 'theme-dark' class.
20 | body.classList.toggle('theme-dark');
21 | const isDark = body.classList.contains('theme-dark');
22 |
23 | // Update localStorage based on the presence of the class.
24 | localStorage.setItem('darkMode', isDark ? 'enabled' : 'disabled');
25 |
26 | // Update all toggles' states
27 | darkModeToggles.forEach(toggle => updateToggleState(toggle, isDark));
28 | }
29 |
30 | // Initialize all toggles
31 | darkModeToggles.forEach(toggle => {
32 | // Set initial state
33 | const isDark = body.classList.contains('theme-dark');
34 | updateToggleState(toggle, isDark);
35 |
36 | // Attach event listeners
37 | toggle.addEventListener('click', toggleTheme);
38 | toggle.addEventListener('keydown', (e) => {
39 | if (e.code === 'Space' || e.code === 'Enter') {
40 | e.preventDefault();
41 | toggleTheme();
42 | }
43 | });
44 | });
45 |
--------------------------------------------------------------------------------