-
Notifications
You must be signed in to change notification settings - Fork 5
MOAMOA FE Design System
๋ชจ์๋ชจ์์ ์ปดํฌ๋ํธ ๊ตฌ์กฐ ๋ฐ ์คํ์ผ๋ง์ ๋ํ ์ปจ๋ฒค์ ์ ๋๋ค.
- ๋ฌด๊ฑฐ์ด ์ปดํฌ๋ํธ๋ค์ ํ์ผ์ ๋ถ๋ฆฌํ๋ค.
- ์นด๋, ๋ฆฌ์คํธ, ํผ ๋ฑ
- ๊ฐ๋ฒผ์ด ์ปดํฌ๋ํธ๋ ๋ด๋ถ์ ๋๋๋ค.
- ๋ถ๋ฆฌํ๋ฉด ํด๋๊ฐ ์ง์ ๋ถํด์ง๊ธฐ ๋๋ฌธ์ ๋๋ค.
- ์คํ์ผ ์ปดํฌ๋ํธ๋ ๋ด๋ถ์ ๋๋๋ค.
- ํ์ผ๋ก ๋ถ๋ฆฌํ๋ฉด S.Component ํน์ Styled.Component ์ด๋ฐ prefix๊ฐ ํ์ํ๋ฐ, ์ด ๋ถ๋ถ์ด ๊ฐ๋ ์ฑ์ ํด์น๊ธฐ ๋๋ฌธ์ ๋๋ค.
- ๊ณตํต ์ปดํฌ๋ํธ๋ src/components/@shared์ ๋ฐฐ์นํ๋ค.
- ๊ณตํต ์ปดํฌ๋ํธ์๋ ๋๋ฉ์ธ ์ ๋ณด๊ฐ ์์ด์ผ ํฉ๋๋ค.
- ํ์ด์ง๊ฐ์ ์ค๋ณต ์ฌ์ฉ๋๋ ์ปดํฌ๋ํธ ์ค
- ๋๋ฉ์ธ์ด ๊ฐ์ ์ปดํฌ๋ํธ๋ src/components์ ๋ฐฐ์นํ๋ค.
- ๋๋ฉ์ธ์ด ๋ค๋ฅธ ๊ฒฝ์ฐ์๋ ๊ณตํต ์ปดํฌ๋ํธ๋ก ๋ถ๋ฆฌํ ์ ์๋์ง ์ ๊ฒํ๊ณ , ๊ทธ๋ด ์ ์๋ค๋ฉด ์ค๋ณต์ ํ์ฉํ๋ค.
import Button from '@shared/button';
import List from '@shared/list';
// ์ปดํฌ๋ํธ์ props ํ์
์ ๋งจ ์์ ๋ก๋๋ค.
export type StudyListProps = {
children: React.ReactNode;
variant?: 'primary' | 'secondary';
onAddButtonClick: React.MouseEventHandler<HTMLButtonElement>;
onRemoveButtonClick: React.MouseEventHandler<HTMLButtonElement>;
};
// Component๋ ํ์ดํ ํจ์๋ก ํํํ๊ณ , React.FC<Props>๋ก ํ์
์ ๋ช
์ํฉ๋๋ค.
// - props์ return type์ ๊ฐ๊ฒฐํ๊ฒ ๊ฐ์ ํ ์ ์์ต๋๋ค.
const StudyList: React.FC<StudyListProps> = ({
children,
variant = 'primary',
onAddButtonClick: handleAddButtonClick,
onRemoveButtonClick: handleRemoveButtonClick,
}) => {
return (
<Self>
<List>
<List.Item>๋ฆฌํฉํ ๋ง ์คํฐ๋</List.Item>
<List.Item>ํ๋ก ํธ ์คํฐ๋</List.Item>
<List.Item>์๋ฐ ์คํฐ๋</List.Item>
</List>
<AddButton onClick={handleAddButtonClick}>์ถ๊ฐํ๊ธฐ</AddButton>
<RemoveButton onClick={handleRemoveButtonClick}>์ญ์ ํ๊ธฐ</RemoveButton>
</Self>
);
};
export default StudyList;
// styled component๋ component ํ์ผ์ ๊ฐ์ด ๋ก๋๋ค. ์๋ํ๋ฉด,
// 1. ์์ง์ฑ -> ์์์ ๋ฐ๋ก ์ฌ์ฉํ๋ ์ปดํฌ๋ํธ๋ฅผ ๋ฐ๋ก ์๋์ชฝ์ ๋ ์ผ๋ก์จ ์์ฃผ ์ฝ๊ฐ์ด์ง๋ง ์์ง๋๋ฅผ ๋์
๋๋ค.
// 2. ์บก์ํ -> ํ์ฌ ์ปดํฌ๋ํธ์์๋ง ์ฌ์ฉํ๋ ํ์ ์ปดํฌ๋ํธ(Self, AddStudyButton, RemoveButton)๋ ์ธ๋ถ์ ๋
ธ์ถ(export)ํ์ง ์์ต๋๋ค.
// 3. styled ํ์ผ์ ๋ฐ๋ก ๋ถ๋ฆฌํ๋ฉด, VSC์์ ๊ฒ์ํ ๋ ๋ถํธํ๊ณ ์ธ๋ถ์ ๋
ธ์ถ๋ฉ๋๋ค.
// ์คํ์ผ์ด ์ ์ฉ๋ ๊ฐ์ฅ ๋ฐ๊นฅ ์ปดํฌ๋ํธ๋ Self๋ก ๋ช
๋ช
ํฉ๋๋ค.
const Self = styled.div`
${({ theme }) => css`
padding: 10px;
max-width: 500px;
border: 2px solid ${theme.color.black}; // color๋ theme์ ์ ์ํ color๋ฅผ ์ฌ์ฉํฉ๋๋ค.
`}
`;
// ๊ณตํต ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ ๋๋ ์คํ์ผ ์์ฑ์ ๊ฐ๋ฆฌ๊ณ ๋๋ฉ์ธ ์ ๋ณด๋ฅผ ์
ํ๋๋ค.
// Divider, Flex, PageTitle ๋ฑ ๋ ์ด์์ ๊ด๋ จ ์ปดํฌ๋ํธ๋ ์ ์ธํฉ๋๋ค.
type AddButtonProps = {
children: React.ReactNode;
onClick: React.MouseEventHandler<HTMLButtonElement>;
};
const AddButton: React.FC<AddButtonProps > = ({ children, onClick: handleClick }) => (
<Button variant="primary" onClick={handleClick}>
{children}
</Button>
);
type RemoveButtonProps = AddButtonProps;
const RemoveButton: React.FC<RemoveButtonProps > = ({ children, onClick: handleClick }) => (
<Button variant="danger" onClick={handleClick}>
{children}
</Button>
);
- props์ ๋ํ ์ ์ด๋ฅผ ์ํด ๊ฐ๊ธ์ styled component๋ฅผ ์ฌ์ฉํ์ง ์์ต๋๋ค.
- styled์ ์ผ๋ฐ ์ปดํฌ๋ํธ๋ฅผ ๊ตฌ๋ณํ ํ์๊ฐ ์์ด์ง๋๋ค.
- ๊ณตํต ์ปดํฌ๋ํธ๋
custom-css
๋ฅผ ํ์ฉํด ์ ํ๋ ์คํ์ผ๋ง ๋ฃ์ ์ ์๋๋ก ๊ฐ์ ํฉ๋๋ค.
๊ณตํต ์ปดํฌ๋ํธ์ ๋ค์ด๊ฐ ์ ์๋ css ์์ฑ์ ์ ํํฉ๋๋ค. ์์ ๋ฅผ ์ ํํด ์คํ์ผ์ ์ผ๊ด์ฑ์ ์งํค๊ธฐ ์ํจ์ ๋๋ค.
type ButtonProps = {
children: React.ReactNode;
type: 'submit' | 'button';
fluid?: boolean;
disabled?: boolean;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
custom?: CustomCSS<'marginBottom'>;
};
const Button: React.FC<ButtonProps> = ({ children, type = 'button', disabled, onClick, custom }) => {
<button css={resolveCustomCSS(custom)} disabled={disabled} type={type} onClick={onClick}>
{children}
</button>;
};
-
custom-css
๊ตฌํ๋ถ๋ custom-css.ts์์ ํ์ธํ ์ ์์ต๋๋ค.
Flex ์ปดํฌ๋ํธ๋ฅผ ๋ ์ด์์์ ํ์ฉํฉ๋๋ค.
JSX์ ๊ฐ๋ฅํ ์คํ์ผ ์ ๋ณด๋ ๋ณด์ฌ์ฃผ์ง ์์ต๋๋ค. ์ญํ ๋ฐ ๊ธฐ๋ฅ์ด ๋ ์ ๋๋ฌ๋๊ธฐ ๋๋ฌธ์ ๋๋ค.
์๋ฅผ ๋ค์ด,
<Button type="button" variant="secondary" custom={{marginBottom: '10px'}} onClick={handleAddButtonClick}>
์ถ๊ฐํ๊ธฐ
</Button>
// vs
<AddButton onClick={handleAddButtonClick} />
์ ์๋ณด๋ค ํ์ ๋ฐฉ์์ ์ ํธํฉ๋๋ค. ์ฆ, ๊ฐ๋ฅํ ๋๋ฉ์ธ์ ์ ํ๊ณ ์คํ์ผ ์ ๋ณด๋ฅผ ์จ๊น๋๋ค. ์ด ๋ฐฉ์์ผ๋ก ๊ตฌํํ๋ฉด ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ ์ด๋ค ๋ฒํผ์ธ์ง ํ๋์ ํ์ ํ๊ธฐ ์ฝ์ต๋๋ค.
์กฐ๊ฑด๋ถ ๋ ๋๋ง์ ์ฆ์ ์คํ ํจ์๋ฅผ ํ์ฉํฉ๋๋ค.
ํจ์ ๋ถ๋ฆฌ๋ณด๋ค ์์ง๋๊ฐ ๋๊ณ , ์ผํญ ์ฐ์ฐ์๋ณด๋ค ๊ฐ๋ ์ฑ์ด ์ข๊ธฐ ๋๋ฌธ์ ๋๋ค.
return (
<Card backgroundColor={theme.colors.white} shadow custom={{ padding: '40px', gap: '8px' }}>
<Card.Heading custom={{ fontSize: 'xl', marginBottom: '10px' }}>
{(() => {
if (isRegistered) return <AlreadyRegistered />;
if (!isOpen) return <Closed />;
if (!enrollmentEndDate) return <Open />;
return <EnrollmentEndDate theme={theme} enrollmentEndDate={enrollmentEndDate} />;
})()}
</Card.Heading>
<Card.Content custom={{ fontSize: 'lg' }}>
...
</Card.Content>
</Card>
);
- ์ปดํฌ๋ํธ์ ๋ณธ์ง์ด ๋๋ ์์ฑ์ ์ฃผ๊ด์ ์ด๋ค
- ์ด ๋์์ธ ์์คํ ์ ๋ชจ๋ฅด๋ ์ฌ๋์ด ์๋ ์ ์ธ ์ ์์๊น? => ์ด๋ค ์ปดํฌ๋ํธ์๋ fontSize๊ฐ ๋ณธ์ง์ ์ธ ์์ฑ์ด๊ณ , ์ด๋ค ๊ฒ์ custom์ ๋ฃ์ด์ผ ํ๋ค. ์ ์ด๊ฒ์ ๋ณธ์ง์ด๊ณ , ์ ๊ฒ์ ์๋์ง ์ฝ๋๋ง ๋ณด๊ณ ํ๋จํ๊ธฐ ์ด๋ ต๋ค. ๋ค์ ๋งํด์ ๋ฉ๋ ์ ๋ ์๋ ์๋ค.
- Storybook์ ์ค๋ช ์ ๋ฃ์ผ๋ฉด ๋์ง ์์๊น?
- ๋๋ฌด ์์ ๋ฅผ ์ฃผ๋ ๊ฒ๋ณด๋ค๋ ์ข์ ๊ฒ ๊ฐ๋ค!
- ์ฃผ๊ด์ด ๋ง์ด ๋ค์ด๊ฐ๋ ๊ฑด ๋ง๋ค!
- '๋ชจ์๋ชจ์' ํ๋ก์ ํธ๋ฅผ ์ํ ๋์์ธ์์คํ ์ด๋ฏ๋ก ์ฃผ๊ด์ ์ธ ๊ฑด ๋น์ฐํ๋ค.
- ๊ทธ๋ฌ๋, ํ์ ๊ฐ์๋ ์๊ฒฌ ์ฐจ์ด๊ฐ ์์ ์ ์์ผ๋ฏ๋ก ์ปค์คํ ํ ํ์ ์๋ '๊ธฐ๋ณธ ์์ฑ'์ ๋ํด ๋ค์ ํ ๋ฒ ์ด์ผ๊ธฐ๋ฅผ ๋๋๋ ๊ฒ ์ข์ ๊ฒ ๊ฐ๋ค.
๊พธ์คํ ๋ ์ข์ ๊ตฌ์กฐ๋ฅผ ์ฐพ๊ธฐ ์ํด ๋ ธ๋ ฅ ์ค์ ๋๋ค.