Skip to content

MOAMOA FE Design System

TaeYoon edited this page Nov 1, 2022 · 17 revisions

๋ชจ์•„๋ชจ์•„์˜ ์ปดํฌ๋„ŒํŠธ ๊ตฌ์กฐ ๋ฐ ์Šคํƒ€์ผ๋ง์— ๋Œ€ํ•œ ์ปจ๋ฒค์…˜์ž…๋‹ˆ๋‹ค.

ํด๋”/์ปดํฌ๋„ŒํŠธ ๊ตฌ์กฐ ๋ฐ ๋ฐฐ์น˜

  • ๋ฌด๊ฑฐ์šด ์ปดํฌ๋„ŒํŠธ๋“ค์€ ํŒŒ์ผ์„ ๋ถ„๋ฆฌํ•œ๋‹ค.
    • ์นด๋“œ, ๋ฆฌ์ŠคํŠธ, ํผ ๋“ฑ
  • ๊ฐ€๋ฒผ์šด ์ปดํฌ๋„ŒํŠธ๋Š” ๋‚ด๋ถ€์— ๋†“๋Š”๋‹ค.
    • ๋ถ„๋ฆฌํ•˜๋ฉด ํด๋”๊ฐ€ ์ง€์ €๋ถ„ํ•ด์ง€๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  • ์Šคํƒ€์ผ ์ปดํฌ๋„ŒํŠธ๋Š” ๋‚ด๋ถ€์— ๋†“๋Š”๋‹ค.
    • ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌํ•˜๋ฉด 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๋ฅผ ํ™œ์šฉํ•ด ์ œํ•œ๋œ ์Šคํƒ€์ผ๋งŒ ๋„ฃ์„ ์ˆ˜ ์žˆ๋„๋ก ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค.

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์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Layout Component

Flex ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ ˆ์ด์•„์›ƒ์— ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.

JSX ๊ฐ€๋…์„ฑ ๋†’์ด๊ธฐ

์Šคํƒ€์ผ ์ •๋ณด ์ˆจ๊ธฐ๊ธฐ

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>
);

custom-css์— ๋Œ€ํ•œ ๊ณต์›์˜ ํ”ผ๋“œ๋ฐฑ

  • ์ปดํฌ๋„ŒํŠธ์˜ ๋ณธ์งˆ์ด ๋˜๋Š” ์†์„ฑ์€ ์ฃผ๊ด€์ ์ด๋‹ค
  • ์ด ๋””์ž์ธ ์‹œ์Šคํ…œ์„ ๋ชจ๋ฅด๋Š” ์‚ฌ๋žŒ์ด ์™€๋„ ์ž˜ ์“ธ ์ˆ˜ ์žˆ์„๊นŒ? => ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ์—๋Š” fontSize๊ฐ€ ๋ณธ์งˆ์ ์ธ ์†์„ฑ์ด๊ณ , ์–ด๋–ค ๊ฒƒ์€ custom์— ๋„ฃ์–ด์•ผ ํ•œ๋‹ค. ์™œ ์ด๊ฒƒ์€ ๋ณธ์งˆ์ด๊ณ , ์ €๊ฒƒ์€ ์•„๋‹Œ์ง€ ์ฝ”๋“œ๋งŒ ๋ณด๊ณ  ํŒ๋‹จํ•˜๊ธฐ ์–ด๋ ต๋‹ค. ๋‹ค์‹œ ๋งํ•ด์„œ ๋‚ฉ๋“ ์•ˆ ๋  ์ˆ˜๋„ ์žˆ๋‹ค.

๋ณ‘๋ฏผ์˜ ์ƒ๊ฐ

  • Storybook์— ์„ค๋ช…์„ ๋„ฃ์œผ๋ฉด ๋˜์ง€ ์•Š์„๊นŒ?
  • ๋„ˆ๋ฌด ์ž์œ ๋ฅผ ์ฃผ๋Š” ๊ฒƒ๋ณด๋‹ค๋Š” ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค!
  • ์ฃผ๊ด€์ด ๋งŽ์ด ๋“ค์–ด๊ฐ€๋Š” ๊ฑด ๋งž๋‹ค!

ํƒœํƒœ ์ƒ๊ฐ

  • '๋ชจ์•„๋ชจ์•„' ํ”„๋กœ์ ํŠธ๋ฅผ ์œ„ํ•œ ๋””์ž์ธ์‹œ์Šคํ…œ์ด๋ฏ€๋กœ ์ฃผ๊ด€์ ์ธ ๊ฑด ๋‹น์—ฐํ•˜๋‹ค.
  • ๊ทธ๋Ÿฌ๋‚˜, ํŒ€์› ๊ฐ„์—๋„ ์˜๊ฒฌ ์ฐจ์ด๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ปค์Šคํ…€ํ•  ํ•„์š” ์—†๋Š” '๊ธฐ๋ณธ ์†์„ฑ'์— ๋Œ€ํ•ด ๋‹ค์‹œ ํ•œ ๋ฒˆ ์ด์•ผ๊ธฐ๋ฅผ ๋‚˜๋ˆ„๋Š” ๊ฒŒ ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

์ฐธ๊ณ 

๋””์Šค์ปค์…˜

๊พธ์ค€ํžˆ ๋” ์ข‹์€ ๊ตฌ์กฐ๋ฅผ ์ฐพ๊ธฐ ์œ„ํ•ด ๋…ธ๋ ฅ ์ค‘์ž…๋‹ˆ๋‹ค.