
Темная тема стала стандартом де-факто. Ее отсутствие может стать причиной отказа от пользования сайтом. Особенно если на него заходят программисты, которые сплошь и рядом работают в тёмной теме.
Я покажу, как можно просто добавить темную тему в React проект. Разберем основные моменты и сделаем всё красиво. Для тех, кто хочет все сразу:
— [Репозиторий](https://github.com/walborn/with-dark-theme)
— [Демо](https://with-dark-theme-4ypv8vyi9-walborn.vercel.app/)
Roadmap
Шаги, которые мы проделаем дальше:
1. Создадим create-react-app проект
2. Добавим контекст, в котором будем хранить текущую тему
3. Напишем переключатель для изменения темы
4. Объявим переменные для каждой темы, которые будут влиять на стили компонентов
Подготовка
1. С помощью cra создаем проект и сразу добавляем sass для удобства работы со стилями
> npx create-react-app with-dark-theme > cd with-dark-theme > npm i sass -S
2. Удалим ненужные файлы
> cd src > rm App.css App.js App.test.js index.css logo.svg
3. Создадим удобную структур
# внутри src/ > mkdir -p components/{Root,Toggle} contexts providers > touch index.scss components/Root/index.js components/Toggle/{index.js,index.module.scss} contexts/ThemeContext.js providers/ThemeProvider.js
Должна получиться такая структура внутри src/
src ├── components │ ├── Root │ │ └── index.js │ └── Toggle │ ├── index.js │ └── index.module.scss ├── contexts │ └── ThemeContext.js ├── providers │ └── ThemeProvider.js ├── index.js ├── index.scss └── ...
Поскольку мы внесли изменения в структуру, то немного изменим index.js
// src/index.js import React from 'react' import ReactDOM from 'react-dom' import reportWebVitals from './reportWebVitals' // теперь корневой компонент у нас не App, а Root import Root from './components/Root' // поменяли css на scss import './index.scss' ReactDOM.render( <React.StrictMode> <Root /> </React.StrictMode>, document.getElementById('root') )
// src/components/Root/index.js import React from 'react' const Root = () => ( <div>There are will be Dark Theme</div> ) export default Root
Проект уже запускается, но никакой темной темы пока еще нет.
Давайте добавим ее!
Добавляем контекст
Наполним кодом наши файлы ThemeContext.js и ThemeProvider.js
Сначала создадим контекст
// src/components/Root/index.js import React from 'react' const Root = () => ( <div>There are will be Dark Theme</div> ) export default Root
А теперь создадим проводник нашего контекста, в котором сначала получим текущее значение темы, которая хранится в localStorage . Если там еще ничего нет, то берем значение из системной темы. Если и этого нет (привет из виндовс xp) — то устанавливаем темную тему (А что?! Можем себе позволить)
При изменении темы — одновременно сохраняем ее в localStorage
// src/providers/ThemeProvider.js import React from 'react' import { ThemeContext, themes } from 'src/contexts/ThemeContext' const getTheme = () => { const theme = `${window?.localStorage?.getItem('theme')}` if (Object.values(themes).includes(theme)) return theme const userMedia = window.matchMedia('(prefers-color-scheme: light)') if (userMedia.matches) return themes.light return themes.dark } const ThemeProvider = ({ children }) => { const [ theme, setTheme ] = React.useState(getTheme) React.useEffect(() => { document.documentElement.dataset.theme = theme localStorage.setItem('theme', theme) }, [ theme ]) return ( <ThemeContext.Provider value={{ theme, setTheme }}> {children} </ThemeContext.Provider> ) } export default ThemeProvider
И теперь зайдем в корневой файл index.js. Тут мы хотим применить наш ThemeProvider, которым оборачиваем Root, чтобы все, что внутри имело доступ к переменной темы
// src/index.js import React from 'react' import ReactDOM from 'react-dom' import reportWebVitals from './reportWebVitals' import ThemeProvider from './providers/ThemeProvider' // + import Root from './components/Root' import './index.scss' ReactDOM.render( <React.StrictMode> <ThemeProvider> <Root /> </ThemeProvider> </React.StrictMode>, document.getElementById('root') ) ...
Пишем переключатель
Осталось создать переключатель для темы. В нашем случае это будет стандартный тогглер. Ну почти стандартный, мы его немного улучшим, чтобы не совсем грустно было.
// src/components/Toggle/index.js import React from 'react' import styles from './index.module.scss' const Toggle = ({ value, onChange }) => ( <label className={styles.switch} htmlFor="toggler"> <input id="toggler" type="checkbox" onClick={onChange} checked={value} readOnly /> <span className={styles.slider} /> <span className={styles.wave} /> </label> ) export default Toggle
// src/components/Toggle/index.module.scss .root { position: absolute; top: 50%; left: 50%; width: 120px; height: 50px; transform: translate(-50%, -50%); input { display: none; } .slider { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; overflow: hidden; background-color: #e74a42; border-radius: 50px; cursor: pointer; transition: all 1.4s; &:before, &:after { content: ""; position: absolute; bottom: 5px; left: 5px; width: 40px; height: 40px; background-color: #ffffff; border-radius: 30px; } &:before { transition: 0.4s; } &:after { transition: 0.5s; } } .wave { position: absolute; top: 0; left: 0; width: 120px; height: 50px; border-radius: 40px; transition: all 1.4s; &:after { content: ""; position: absolute; top: 3px; left: 20%; width: 60px; height: 3px; background: #ffffff; border-radius: 100%; opacity: 0.4; } &:before { content: ""; position: absolute; top: 10px; left: 30%; width: 35px; height: 2px; background: #ffffff; border-radius: 100%; opacity: 0.3; } } input:checked + .slider { background-color: transparent; &:before, &:after { transform: translateX(70px); } } input:checked ~ .wave { display: block; background-color: #3398d9; } }
Почти все! Осталось только добавить наш Toggle на главную страницу
// src/components/Root/index.js import React from 'react' import { ThemeContext, themes } from '../../contexts/ThemeContext' import Toggle from '../Toggle' const Root = () => ( <ThemeContext.Consumer> {({ theme, setTheme }) => ( <Toggle onChange={() => { if (theme === themes.light) setTheme(themes.dark) if (theme === themes.dark) setTheme(themes.light) }} value={theme === themes.dark} /> )} </ThemeContext.Consumer> ) export default Root
// src/index.scss :root[data-theme="light"] { --background-color: #fafafa; } :root[data-theme="dark"] { --background-color: #2b3e51; } body { background: var(--background-color); }
И чтобы все заработало как надо, нужно задать переменные для каждой темы. Задавать мы их будем через css переменные, поскольку те переменные, которые используются в scss нам не подойдут. scss компилится в css довольно глупо, он просто подставляет значения переменных во всех местах, где они фигурируют.
Заключение
Внедрить темную тему в React оказалось не так уж и сложно. Для этого мы прокидывали информацию о теме с помощью механизма контекстов, который есть в React. В качестве переключателя можно взять что угодно, делитесь своими компонентами в комментариях! Жду от вас вашего мнения о статье и рассказов о вашем опыте добавления тёмной темы!
ссылка на оригинал статьи https://habr.com/ru/post/656995/
Добавить комментарий