
๐ฏ
@vanilla-extract/css๋ผ์ด๋ธ๋ฌ๋ฆฌ์redux๋ฅผ ํ์ฉํ์ฌ Trello ํ๋ฉด์ ๊ตฌํํฉ๋๋ค.

์๋จ: ํ์ฌ ์ ํ๋ Board์ List ๋ชฉ๋ก์ด ๊ฐ๋ก๋ก ๋์ด๋์ด ์์ต๋๋ค. ๊ฐ ๋ฆฌ์คํธ์๋ Task ์นด๋๊ฐ ํฌํจ๋์ด ์์ผ๋ฉฐ, ๋ฆฌ์คํธ์ ์นด๋์ ์ถ๊ฐ ๋ฐ ์์ ์ด ๊ฐ๋ฅํฉ๋๋ค.
๊ฒ์ํ ์ถ๊ฐ: ์ค๋ฅธ์ชฝ ๋งจ ์์ ์๋ ๋ฒํผ+์ ํด๋ฆญํ๋ฉด ์๋ก์ด ๋ณด๋๊ฐ ์ถ๊ฐ๋ฉ๋๋ค.
๋ฆฌ์คํธ ์ถ๊ฐ: + ์๋ก์ด ๋ฆฌ์คํธ ๋ฑ๋ก ๋ฒํผ์ ๋๋ฅด๋ฉด ์ ๋ฆฌ์คํธ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
์์
์ถ๊ฐ: ๋ฆฌ์คํธ ์์ + ์๋ก์ด ์ผ ๋ฑ๋ก ๋ฒํผ์ ๋๋ฅด๋ฉด Task๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค.
ํ๋จ: ์ด ๊ฒ์ํ ์ญ์ ํ๊ธฐ ๋ฒํผ๊ณผ ํ๋ ๋ชฉ๋ก ๋ณด์ด๊ธฐ ๋ฒํผ์ด ์์ต๋๋ค.
๋ณด๋ ์ญ์ : ํ์ฌ ์ ํ๋ Board๋ฅผ ์ญ์ ํ ์ ์์ต๋๋ค. (์ต์ 1๊ฐ์ ๊ฒ์ํ์ ์ ์ง)
ํ๋ ๋ก๊ทธ ํ์ธ: ์ญ์ , ์ถ๊ฐ, ์์ ๋ฑ์ ๋ก๊ทธ๋ค์ด ๊ธฐ๋ก๋ฉ๋๋ค.
// React ์ํ ๊ด๋ฆฌ๋ฅผ ์ํ useState ์ํฌํธ
import { useState } from 'react';
// CSS ๋ชจ๋ ์คํ์ผ๋ค ์ํฌํธ
import {
appContainer,
board,
buttons,
deleteBoardButton,
loggerButton,
} from './App.css';
// ํ์ ์ปดํฌ๋ํธ๋ค ์ํฌํธ
import BoardList from './components/BoardList/BoardList';
import ListsContainer from './components/ListsContainer/ListsContainer';
import ModalEdit from './components/ModalEdit/ModalEdit';
import LoggerModal from './components/LoggerModal/LoggerModal';
// ์ปค์คํ
ํ
: ํ์
์ด ์ง์ ๋ Redux ๋์คํจ์น & ์
๋ ํฐ ํ
import { useTypedDispatch, useTypedSelector } from './hooks/redux';
// Redux ์ฌ๋ผ์ด์ค์์ ์ก์
๊ฐ์ ธ์ค๊ธฐ
import { deleteBoard } from './store/slices/boardsSlice';
import { addLog } from './store/slices/loggerSlice';
// ๋ก๊ทธ ID ์์ฑ์ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
import { v4 } from 'uuid';
function App() {
// Redux ์ก์
์คํ์ฉ
const dispatch = useTypedDispatch();
// ์ง์ญ ์ํ
const [isLoggerOpen, setIsLoggerOpen] = useState(false);
const [activeBoardId, setActiveBoardId] = useState('board-0');
// ์ ์ญ ์ํ
const modalActive = useTypedSelector((state) => state.boards.modalActive);
const boards = useTypedSelector((state) => state.boards.boardArray);
// ํ์ฌ ํ์ฑ ๋ณด๋ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
const getActiveBoard = boards.filter(
(board) => board.boardId === activeBoardId
)[0];
// ํ์ฑ ๋ณด๋์ ๋ฆฌ์คํธ๋ค ๊ฐ์ ธ์ค๊ธฐ
const lists = getActiveBoard.lists;
// ๊ฒ์ํ ์ญ์ ํจ์
const handleDeleteBoard = () => {
if (boards.length > 1) {
// Redux์ ์ญ์ ์ก์
๋์คํจ์น
dispatch(
deleteBoard({
boardId: getActiveBoard.boardId,
})
);
// Redux์ ๋ก๊ทธ ์ถ๊ฐ ์ก์
๋์คํจ์น
dispatch(
addLog({
logId: v4(),
logMessage: `๊ฒ์ํ ์ง์ฐ๊ธฐ : ${getActiveBoard.boardName}`,
logAuthor: 'User',
logTimestamp: String(Date.now()),
})
);
// ์ญ์ ํ ์ด๋ค ๊ฒ์ํ์ ๋ณด์ฌ์ค์ง ๊ฒฐ์
const newIndexToSet = () => {
const indexToBeDeleted = boards.findIndex(
(board) => board.boardId === activeBoardId
);
return indexToBeDeleted === 0
? indexToBeDeleted + 1
: indexToBeDeleted - 1;
};
// ์๋ก์ด ๋ณด๋ ID๋ก ์ ํ
setActiveBoardId(boards[newIndexToSet()].boardId);
} else {
alert('์ต์ ๊ฒ์ํ ๊ฐ์๋ ํ ๊ฐ์
๋๋ค.');
}
};
return (
<div className={appContainer}>
{isLoggerOpen && <LoggerModal setIsLoggerOpen={setIsLoggerOpen} />}
{modalActive && <ModalEdit />}
<BoardList
activeBoardId={activeBoardId}
setActiveBoardId={setActiveBoardId}
/>
<div className={board}>
<ListsContainer lists={lists} boardId={getActiveBoard.boardId} />
</div>
<div className={buttons}>
<button className={deleteBoardButton} onClick={handleDeleteBoard}>
์ด ๊ฒ์ํ ์ญ์ ํ๊ธฐ
</button>
<button
className={loggerButton}
onClick={() => setIsLoggerOpen(!isLoggerOpen)}
>
{isLoggerOpen ? 'ํ๋ ๋ชฉ๋ก ์จ๊ธฐ๊ธฐ' : 'ํ๋ ๋ชฉ๋ก ๋ณด์ด๊ธฐ'}
</button>
</div>
</div>
);
}
export default App;
useState vs Redux์ง์ญ ์ํ
isLoggerOpen, activeBoardId : App ์ปดํฌ๋ํธ์์๋ง ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ useState๋ฅผ ์ฌ์ฉํ์ฌ ๊ด๋ฆฌํด์ค๋๋ค. ์ ์ญ ์ํ
modalActive, boards : ์ฌ๋ฌ ์ปดํฌ๋ํธ์์ ํ์ํ ๊ตฌ์กฐ์ด๊ธฐ ๋๋ฌธ์ Redux๋ฅผ ํ์ฉํด์ ๊ด๋ฆฌํด์ค๋๋ค.handleDeleteBoard )handleDeleteBoard : ํ์ฌ ํ์ฑํ๋ ๊ฒ์ํ์ ์ญ์ ํ๊ณ , ๋ก๊ทธ๋ฅผ ๊ธฐ๋กํ๋ฉฐ, ์ญ์ ํ ๋ณด์ฌ์ค ๊ฒ์ํ์ ์๋ก ์ค์ ํฉ๋๋ค.
newIndexToSet: ์ญ์ ๋์์ด ์ฒซ ๋ฒ์งธ ๋ณด๋๋ฉด ๋ค์ ๋ณด๋ ๋ณด์ฌ์ฃผ๊ณ , ์๋๋ฉด ์ด์ ๋ณด๋๋ก ์ด๋ํฉ๋๋ค.import { createGlobalTheme, style } from '@vanilla-extract/css';
export const vars = createGlobalTheme(':root', {
color: {
main: '#ffa726',
mainDarker: '#f57c00',
mainFaded: '#ffb74d',
mainFadedBright: '#ffb74da6',
list: 'rgb(235,236,240)',
task: 'rgb(255,255,255)',
taskHover: 'rgb(245,245,245)',
brightText: 'rgb(255,255,255)',
darkText: 'rgb(24,42,77)',
secondaryDarkText: 'rgb(94,108,132)',
secondaryDarkTextHover: 'rgb(218,219,226)',
selectedTab: 'rgb(137,176,74)',
updateButton: 'rgb(237,180,88)',
deleteButton: 'rgb(237,51,88)',
},
fontSizing: {
T1: '32px',
T2: '24px',
T3: '18px',
T4: '14px',
P1: '12px',
},
spacing: {
small: '5px',
medium: '10px',
big1: '20px',
big2: '15px',
listSpacing: '30px',
},
font: {
body: 'arial',
},
shadow: {
basic: '4px 4px 8px 0px rgba(34, 60, 80, 0.2)',
},
minWidth: {
list: '250px',
},
});
export const appContainer = style({
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
height: 'max-content',
width: '100vw',
});
export const board = style({
display: 'flex',
flexDirection: 'row',
height: '100%',
});
export const buttons = style({
marginTop: 'auto',
paddingLeft: vars.spacing.big2,
});
export const deleteBoardButton = style({
border: 'none',
borderRadius: 5,
width: 'max-content',
marginTop: 'auto',
marginLeft: 'auto',
marginBottom: '30px',
fontSize: vars.fontSizing.T4,
padding: vars.spacing.big2,
backgroundColor: vars.color.mainFaded,
cursor: 'pointer',
opacity: 0.6,
minWidth: 150,
':hover': {
opacity: 0.8,
},
});
export const loggerButton = style({
border: 'none',
borderRadius: 5,
width: 'max-content',
marginTop: 'auto',
marginLeft: '15px',
marginRight: '30px',
marginBottom: '30px',
fontSize: vars.fontSizing.T4,
padding: vars.spacing.big2,
backgroundColor: vars.color.mainFaded,
cursor: 'pointer',
opacity: 0.6,
minWidth: 150,
':hover': {
opacity: 0.8,
},
});
@vanilla-extract/css ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด ์ ์ญ ํ
๋ง( createGlobalTheme )์ ๊ฐ ์ปดํฌ๋ํธ์ ์คํ์ผ( style )์ ํ์
์์ ํ๊ฒ ์ ์ํฉ๋๋ค.
createGlobalTheme : ์ ์ญ ํ
๋ง ๋ณ์๋ฅผ ์ ์ธํจ์ผ๋ก์จ vars.color.main์ ๋ฐฉ์์ผ๋ก ์ฌ์ฌ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค.์ ์ญ ํ ๋ง ์ค์ ๋๋ถ์ ๋์์ธ์ ์ฝ๊ฒํ ์ ์์ด์ ์ข์ ๊ฑฐ ๊ฐ๊ณ , useState์ Redux ์ฌ์ด์์ ์ด๋ค ๊ฑด ๋ญ ์จ์ผ ํ๋์ง ๋ ๊ณ ๋ฏผํด๋ณด๋ฉด์ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ ๊ฐ๋ค.
