Skip to content

MOAMOA FE Design System

TaeYoon edited this page Oct 4, 2022 · 17 revisions

๋ชจ์•„๋ชจ์•„์˜ ๋””์ž์ธ ์‹œ์Šคํ…œ์„ ์ •๋ฆฝํ•ฉ๋‹ˆ๋‹ค.

Layout Components

Flex

flex์™€ ๊ด€๋ จ๋œ props๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.
ex) alignItems, justifyContens ๋“ฑ

Grid

grid์™€ ๊ด€๋ จ๋œ props๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.
ex) columns, columnGap, rows, rowGap ๋“ฑ

Responsive

Flex์™€ Grid๋Š” mediaQuery์†์„ฑ์„ ๊ฐ์ฒด๋กœ ๋ฐ›์Šต๋‹ˆ๋‹ค.

Wrapper Component

const Wrapper = (children, width, height, padding, margin, border ... ๋“ฑ์˜ ๋‹ค์–‘ํ•œ props) => <div>{children}</div>;

Common Components

๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ๋Š” ์ž์ฃผ ์‚ฌ์šฉ๋ ๊ฒƒ๋“ค๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
์ปดํฌ๋„ŒํŠธ ๊ฐœ๋ฐœ์‹œ props๋งŒ ์ž˜ ์ฃผ๋ฉด ๋‹ค๋ฅธ๊ณณ์—์„œ๋„ ์“ธ ์ˆ˜ ์žˆ์„๊ฒƒ ๊ฐ™์„๋•Œ ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ๋กœ ์Šน๊ฒฉ์‹œํ‚ต๋‹ˆ๋‹ค.

Component Structure

์˜ˆ๋ฅผ๋“ค์–ด ์ปดํฌ๋„ŒํŠธ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

import Button from '@components/button';
import UnorderedList from '@components/unorderd-list';

// Props๋ฅผ ๋งจ ์œ„์— ๋‘ก๋‹ˆ๋‹ค
export type StudyListProps = {
  children?: React.ReactNode;
  variant?: 'primary' | 'secondary';
  onAddButtonClick: React.MouseEventHandler<HTMLButtonElement>;
  onRemoveButtonClick: React.MouseEventHandler<HTMLButtonElement>;
};

// Component๋Š” ํ™”์‚ดํ‘œ ํ•จ์ˆ˜๋กœ ํ‘œํ˜„ํ•˜๊ณ , React.FC<Props>๋กœ ํƒ€์ž…์„ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค
// - return type์„ ๋” ์‰ฝ๊ฒŒ ๊ฐ•์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
const StudyList: React.FC<StudyListProps> = ({
  children,
  variant,
  onAddButtonClick: handleAddButtonClick,
  onRemoveButtonClick: handleRemoveButtonClick,
}) => {
  return (
    <Self>
      <List>
        <Item>๋ฆฌํŒฉํ† ๋ง ์Šคํ„ฐ๋””</Item>
        <Item>ํ”„๋ก ํŠธ ์Šคํ„ฐ๋””</Item>
        <Item>์ž๋ฐ” ์Šคํ„ฐ๋””</Item>
      </List>
      <AddButton onClick={handleAddButtonClick}>์ถ”๊ฐ€ํ•˜๊ธฐ</AddButton>
      <RemoveButton onClick={handleRemoveButtonClick}>์‚ญ์ œํ•˜๊ธฐ</RemoveButton>
    </Self>
  );
};

export default StudyList;

// styled component๋Š” component์— ๊ฐ™์ด ๋‘ก๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด
// 1. ์‘์ง‘์„ฑ -> ์œ„์—์„œ ๋ฐ”๋กœ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋“ค์„ ๋ฐ”๋กœ ์•„๋ž˜์ชฝ์— ๋‘ ์œผ๋กœ์จ ์•„์ฃผ ์•ฝ๊ฐ„์ด์ง€๋งŒ ์‘์ง‘์„ฑ์„ ๋†’์ž…๋‹ˆ๋‹ค.
// 2. ์บก์Аํ™” -> ์ด ์ปดํฌ๋„ŒํŠธ์—์„œ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ํ•˜์œ„์ปดํฌ๋„ŒํŠธ(Self, StudyList, AddStudyButton ..)๋“ค์€ ์™ธ๋ถ€์— ๋…ธ์ถœํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
// 3. styled ํŒŒ์ผ์„ ๋”ฐ๋กœ ๋งŒ๋“ค๋ฉด, VSC์—์„œ ๊ฒ€์ƒ‰ํ• ๋•Œ ์กฐ๊ธˆ ๋ถˆํŽธํ•ฉ๋‹ˆ๋‹ค.

// Self๋ฅผ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ƒ์†๋ฐ›์•„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, (์‚ฌ์šฉํ•˜๋Š”์ชฝ์—์„œ ์ด๊ณณ์˜) Component๋ฅผ importํ•ด์„œ ์ƒ์† ๋ฐ›์œผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.
// ๊ฐ€์žฅ ๋ฐ”๊นฅ ์ปดํฌ๋„ŒํŠธ๋Š” Self๋กœ ๋ช…๋ช…ํ•ฉ๋‹ˆ๋‹ค.
// ์›๋ž˜๋ผ๋ฉด S.StudyList๋ผ๊ณ  ํ•˜๋Š”๋ฐ S๋ฅผ ๋นผ๊ณ  ์‹ถ์€๋ฐ ๋นผ๋ฉด ์ปดํฌ๋„ŒํŠธ์™€ ์ด๋ฆ„์ด ๊ฐ™์•„์ง€๊ธฐ ๋•Œ๋ฌธ์— Self๋กœ ๋ช…๋ช…ํ–ˆ์Šต๋‹ˆ๋‹ค.
const Self = styled.div`
  ${({ theme }) => css`
    padding: 10px;
    max-width: 500px;
    border: 2px solid ${theme.color.black}; // color๋Š” theme์— ์ •์˜ํ•œ color๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค
  `}
`;

