Weekly TIL - Day 12

2025. 4. 25. 09:26·Weekly TIL

⭐️ 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들에게 접근하여 사용할 수 있게된다.

 


 

 

다크모드 X

 

 

다크모드 O

'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
'Weekly TIL' 카테고리의 다른 글
  • Weekly TIL - Day 14
  • Weekly TIL - Day 13
  • Weekly TIL - Day 11
  • Weekly TIL - Day 10
KoesJin
KoesJin
hEELo
  • KoesJin
    Seok DevLog
    KoesJin
  • 전체
    오늘
    어제
    • 분류 전체보기 (110) N
      • Back End (31)
        • DataBase (15)
        • JAVA (12)
        • JDBC (4)
      • Front End (9)
        • HTML5 & CSS (3)
        • Java Script (6)
        • REACT (0)
      • Server (9)
        • JSP - TomCat - Servlet (7)
        • Spring Boot (2)
      • GitHub (1)
      • AWS (1) N
      • IT 지식 (기술면접 대비) (20)
      • Weekly TIL (39)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    order by
    View
    DDL
    INNER JOIN
    exception
    weekly til - day 43
    MVC 패턴
    GC
    weekly til - day 38
    순서에 대하여
    DAO
    from
    weekly til - day 40
    where
    weekly til - day 39
    commit
    dml
    weekly til - day 41
    css
    select
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
KoesJin
Weekly TIL - Day 12
상단으로

티스토리툴바