다크모드는 유튜브를 보면 알 수 있는데 본래 흰바탕의 화면이 검은 바탕의 화면으로 변경되는 것을 말한다.
아래 사진을 보면 단번에 이해될 것이다.
일반 유튜브
다크모드 유튜브
material-ui 에서는 theme를 이용해 이를 조절할 수 있는데, 이번에 이걸 적용하는 방법에 대해 알아볼 것이다.
우선 테마를 등록해야 한다. 다음처럼 ThemeManager.tsx 파일을 만들어 생성하도록 하자.
(여기서는 타입스크립트를 사용했기 때문에 tsx 확장자를 사용한 것이다. js, jsx 로 만들어도 상관없다)
여기서는 3개만 생성했다. 원하는 만큼 만들어 등록하는 것도 추천. 테마에 활용할 수 있는 컬러종류는 아래 링크 참조. red, pink, purple indego, teal 등 여러가지 있다.
https://material-ui.com/customization/color/
import {createMuiTheme} from "@material-ui/core";
import {amber, blue, green, indigo, red} from "@material-ui/core/colors";
/**
* 테마변경 매니저
*/
const defaultTheme = createMuiTheme({
palette: {
primary: indigo,
},
});
const darkTheme = createMuiTheme({
palette: {
type: 'dark', // type 지정이 중요
primary: {
main: '#2D3032' // 제공된 색상이 없어 직접 설정
}
},
});
const amberTheme = createMuiTheme({
palette: {
primary: amber
},
});
// 테마종류
export enum themes {
default
, dark
, amber
}
function changeTheme(theme:themes) {
switch (theme as themes) {
case themes.amber:
return amberTheme;
case themes.dark:
return darkTheme
default:
return defaultTheme
}
return defaultTheme;
}
export {changeTheme}
createMuiTheme로 생성한 코드를 보면 type: 'dark'라는 부분이 있는데 여기서 type이 위에서 본 밝기, 어둡게 모드를 지정하는 것이다. 아무것도 설정하지 않으면 기본 light 버전으로 된다.
enum으로 쓴 이유는 typesafe 를 위해 사용한 것이다.
changeTheme를 이용해서 코드에 맞는 테마를 리턴하도록 되었다. 잘못된 코드가 들어오면 기본코드를 리턴하도록 한다.
이번엔 Redux에 등록해보도록 하자.
내 경우 로딩바와 같은 레벨로 보기 때문에 GlobalReducer라는 곳에 만들었다.
import {ActionType, createAction, createReducer} from 'typesafe-actions'
import { themes } from '../function/comm/ThemeManager'
export interface IGlobalReducer {
loading: boolean
theme: themes
}
const initialState: IGlobalReducer = {
loading: false,
theme: themes.default
}
export const LOADING_ON = "global/LOADING_ON"
export const LOADING_OFF = "global/LOADING_OFF"
export const THEME_CHANGE = "global/THEME_CHANGE"
export const loadingOn = createAction(LOADING_ON)();
export const loadingOff = createAction(LOADING_OFF)();
export const themeChange = createAction(THEME_CHANGE)<themes>();
export const actions = {loadingOn, loadingOff, themeChange}
type GlobalReducerAction = ActionType<typeof actions>
// 리듀서 추가
const reducer = createReducer<IGlobalReducer, GlobalReducerAction>(initialState, {
[LOADING_ON]: (state) => ({
...state
, loading: true
})
, [LOADING_OFF]: (state) => ({
...state
, loading: false
})
, [THEME_CHANGE]: (state, action) => {
const payload = action.payload;
return ({
...state
, theme: payload
})
}
})
export default reducer
dispatch에 THEME_CHANGE 를 이용해 현재 테마를 저장할 수 있도록 했다.
그럼 이제 호출하는 부분을 만들어 보자
import {Box, FormControlLabel, Radio, RadioGroup} from "@material-ui/core";
import {themes} from "../../function/comm/ThemeManager";
import React, {useState} from "react";
import {THEME_CHANGE} from "../../reducers/GlobalReducer";
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "../../reducers";
import styled from "styled-components";
const InputArea = styled(Box)`
padding: 5px 10px;
`
const ChangeThemeTest = () => {
const dispatch = useDispatch();
const {theme} = useSelector((state:RootState) => state.globalReducer);
const [choiceTheme, setChoiceTheme] = useState<themes>(theme);
// 태마변경
const changeThemeHandler = (value:string) => {
// 넘어오는게 string 이므로 int로 파싱
let themeIdx = parseInt(value);
setChoiceTheme(themeIdx)
dispatch({
type: THEME_CHANGE
, payload: themeIdx
})
}
return (
<>
<InputArea>
테마설정
<RadioGroup
row
aria-label="choiceTheme"
name="choiceTheme"
value={choiceTheme}
onChange={(e) => changeThemeHandler(e.target.value)}>
<FormControlLabel
value={themes.default}
control={<Radio />}
label={"기본"} />
<FormControlLabel
value={themes.dark}
control={<Radio />}
label={"dark"} />
<FormControlLabel
value={themes.amber}
control={<Radio />}
label={"amber"} />
</RadioGroup>
</InputArea>
</>
)
}
export default ChangeThemeTest
라디오박스로 설정할 수 있도록 했고, 화면으로 보면 다음과 같다.
스샷 위쪽의 AppBar는 Layout 에서 설정한 것이며 여기서는 신경쓰지 않아도 된다. 다만 테마가 변경될때마다 색상변화를 가장 직관적으로 알 수 있어 배치해놨다.
이제 교체할때마다 Redux 값이 변경되는 것을 확인한다.(관련코드는 아래 추가했으니 밑에 참고)
그럼 이제 모든화면에 적용될 수 있도록 설정해보자.
ThemeProvider를 이용해 설정해야 한다. 그러기 위해선 최상단에 등록하는 것이 좋다. 내겐 두가지 선택권이 있었는데 _app.tsx 와 Layout.tsx 파일이었다. 여기선 _app.tsx 파일에 설정했다. 그 이유는 아래 설명하겠다.
아래는 app.tsx 파일
...
// material-ui 테마설정
const {theme} = useSelector((state:RootState) => state.globalReducer);
return (
<>
<ThemeProvider theme={changeTheme(theme)}>
<CssBaseline />
<Layout>
<GlobalStyle />
<Component {...pageProps} />
</Layout>
</ThemeProvider>
</>
)
...
아래는 Layout.tsx 파일
다크모드의 경우 배경화면을 모두 다크색으로 채워줘야 하는 작업이 필요한데, 그 설정은 직접 넣어줘야했다. 다음과 같은 설정이다.
참고로 type이 기본과 다크모드가 다른데, theme.palette.background에 들어가는 값이 다음과 같다.
기본: {paper: "#fff", default: "#fafafa"}
다크모드: {paper: "#424242", default: "#303030"}
...
// 테마설정
const useStyles = makeStyles((theme) => ({
root: {
height: "100%",
background: theme.palette.background.default,
}
}))
const classes = useStyles();
...
return (
<div>
{/* 테마설정에서 classes.root 설정을 여기에, */}
<div className={classes.root}>
<TopBar />
{children}
</div>
</div>
)
useStyles 에 커스텀한 스타일을 만들고 그것을 div className에 삽입했다.
처음에는 ThemeProvider와 useStyles 설정을 같은곳에 넣었는데 useStyles에 설정한 배경색 변경이 작동하지 않았다. 아마 렌더링을 새로하면서 스타일을 새로 적용 => 배경색 변경이 일어나야 하는데 그부분이 안되는거 같았다. 그래서 두개의 설정을 각각 분리하여 설정했다. 참고로 _app.tsx 는 최상단, Layout은 다음으로 감싸는 포멧이다.
이제 이것이 작동하는 것을 구현하자.
changeTheme.tsx 라는 컴포넌트를 만들어 라디오버튼을 만들어 각 테마를 연결하도록 했다.
import {Box, FormControlLabel, Radio, RadioGroup} from "@material-ui/core";
import {themes} from "../../function/comm/ThemeManager";
import React, {useState} from "react";
import {THEME_CHANGE} from "../../reducers/GlobalReducer";
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "../../reducers";
import styled from "styled-components";
const InputArea = styled(Box)`
padding: 5px 10px;
`
const ChangeTheme = () => {
const dispatch = useDispatch();
const {theme} = useSelector((state:RootState) => state.globalReducer);
const [choiceTheme, setChoiceTheme] = useState<themes>(theme);
// 태마변경
const changeThemeHandler = (value:string) => {
// 넘어오는게 string 이므로 int로 파싱(enum 대비)
let themeIdx = parseInt(value);
setChoiceTheme(themeIdx)
dispatch({
type: THEME_CHANGE
, payload: themeIdx
})
}
return (
<>
<InputArea>
테마설정
<RadioGroup
row
aria-label="choiceTheme"
name="choiceTheme"
value={choiceTheme}
onChange={(e) => changeThemeHandler(e.target.value)}>
<FormControlLabel
value={themes.default}
control={<Radio />}
label={"기본"} />
<FormControlLabel
value={themes.dark}
control={<Radio />}
label={"dark"} />
<FormControlLabel
value={themes.amber}
control={<Radio />}
label={"amber"} />
</RadioGroup>
</InputArea>
</>
)
}
export default ChangeThemeTest
dispatch를 이용해 store값을 변경했고, 변경된 값은 _app.tsx 에서 설정된 ThemeProvider에 즉각 반영되도록 했다.
이제 실행되는 것을 아래 스크린샷에서 확인해보자.
기본 테마
dark 테마
amber 테마
끝.
'공부 > 프로그래밍' 카테고리의 다른 글
[springboot, jwt] jwt 로 토큰 생성, 유효시간 관리 하기 (1) | 2021.04.05 |
---|---|
[react, springboot] kakao 로그인 API 연동 (0) | 2021.04.02 |
[react] wrapper.getServerSideProps 의 문제점(store 값 초기화 문제) (0) | 2021.03.30 |
[js] video.js 사용하기 (속도조절, 품질 조절 옵션) (2) | 2021.03.26 |
[aws] s3 폴더 내 파일 public-read 권한 주기(aws cli 이용) (0) | 2021.03.24 |
댓글