type ListProps = {
  theme: Theme;
  children: React.ReactNode;
};
const List: React.FC<ListProps> = ({ theme, children }) => (
  // custom props๋Š” ํ—ˆ์šฉ๋œ css property๋งŒ ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  // css props๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ์•„๋ฌด๊ฑฐ๋‚˜ ๋„ฃ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋””์ž์ธ์„ ๊ฐ•์ œํ•  ์ˆ˜ ์—†๊ฒŒ๋ฉ๋‹ˆ๋‹ค.
  <UnorderedList custom={{ marginBottom: '20px', backgroundColor: theme.colors.green }}>{children}</UnorderedList>
);

type ItemProps = {
  theme: Theme;
  children: React.ReactNode;
};
const Item: React.FC<ItemProps> = ({ theme, children }) => (
  <UnorderedList.Item custom={{ padding: '4px' }}>{children}</UnorderedList.Item>
);

type AddButtonProps = {
  children: React.ReactNode;
  onClick: React.MouseEventHandler<HTMLButtonElement>;
};

// ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ๋Š” ์ด๋ ‡๊ฒŒ ๋„๋ฉ”์ธ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ž…ํ˜€์„œ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.
const AddButton = ({ children, onClick: handleClick }: AddButtonProps) => (
  <Button custom={{ marginBottom: '20px' }} variant="primary" onClick={handleClick}>
    {children}
  </Button>
);

type RemoveButtonProps = AddButtonProps;
const RemoveButton = ({ children, onClick: handleClick }: RemoveButtonProps) => (
  <Button custom={{ marginBottom: '30px' }} variant="danger" onClick={handleClick}>
    {children}
  </Button>
);

custom props

custom prop์€ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์˜ ํ•ต์‹ฌ์ด ์•„๋‹Œ ์Šคํƒ€์ผ์„ ๋„ฃ์„๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
๋‹จ!! ์•„๋ฌด ์Šคํƒ€์ผ์ด๋‚˜ ๋„ฃ๋Š”๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ—ˆ์šฉํ•œ ์Šคํƒ€์ผ๋งŒ ๋„ฃ์„ ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { css } from '@emotion/react';
import { type CSSProperties as OriginalCSSProperties } from '@emotion/serialize';

import { BreakPoint, BreakpointsFor, mqDown } from '@styles/responsive';

type KeyOfOriginalCSSProperties = keyof OriginalCSSProperties;

export type PickAllowedCSSProperties<CSSProperties extends KeyOfOriginalCSSProperties> = Pick<
  OriginalCSSProperties,
  CSSProperties
>;

export type CSSPropertyWithValue<AllowedCSSProperties extends KeyOfOriginalCSSProperties> = {
  [K in keyof PickAllowedCSSProperties<AllowedCSSProperties>]: OriginalCSSProperties[K];
};

export type ResponsiveCSSPropertyWithValue<AllowedCSSProperties extends KeyOfOriginalCSSProperties> = BreakpointsFor<
  CSSPropertyWithValue<AllowedCSSProperties>
>;

export type CustomCSS<AllowedCSSProperties extends KeyOfOriginalCSSProperties> =
  CSSPropertyWithValue<AllowedCSSProperties> & { responsive?: ResponsiveCSSPropertyWithValue<AllowedCSSProperties> };

export const getResponsiveStyle = <AllowedCSSProperties extends KeyOfOriginalCSSProperties>(
  breakPoint: BreakPoint,
  styleObject: CSSPropertyWithValue<AllowedCSSProperties>,
) => {
  return css`
    ${mqDown(breakPoint)} {
      ${styleObject}
    }
  `;
};

export const resolveCustomCSS = <AllowedCSSProperties extends KeyOfOriginalCSSProperties>(
  custom?: CustomCSS<AllowedCSSProperties>,
) => {
  if (!custom) return css``;
  const { responsive, ...defaultStyle } = custom;

  if (responsive) {
    const { xs, sm, md, lg, xl, xxl, xxxl } = responsive;
    const xsStyle = xs && getResponsiveStyle<AllowedCSSProperties>('xs', xs);
    const smStyle = sm && getResponsiveStyle<AllowedCSSProperties>('sm', sm);
    const mdStyle = md && getResponsiveStyle<AllowedCSSProperties>('md', md);
    const lgStyle = lg && getResponsiveStyle<AllowedCSSProperties>('lg', lg);
    const xlStyle = xl && getResponsiveStyle<AllowedCSSProperties>('xl', xl);
    const xxlStyle = xxl && getResponsiveStyle<AllowedCSSProperties>('xxl', xxl);
    const xxxlStyle = xxxl && getResponsiveStyle<AllowedCSSProperties>('xxxl', xxxl);

    return css`
      ${defaultStyle}
      // ์šฐ์„ ์ˆœ์œ„๊ฐ€ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค!! xxxl -> xs ์ˆœ์œผ๋กœ ๋†“์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค
      ${xxxlStyle}
      ${xxlStyle}
      ${xlStyle}
      ${lgStyle}
      ${mdStyle}
      ${smStyle}
      ${xsStyle}
    `;
  }

  return css`
    ${defaultStyle}
  `;
};

Button ์ปดํฌ๋„ŒํŠธ๋Š” ์ด๋ ‡๊ฒŒ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค

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

๊ณต์›์˜ ํ”ผ๋“œ๋ฐฑ

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

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

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

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

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

์ฐธ๊ณ 

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

Clone this wiki locally