⭐️ Today's summary
오늘은 학원 과제가 하나 있는데 관리자 리스트를 띄우고 관리자 등록, 관리자 삭제를 할 수 있는
페이지를 만드는 것이다.
오늘 과제 1일차에는 다크모드를 넣어볼려고 한다.
⭐️ Problem
[ ThemeContext.jsx ]
import { createContext } from 'react';
const ThemeContext = createContext();
export default ThemeContext;
[ App.jsx ]
import './App.css';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import UserList from './pages/UserList';
import UserRegistration from './pages/UserRegistration';
import UserDetail from './pages/UserDetail';
import ErrorPage from './pages/ErrorPage';
import Header from './components/common/Header';
import { useState } from 'react';
import ThemeContext from './context/ThemeContext';
import { lightTheme, darkTheme } from './theme/themes';
import { UserProvider } from './context/UserContext';
function App() {
const [theme, setTheme] = useState('white');
const toggleTheme = () => {
setTheme(theme === 'white' ? 'black' : 'white');
};
return (
<>
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<UserProvider>
<BrowserRouter>
<Header />
<Routes>
<Route path="/" element={<UserList />} />
<Route path="/user" element={<UserRegistration />} />
<Route path="/user/:userNo" element={<UserDetail />} />
<Route path="*" element={<ErrorPage />} />
</Routes>
</BrowserRouter>
</UserProvider>
</ThemeContext.Provider>
</>
);
}
export default App;
위와같이 자식 컴포넌트들이 전부 ThemeContext의 value로 theme를 받으면 다크모드를 구현하는 자식들한테props를 다 설정해줘야하는 불편함과 어려움이 있었다.
⭐️ Try
[ App.jsx ]
import './App.css';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import UserList from './pages/UserList';
import UserRegistration from './pages/UserRegistration';
import UserDetail from './pages/UserDetail';
import ErrorPage from './pages/ErrorPage';
import Header from './components/common/Header';
import { useState } from 'react';
import ThemeContext from './context/ThemeContext';
import { ThemeProvider } from 'styled-components';
import { lightTheme, darkTheme } from './theme/themes';
import { UserProvider } from './context/UserContext';
function App() {
const [theme, setTheme] = useState('white');
const toggleTheme = () => {
setTheme(theme === 'white' ? 'black' : 'white');
};
return (
<>
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<ThemeProvider theme={theme === 'black' ? darkTheme : lightTheme}>
<UserProvider>
<BrowserRouter>
<Header />
<Routes>
<Route path="/" element={<UserList />} />
<Route path="/user" element={<UserRegistration />} />
<Route path="/user/:userNo" element={<UserDetail />} />
<Route path="*" element={<ErrorPage />} />
</Routes>
</BrowserRouter>
</UserProvider>
</ThemeProvider>
</ThemeContext.Provider>
</>
);
}
export default App;
위와같이 Styled-components가 제공하는 ThemeProvider를 사용하면 props로 직접 받아서 적용하지 않아도
theme={ theme === 'black' ? darkTheme : lightTheme} 라고 props을 넘겨주는데 이때 자식들은
자동으로 props를 받을 수 있다.
[ UserDetail.jsx ]
import React from 'react';
import { useUser } from '../context/UserContext';
import { useNavigate, useParams } from 'react-router-dom';
import { styled } from 'styled-components';
const PageLayout = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: ${(props) => props.theme.background};
transition: all 0.3s ease;
`;
const Card = styled.div`
width: 400px;
background-color: ${(props) => props.theme.card};
color: ${(props) => props.theme.cardText};
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
border: 1px solid ${(props) => props.theme.cardBorder};
padding: 32px;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
`;
const ProfileImage = styled.img`
width: 160px;
height: 160px;
border-radius: 50%;
object-fit: cover;
border: 3px solid #dcdcdc;
`;
const InfoBox = styled.div`
width: 100%;
text-align: center;
`;
const Name = styled.h2`
font-size: 24px;
font-weight: bold;
`;
const Detail = styled.p`
font-size: 16px;
color: ${(props) => props.theme.cardText};
margin: 4px 0;
font-weight: 600;
`;
const Status = styled.p`
font-size: 14px;
font-weight: bold;
color: ${(props) => (props.$isOnline ? props.theme.onlineText : props.theme.offlineText)};
`;
const ButtonBox = styled.div`
display: flex;
gap: 12px;
margin-top: 20px;
`;
const ActionButton = styled.button`
padding: 10px 18px;
border-radius: 8px;
border: none;
background-color: ${(props) => props.color || '#4b7fcc'};
color: white;
font-weight: bold;
cursor: pointer;
&:hover {
opacity: 0.9;
}
`;
const UserDetail = () => {
const { userInfoes, removeUser } = useUser();
const { userNo } = useParams();
const navigate = useNavigate();
const user = userInfoes.find((user) => user.userNo === parseInt(userNo));
return (
<PageLayout>
<Card>
<ProfileImage src={user.profileImg} alt="Profile" />
<InfoBox>
<Name>{user.name}</Name>
<Detail>나이: {user.age}세</Detail>
<Detail>이메일: {user.email}</Detail>
<Detail>가입일: {user.joinDate}</Detail>
<Detail>역할: {user.role}</Detail>
<Status $isOnline={user.isOnline}>
{user.isOnline ? '🟢 온라인 상태입니다' : '🔴 오프라인 상태입니다.'}
</Status>
</InfoBox>
<ButtonBox>
<ActionButton color="#4b7fcc" onClick={() => alert('준비중입니다.')}>
수정
</ActionButton>
<ActionButton
color="#d62727"
onClick={() => {
const deleteUser = confirm(`${user.name}님을 삭제하시겠습니까?`);
if (deleteUser) {
removeUser(user.userNo);
navigate('/');
} else {
return false;
}
}}
>
삭제
</ActionButton>
</ButtonBox>
</Card>
</PageLayout>
);
};
export default UserDetail;
현재 코드와 같이 UserDetail 컴포넌트는 props를 받지 않고도 styled-components에 props를 사용하고 있다.
props를 사용할때 props.theme로 접근하는데 이때 props.theme는 App.jsx에 넘겨준 theme props이다.
[ theme.js ]
// 다크모드를 위한 테마 정리 js
// header
// 다크모드시에 헤더바 라이트모드 : #4b7fcc 다크모드 : #252528
// 버튼들 배경 #ffffff 고정
// 버튼들 글씨색 라이트 모드 : #4b7fcc , 다크모드 : #000000
// 밝은 테마 hover
export const lightTheme = {
// Header or everyWhere
background: '#ffffff', // 기본 다크 <-> 화이트 변경에 사용
text: '#000000', // 기본 다크 <-> 화이트 변경에 사용
primary: '#4b7fcc', // 아직 미사용
nav: '#4b7fcc', // 버튼 호버시 유저목록, 유저등록에 사용
header: '#4b7fcc', // 헤더 배경, 버튼 호버전 유저목록, 유저등록에 사용
// User
card: '#ffffff',
cardText: '#000000',
cardBorder: 'rgba(0, 0, 0, 0.05)',
// User의 온라인 여부
onlineText: '#198754', // 부드러운 초록
offlineText: '#6c757d', // 중간 회색
// UserStatsCard
cardBg: '#ffffff',
cardText: '#222222',
cardLabel: '#666666',
cardWrapBg: '#eff1f4',
insertBtn: '#4b7fcc',
};
// 어두운 테마 hover
export const darkTheme = {
// Header or everyWhere
background: '#1c1d1e',
text: '#ffffff',
primary: '#8a8a91',
nav: '#000000',
header: '#252528',
// User
card: '#2a2b2e', // 카드 배경 (살짝 어두운 회색)
cardText: '#ffffff',
cardBorder: 'rgba(255, 255, 255, 0.1)',
// User의 온라인 여부
onlineText: '#71dd8a', // 밝은 연두 계열
offlineText: '#a3a3a3', // 부드러운 중간 회색
// UserStatsCard
cardBg: '#2f3033',
cardText: '#ffffff',
cardLabel: '#a3a3a3',
cardWrapBg: '#1c1d1e',
insertBtn: '#ffffff',
};
props.theme.~~ 를하게되면 theme.js에 정의한 props들에게 접근하여 사용할 수 있게된다.
'Weekly TIL' 카테고리의 다른 글
Weekly TIL - Day 14 (0) | 2025.04.28 |
---|---|
Weekly TIL - Day 13 (0) | 2025.04.27 |
Weekly TIL - Day 11 (1) | 2025.04.25 |
Weekly TIL - Day 10 (2) | 2025.04.24 |
Weekly TIL - Day 9 (0) | 2025.04.23 |