React css modules dark theme

Тёмная тема в React с помощью Redux-toolkit

Эта статья является продолжением статьи Тёмная тема в React с использованием css переменных в scss. Если в прошлый раз мы добавляли тёмную тему через родной реактовский контекст, то сейчас мы попробуем сделать всё то же самое, но с помощью Redux , точнее redux-toolkit

Roadmap

Мы проделаем почти те же шаги, что и в прошлый раз:

  1. Run-up Создадим create-react-app проект и немного поправим структуру
  2. Redux Добавим компонент переключателя темы с redux-состоянием
  3. CSS Variables Объявим переменные для каждой темы, которые будут влиять на стили компонентов
  4. Bonus Добавим роутинг

1. Подготовка

  1. С помощью create-react-app создаём проект и сразу добавляем sass и classnames для удобства работы со стилями
> npx create-react-app with-redux-theme --template redux > cd with-redux-theme > npm i sass classnames -S
  1. Поскольку все дальнейшие действия мы будем производить, находясь в папке /src , то для удобства перейдем в неё
# находимся внутри папки /src > rm -rf app features App.css App.js App.test.js index.css logo.svg

4. Создадим удобную структуру приложения

# находимся внутри папки /src > mkdir -p components/Theme > touch index.scss root.js store.js > touch components/Theme/

Поддерево проекта внутри папки /src должно получиться таким

# перейдем в корень и проверим структуру > tree src src ├── components │ └── Theme │ ├── index.js │ ├── index.module.scss │ └── slice.js ├── index.js ├── index.scss ├── root.js ├── store.js └── . 

Поскольку мы внесли изменения в структуру, то перепишем наш src/index.js

// src/index.js import React from 'react' import ReactDOM from 'react-dom/client' import < Provider >from 'react-redux' import Root from './root' import store from './store' import './index.scss' const rootElement = document.getElementById('root') if (!rootElement) throw new Error('Failed to find the root element') const root = ReactDOM.createRoot(rootElement) root.render( >   )

Вместо App.js я использую файл root.js с компонентом Root , который в конце концов у нас будет хранить роуты на страницы, но а пока.

// src/root.js const Root = () => ( 
There are will be routes
) export default Root

Теперь можно приступить ко второй части — написание самой логики изменения темы

2. Добавляем логику для темы

Сконфигурируем наш стор. В нем у нас будет один лишь редьюсер темы. Делаем его по аналогии с counter , который шел из коробки, только наш будет попроще.

// src/store.js import < configureStore >from '@reduxjs/toolkit' import themeReducer from './components/theme/slice' export const store = configureStore(< reducer: < theme: themeReducer, >, >)

Теперь реализуем сам редьюсер с остальной логикой, необходимой для работы темы.

// src/components/theme/slice.js import < createSlice >from '@reduxjs/toolkit' // пытаемся получить тему из локального хранилища браузера // если там ничего нет, то пробуем получить тему из настроек системы // если и настроек нет, то используем темную тему const getTheme = () => < const theme = `$` if ([ 'light', 'dark' ].includes(theme)) return theme const userMedia = window.matchMedia('(prefers-color-scheme: light)') if (userMedia.matches) return 'light' return 'dark' > const initialState = getTheme() export const themeSlice = createSlice( < name: 'theme', initialState, reducers: < set: (state, action) =>action.payload, >, >) export const < set >= themeSlice.actions export default themeSlice.reducer

На этом этапе у нас всё работает, но нет компонента, который бы изменял тему.
Реализуем его:

