DarkMode Theme

heyj·2022년 3월 25일
1

wanted_PreOnboarding

목록 보기
7/7

예전 글에서 언급했던 DarkMode를 추가해보기로 했습니다.(꺄아)

1.styled-components ThemeProvider 걷어내기

상태관리 할 것들이 많지 않지만, redux와 redux toolkit을 사용해 상태관리를 하기로 결정했습니다.
현재 styled-components의 ThemeProvider로 theme를 넘겨주고 있었는데요, 굳이 redux와 함께 쓸 필요가 없을 것 같아 ThemeProvider는 제거를 했습니다.

npm install react-redux redux-toolkit

/src/redux 폴더를 만들고 theme.js를 만들어줬습니다.
그리고 theme.js에는 테마에 필요한 값들을 저장했습니다.

  windowSize: {
    small: `screen and (max-width: 600px)`,
    base: `screen and (max-width: 768px)`,
    large: `screen and (max-width: 1024px)`,
  },
  fontSize: {
    xs: '0.5rem',
    sm: '0.75rem',
    base: '1rem',
    md: '1.25rem',
    lg: '1.5rem',
  },
  lightversion: {
    background: '#fff',
    fontPrimary: 'black',
    fontSecondary: 'gray',
    primary: '#00a0ff',
    ...
    ....

2. Toggle Switch만들기

Toggle Switch는 input checkbox를 이용해 만들어줍니다. 토글은 토글 내부의 배경과 버튼 스타일링 손이 많이 갑니다. 가상 요소(Pseudo-Element)인 :before, :after를 이용해서 토글 내부를 만들어줍니다.

  • 선택자에 추가하는 키워드로, 선택한 요소의 지정된 부분에 스타일을 입힐 수 있습니다.
  • :before는 실제 내용 바로 앞에서 생성되는 자식요소이고, :after는 실제 내용 바로 뒤에서 생성되는 자식요소입니다.
    (https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements)
import React, { useEffect } from 'react';
import styled from 'styled-components';

const Toggle = (props) => {
  const dispatch = useDispatch();
  const isSwitchOn = useSelector((state) => state.toggleReducer.isSwitchOn);

  const toggleSwitch = () => {
    dispatch(switchOn());
  };

  return (
    <ToggleContainer>
      {isSwitchOn ? <Dark>🌙</Dark> : <Light>☀️</Light>}
      <ToggleSwitch>
        <Checkbox
          type="checkbox"
          id="toggleSwitch"
          onClick={toggleSwitch}
          isSwitchOn={isSwitchOn}
        />
        <Label htmlFor="toggleSwitch" isSwitchOn={isSwitchOn}>
          <ToggleInner isSwitchOn={isSwitchOn} />
          <Switch isSwitchOn={isSwitchOn} />
        </Label>
      </ToggleSwitch>
    </ToggleContainer>
  );
};

export default Toggle;

...
....

const ToggleInner = styled.span`
  display: block;
  width: 200%;
  margin-left: -100%;
  transition: margin-left 0.4s ease-in;
  margin-left: ${(props) => props.isSwitchOn && 0};
  &:before {
    float: left;
    width: 50%;
    height: 20px;
    padding: 0;
    content: '';
    background-color: ${props => props.theme.lightversion.primary};
  }
  &:after {
    float: left;
    width: 50%;
    height: 20px;
    padding: 0;
    box-sizing: border-box;
    content: '';
    background-color: ${props => props.theme.lightversion.secondary};
  }
`;

const Switch = styled.span`
  display: block;
  width: 12px;
  margin: 5px;
  background: ${props => props.theme.lightversion.primary};
  position: absolute;
  top: 0;
  bottom: 0;
  right: 28px;
  border: 0 solid ${props => props.theme.lightversion.secondary};
  border-radius: 50%;
  transition: all 0.4s ease-in;
  right: ${(props) => props.isSwitchOn && 0};
`;

모드를 확인할 수 있는 이모티콘도 추가해줬습니다.

이 Toggle Switch의 상태 확인을 통해 전체 테마를 변경시켜줘야 합니다. 'isSwitchOn'이라는 상태변수를 만들어주고, reducer도 만들어 줍니다.

import { createSlice } from '@reduxjs/toolkit';

const initialState = {
  isSwitchOn: false,
};

export const toggleReducer = createSlice({
  name: 'toggleSwitch',
  initialState,
  reducers: {
    switchOn: (state, actions) => {
      state.isSwitchOn = !state.isSwitchOn;
    },
  },
});

export const { switchOn } = toggleReducer.actions;

export default toggleReducer.reducer;

계속 다크모드를 쓰고 싶은 유저가 있다면, 이 스위치 값을 계속 기억해둬야 할 필요가 있습니다. 브라우저를 새로고침 한다면 isSwitchOn 값은 다시 false로 돌아가 light theme으로 변경되기 때문입니다.

스위치 값을 로컬스토리지에 저장하는 로직도 추가해줬습니다. 또한 브라우저를 새로고침하거나 껐다가 다시 켜도 모드를 기억할 필요가 있기 때문에 useEffect로 getSwitchState를 불러오도록 했습니다.

// reducer
switchOn: (state, actions) => {
  state.isSwitchOn = !state.isSwitchOn;
  window.localStorage.setItem(
    'switch',
    JSON.stringify({ isSwitchOn: state.isSwitchOn }),
  );
},
  getSwitchState: (state, actions) => {
    let result = JSON.parse(window.localStorage.getItem('switch'));
    if (result) {
      state.isSwitchOn = result.isSwitchOn;
    } else {
      state.isSwitchOn = false;
    }
  },
    
// toggle.js
    
useEffect(() => {
  dispatch(getSwitchState());
}, [dispatch]);

3. 각 컴포넌트에 테마 연결하기

각 컴포넌트에서 theme과 isSwitchOn을 불러와서 상태에 맞게 연결시켜 줍니다.

// src/pages/Search.js
const Search = () => {
  const isSwitchOn = useSelector((state) => state.toggleReducer.isSwitchOn);
  const theme = useSelector((state) => state.theme);
	....
    return (...
 )
}

// example            
const SearchInput = styled.input`
  margin-left: 30px;
  width: 80%;
  height: 100%;
  border: none;
  border-bottom: 1px solid
    ${(props) =>
      props.isSwitchOn
        ? props.theme.darkversion.secondary
        : props.theme.lightversion.secondary};
  color: ${(props) => props.isSwitchOn && props.theme.darkversion.fontPrimary};
  background-color: ${(props) =>
    props.isSwitchOn && props.theme.darkversion.background};
  font-size: ${(props) => props.theme.fontSize.md};
  &:focus {
    outline: none;
  }
  ....
`

이 작업이 정말 매우 오래 걸렸습니다...ㅠㅠ
빠지는 부분 없이 모두 연결해줍니다.

4. 완성

정말 오래걸리긴 했는데, 다 만들어놓으니 이것만큼 예쁜게 없더군요!(다크모드 넘 좋아ㅠ)
이번 프로젝트는 기능보다 UI에 신경을 많이 썼던 것 같습니다.
만들어 본 적 없는 것들을 구현해보면서 또 많이 공부했습니다.

마지막 과제라 아쉽기도 합니다.

0개의 댓글