기존에 만들었던 헤더에 닉네임을 한글로 작성하거나, 윈도우 화면에서 보는 경우 세로로 글자가 정렬되는 에러가 발생했다. 기존 코드의 문제점을 살펴보면 내가 인라인 태그인 닉네임 부분의 max-width를 100%로 설정해놓아 텍스트 내용과 넓이간의 조율이 어려웠던 것 같다. 이 상태에서는 말줄임표 처리도 제대로 적용되지 않았다.
그래서 max-width값을 100vw로 바꾸고 말줄임료를 적용했더니 원하던 UI가 나오는 것을 확인할 수 있었다.
max-width: calc(100vw - 200px);
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
서영님, 기영님이 다크모드를 적용시켜주었다!🌝🌚 우선 다크모드 적용 전, 프로젝트에서는 constant 폴더에 dark와 light 컬러를 선정해두고 사용했었다. 다크모드 적용을 위해 우선 styles 폴더에 작성했던 컬러값을 따로 만들어 다음과 같은 theme.tsx파일을 만들어 주셨다.
theme 타입 지정
모드에 따라 로고 파일이 달라져서, theme에 따른 이미지를 가져오기 위해 LOGO값을 추가해주었다. 이 과정에서 key가 같지 않으면 에러가 뜨는 부분이 신기해서 왜 그런가 코드를 뜯어보았다.
theme의 lightTheme 값을 Theme 이라는 타입으로 설정해준 뒤 , 해당 타입을 darkTheme의 타입으로 설정해주었다. lightTheme을 기준으로 darkTheme의 템플릿이 정해지기 때문에, theme을 가져와 사용할 때 헷갈리는 실수를 방지할 수 있는 장점이 있다고 생각했다.
useDarkMode라는 커스텀 훅을 만들어주셨다. 로컬 스토리지에 theme 상태를 저장해, 창을 옮겨도 상태가 유지되도록 만들어졌다.
import { useEffect, useState } from 'react';
import { lightTheme, darkTheme, Theme } from '../styles/theme';
export const useDarkMode = () => {
const [theme, setTheme] = useState<Theme>(lightTheme);
const setMode = (mode: Theme) => {
mode === lightTheme
? window.localStorage.setItem('theme', 'light')
: window.localStorage.setItem('theme', 'dark');
setTheme(mode);
};
const toggleTheme = () => {
theme === lightTheme ? setMode(darkTheme) : setMode(lightTheme);
};
useEffect(() => {
const localTheme = window.localStorage.getItem('theme');
if (localTheme !== null) {
if (localTheme === 'dark') {
setTheme(darkTheme);
} else {
setTheme(lightTheme);
}
}
}, []);
return { theme, toggleTheme };
};
전체를 아우르는 _app.tsx파일에 다음과 같은 파일들을 import 해줬다.
import type { AppProps } from "next/app";
import React, { createContext } from "react";
import { css, Global, ThemeProvider } from "@emotion/react";
import { GlobalStyle } from "../styles/global-styles";
import { lightTheme, darkTheme, Theme } from "../styles/theme";
import { useDarkMode } from "../hooks/useDarkMode";
import DarkModeToggle from "../components/Home/DarkModetoggle";
하나씩 뜯어보면,
스타일 상태를 전역에서 관리하기 위해 useContext를 만들기 위한 createContext
css 스타일을 전체에 적용하기 위한 Global과 Global style를 사용하셨다.
왜 ThemeProvider를 사용하지 않고 별도의 css파일을 만들어 적용하셨나요?
스타일드 컴포넌트에서 사용하는 것과 이모션에서 사용하는 것이 달라 별도로 만들어주었는데, 현재 코드상으로<Global styles={GlobalStyle(theme === lightTheme ? lightTheme : darkTheme)} />
이 부분이 없어도 스타일이 적용이 된다고 한다.
이 부분에 대해선 나중에 리팩토링이 진행될 예정이라, 서영님이 관련 글 작성하시면 첨부하도록 하겠다 🔗
그 다음 가져온 theme들을 적용시켜주기 위해 theme style들을 import 하고
앞서 말했던 새로고침을 하거나 창을 옮겼다 들어왔을 때도 해당 theme이 유지되도록 하기 위해 useDarkMode를 가져왔다.
interface ContextProps {
theme: Theme;
toggleTheme: () => void;
}
// theme을 전역에서 변경할 수 있도록 context 생성
export const ThemeContext = createContext<ContextProps>({
theme: lightTheme,
toggleTheme: () => {
return null;
},
});
function MyApp({ Component, pageProps }: AppProps) {
const { theme, toggleTheme } = useDarkMode();
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<>
<Global
styles={GlobalStyle(theme === lightTheme ? lightTheme : darkTheme)}
/>
<Component {...pageProps} />
<DarkModeToggle />
</>
</ThemeContext.Provider>
);
}
이렇게 전역으로 내려준 theme context를 사용하기 위해서
theme을 사용하는 컴포넌트에
import { Theme } from "../../styles/theme"
import { useContext } from "react"
import { ThemeContext } from "../../pages/_app"
import { ThemeProps } from "../../types/Theme"
theme을 내려준 뒤,
컴포넌트 안에 const { theme } = useContext(ThemeContext)
를 선언해준다.
가져온 theme을 리턴되는 각 컴포넌트에 attribute로 넘겨주고, 스타일드 컴포넌트를 선언하는 부분에 리턴타입을 설정해준다.
예를 들어 Menubar라는 컴포넌트에 theme을 사용한다면,
<Menubar theme={theme}/>
이렇게 넘기고 선언 부분을
const Menubar = styled.nav<ThemeProps>`
width: 100%;
margin: 72px auto;
box-shadow: 0 -10px 10px 0 ${({ theme }) => theme.SUBBACKGROUND};
`
이런 식으로 사용하면 된다.
전에 포트폴리오 사이트에 넣기 위해 바닐라 자바스크립트로 네비게이션 애니메이션을 넣은 적이 있다. 거기서 착안해 리액트 형식으로 바꿔 사용해 보았다.
const scrollTop = useRef(0);
const lastscrollTop = useRef(0);
const [navTop, setNavTop] = useState(0);
계속해서 스크롤 값을 업데이트하는 scrollTop과 lastscrollTop은 useRef를 사용해주었다. top 값이 변경되면 재렌더링이 필요하기 때문에 top을 state를 이용해주었다.
const scrollNav = () => {
scrollTop.current = window.scrollY
if (scrollTop.current > lastscrollTop.current) {
setNavTop(-64)
setShowMenu(false)
} else {
setNavTop(0)
}
lastscrollTop.current = scrollTop.current
}
웹 렌더링과 동시에 이벤트를 넣어주고 웹이 종료되면 사라지도록 하기 위해 useEffect를 사용했다. 초반에만 실행되면 되기 때문에 의존성 배열로 빈 배열을 넣어주었다.
useEffect(() => {
window.addEventListener("scroll", scrollNav);
return () => {
window.removeEventListener("scroll", scrollNav);
};
}, []);