// src/components/theme/index.js import React from 'react' import < useSelector, useDispatch >from 'react-redux' import cn from 'classnames' import < set >from './slice' import styles from './index.module.scss' const Theme = (< className >) => < const theme = useSelector((state) =>state.theme) const dispatch = useDispatch() React.useEffect(() => < document.documentElement.dataset.theme = theme localStorage.setItem('theme', theme) >, [ theme ]) const handleChange = () => < const next = theme === 'dak' ? 'light' : 'dark' dispatch(set(next)) >return ( onClick= /> ) > export default Theme
// src/components/theme/index.module.scss .root < position: relative; border-radius: 50%; display: block; height: 24px; overflow: hidden; width: 24px; transition: 0.5s all ease; input < display: none; >&:hover < cursor: pointer; >&:before < content: ""; display: block; position: absolute; >&.light:before < animation-duration: 0.5s; animation-name: sun; background-color: var(--text-color); border-radius: 50%; box-shadow: 10px 0 0 -3.5px var(--text-color), -10px 0 0 -3.5px var(--text-color), 0 -10px 0 -3.5px var(--text-color), 0 10px 0 -3.5px var(--text-color), 7px -7px 0 -3.5px var(--text-color), 7px 7px 0 -3.5px var(--text-color), -7px 7px 0 -3.5px var(--text-color), -7px -7px 0 -3.5px var(--text-color); height: 10px; left: 7px; top: 7px; width: 10px; &:hover < background-color: var(--background-color); box-shadow: 10px 0 0 -3.5px var(--background-color), -10px 0 0 -3.5px var(--background-color), 0 -10px 0 -3.5px var(--background-color), 0 10px 0 -3.5px var(--background-color), 7px -7px 0 -3.5px var(--background-color), 7px 7px 0 -3.5px var(--background-color), -7px 7px 0 -3.5px var(--background-color), -7px -7px 0 -3.5px var(--background-color); >> &.dark < &:before < animation-duration: .5s; animation-name: moon; background-color: var(--text-color); border-radius: 50%; height: 20px; left: 2px; top: 2px; width: 20px; z-index: 1; &:hover < background-color: var(--background-color); >> &:after < animation-duration: .5s; animation-name: moon-shadow; background: var(--background-color); border-radius: 50%; content: ""; display: block; height: 18px; position: absolute; right: -2px; top: -2px; width: 18px; z-index: 2; >> > @keyframes sun < from < background-color: var(--background-color); box-shadow: 0 0 0 -5px var(--background-color), 0 0 0 -5px var(--background-color), 0 0 0 -5px var(--background-color), 0 0 0 -5px var(--background-color), 0 0 0 -5px var(--background-color), 0 0 0 -5px var(--background-color), 0 0 0 -5px var(--background-color), 0 0 0 -5px var(--background-color); >to < background-color: var(--text-color); box-shadow: 10px 0 0 -3.5px var(--text-color), -10px 0 0 -3.5px var(--text-color), 0 -10px 0 -3.5px var(--text-color), 0 10px 0 -3.5px var(--text-color), 7px -7px 0 -3.5px var(--text-color), 7px 7px 0 -3.5px var(--text-color), -7px 7px 0 -3.5px var(--text-color), -7px -7px 0 -3.5px var(--text-color); >> @keyframes moon < from < height: 0; left: 12px; top: 12px; width: 0; >to < height: 20px; left: 2px; top: 2px; width: 20px; >> @keyframes moon-shadow < from < background-color: var(--background-color); height: 0; right: 7px; top: 7px; width: 0; >to < background-color: var(--background-color); height: 18px; right: -2px; top: -2px; width: 18px; >>

Добавим наш компонент Theme на главную страницу.

// src/root.js import Theme from './components/Theme' const Root = () => ( <> 

Тёмная тема в React с помощью Redux-toolkit

) export default Root

И чтобы все заработало как надо, нужно задать переменные для каждой темы. Задавать мы их будем через css переменные, поскольку те переменные, которые используются в scss нам не подойдут. scss компилится в css довольно глупо, он просто подставляет значения переменных во всех местах, где они фигурируют.

// src/index.scss :root[data-theme="light"] < --background-color: #ffffff; --text-color: #1C1E21; >:root[data-theme="dark"] < --background-color: #18191a; --text-color: #f5f6f7; >body

Ура! Все работает! И теперь обещанный бонус — добавление роутов

Добавляем роутинг

Для начала установим библиотеку

Обернем все в провайдер BrowserRouter от react-router

import React from 'react' import ReactDOM from 'react-dom/client' import < Provider >from 'react-redux' import < BrowserRouter >from 'react-router-dom' import * as serviceWorker from './serviceWorker' import Root from './root' import store from './store' import './index.scss' const rootElement = document.getElementById('root') if (!rootElement) throw new Error('Failed to find the root element') const root = ReactDOM.createRoot(rootElement) root.render(  >    ) serviceWorker.unregister()

Теперь можно в файле src/root.js добавить такой код

import < Routes, Route >from 'react-router-dom' import Layout from './components/Layout' import Home from './pages/Home' import NoMatch from './pages/NoMatch' const Root () => ( >> > /> > />  ) export default Root

Создадим недостающие компоненты

> mkdir -p src/pages/ src/components/Layout > touch src/pages/Home/index.js src/pages/NoMatch/index.js > touch src/components/Layout/index.js

Страницы приложения я поместил в папку /pages . Подобным образом сделано в NextJS и мне кажется это хорошей практикой.

// src/components/Layout/index.js import < Outlet >from 'react-router-dom' import Theme from '../Theme' const Layout = () => ( <>  
) export default Layout
// src/pages/Home/index.js const Home = () => 

Home

export default Home
// src/pages/NoMatch/index.js import < Link >from 'react-router-dom' const NoMatch = () => ( <> 

Page Not Found

We could not find what you were looking for.

Go to the home page

) export default NoMatch

И теперь мы по умолчанию находимся на странице Home , а если перейдем на любую другую, то нам откроется страница NoMatch

Заключение

С помощью redux-toolkit добавление тёмной темы выглядит еще проще. К тому же, если вы всё равно собираетесь его использовать на своем проекте, то этот подход будет предпочтительней контекста. Делитесь в комментариях мыслями о том, как можно улучшить этот код или задавайте вопросы, если что-то осталось не ясно — с удовольствием всем отвечу!

Репозиторий

Весь код вы можете посмотреть тут!
Как это будет выглядеть — тут

Источник

Тёмная тема в React с использованием css переменных в scss

Темная тема стала стандартом де-факто. Ее отсутствие может стать причиной отказа от пользования сайтом. Особенно если на него заходят программисты, которые сплошь и рядом работают в тёмной теме.

Я покажу, как можно просто добавить тёмную тему в React проект. Разберем основные моменты и сделаем всё красиво. Для тех, кто хочет все сразу:

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
> cd src > rm App.css App.js App.test.js index.css logo.svg

3. Создадим удобную структур

# внутри src/ > mkdir -p components/ contexts providers > touch index.scss components/Root/index.js components/Toggle/ 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(  , document.getElementById('root') )
// src/components/Root/index.js import React from 'react' const Root = () => ( 
There are will be Dark Theme
) export default Root

Проект уже запускается, но никакой темной темы пока еще нет.
Давайте добавим ее!

Добавляем контекст

Наполним кодом наши файлы ThemeContext.js и ThemeProvider.js .
Сначала объявим контекст.

// src/contexts/ThemeContext.js import React from 'react' export const themes = < dark: 'dark', light: 'light', >export const ThemeContext = React.createContext(<>)

А далее создадим проводник нашего контекста, в котором сначала получим текущее значение темы, которая хранится в localStorage . Если там еще ничего нет, то берем значение из системной темы. Если и этого нет (привет из виндовс xp) — то устанавливаем тёмную тему (А что?! Можем себе позволить).

При изменении темы — одновременно сохраняем ее в localStorage .

// src/providers/ThemeProvider.js import React from 'react' import < ThemeContext, themes >from '../contexts/ThemeContext' const getTheme = () => < const 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 ( >> ) > 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(    , document.getElementById('root') ) . 

Пишем переключатель

Осталось создать переключатель для темы. В нашем случае это будет стандартный тогглер. Ну почти стандартный, мы его немного улучшим, чтобы не совсем грустно было.

// src/components/Toggle/index.js import React from 'react' import styles from './index.module.scss' const Toggle = (< value, onChange >) => (  ) 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 = () => ( <(< theme, setTheme >) => ( < if (theme === themes.light) setTheme(themes.dark) if (theme === themes.dark) setTheme(themes.light) >> value= /> )> ) export default Root
// src/index.scss :root[data-theme="light"] < --background-color: #fafafa; >:root[data-theme="dark"] < --background-color: #2b3e51; >body

И чтобы все заработало как надо, нужно задать переменные для каждой темы. Задавать мы их будем через css переменные, поскольку те переменные, которые используются в scss нам не подойдут. scss компилится в css довольно глупо, он просто подставляет значения переменных во всех местах, где они фигурируют.

Заключение

Внедрить тёмную тему в React оказалось не так уж и сложно. Для этого мы прокидывали информацию о теме с помощью механизма контекстов, который есть в React . В качестве переключателя можно взять что угодно, делитесь своими компонентами в комментариях! Жду от вас вашего мнения о статье и рассказов о вашем опыте добавления тёмной темы!

Источник

Читайте также:  Search keys in dictionary python
Оцените статью