๐Ÿ’ป์›น(Web)/React

[React]์ปดํฌ๋„ŒํŠธ์˜ ์žฌ์‚ฌ์šฉ์„ฑ/์ผ๊ด€์„ฑ

stonesy 2024. 6. 25. 22:37
728x90

๐Ÿ”Ž์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ & ์ผ๊ด€์„ฑ ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ ๋งŒ๋“ค๊ธฐ

๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๊ฒŒ + ์ผ๊ด€์„ฑ ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด๋ณด์ž.

 

1. ๋ชฉํ‘œ/์š”๊ตฌ์‚ฌํ•ญ

1) ๋ฒ„ํŠผ์˜ shape์€ ํ•ญ์ƒ ๋™์ผํ•ด์•ผ ํ•œ๋‹ค.

๋ฒ„ํŠผ์˜ ์ „์ฒด์ ์ธ ๋ชจ์–‘์€ ๋ณ€ํ•˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค. ์ฆ‰, ์–ด๋–ค ํฌ๊ธฐ๋ฅผ ๊ฐ–๋“ , ์–ด๋–ค ์ƒ‰์„ ๊ฐ–๋˜์ง€ ๊ฐ„์— border-radius, padding ๊ฐ’์— ๋Œ€ํ•œ ์†์„ฑ๋“ค์€ ๊ณ ์ •๋˜์–ด ์žˆ์–ด์•ผ ํ•œ๋‹ค.

 

2) variant props์„ ํ†ตํ•ด preset๋œ ์ƒ‰์ƒ์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

์ผ๊ด€์„ฑ ์žˆ๋Š” ๋””์ž์ธ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์ œํ•œ์ ์œผ๋กœ props๋ฅผ ํ†ตํ•ด ์ •์˜๋œ ์Šคํƒ€์ผ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ ๋‹ค.

๋ฏธ๋ฆฌ ์ •์˜๋œ ์ƒ‰์„ variant๋ผ๋Š” props์„ ์ด์šฉํ•˜์—ฌ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ ๋‹ค.

 

3) size props๋ฅผ ํ†ตํ•ด preset๋œ ํฌ๊ธฐ๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

์ƒ‰์ƒ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ size ์—ญ์‹œ ์ผ๊ด€์„ฑ ์žˆ๋Š” ๋””์ž์ธ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด, ๋ฏธ๋ฆฌ ์ •์˜๋œ ์Šคํƒ€์ผ์„ size๋ผ๋Š” props์„ ์ด์šฉํ•˜์—ฌ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

 

4) ๋ฒ„ํŠผ hover, active, disabled์— ๋Œ€ํ•œ ๊ฐ๊ฐ์˜ ์Šคํƒ€์ผ์ด ์กด์žฌํ•˜๋ฉฐ, ์‚ฌ์šฉ์ž๊ฐ€ ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ ํด๋ฆญํ•˜๋Š” ๋Š๋‚Œ์ด ๋“ค์–ด์•ผ ํ•œ๋‹ค. disabled ๋˜์—ˆ์„ ๋•Œ๋Š” disabled ๋˜์—ˆ๋‹ค๋Š” ๋Š๋‚Œ์ด ๋“ค์–ด์•ผ ํ•œ๋‹ค.

๋”ฐ๋ผ์„œ variant์— ๋งž๋„๋ก ์ ์ ˆํ•œ ์ƒ‰์ƒ์„ :hover, :active, :diabled ๋“ฑ ๋‹ค์–‘ํ•œ ๊ฒฝ์šฐ์— ๋งž๊ฒŒ ๋ฐฐ์น˜ํ•ด์•ผ ํ•œ๋‹ค.

์žฌ์‚ฌ์šฉ์„ฑ์„ ๋†’์ด๊ธฐ ์œ„ํ•ด์„œ๋Š” ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋„๋ฉ”์ธ์— ์–ฝํžˆ์ง€ ์•Š์•„์•ผ ํ•˜๋ฉฐ, props, attributes์˜ ์ด๋ฆ„๋“ค์ด ์ผ๋ฐ˜์ ์ด์–ด์•ผ ํ•œ๋‹ค.

 

2. ์ฝ”๋“œ๋กœ ์ดํ•ด

React.js + styled-components๋ฅผ ์ด์šฉํ•ด์„œ ๋งŒ๋“ค์–ด๋ณด์•˜๋‹ค.

*์ „์ฒด ButtonBase ์ฝ”๋“œ

import React from 'react';
import styled, { css } from "styled-components";
import { get500Color, get600Color, get700Color, get300Color } from '../../utils/color';
import { DEFAULT_FONT_SIZES } from "../../utils/font";

// ๋ฒ„ํŠผ ์Šคํƒ€์ผ ์ •์˜
const buttonRoleStyle = css`
  ${({ variant = 'default', disabled }) => css`
    background-color: ${get500Color(variant)};
    color: ${get500Color()};

    &:hover {
      background-color: ${get600Color(variant)};
    }

    &:active {
      background-color: ${get700Color(variant)};
    }

    &:disabled {
      background-color: ${get300Color(variant)};
      pointer-events: none;
      cursor: ${disabled ? 'default' : 'pointer'};
    }
  `}
`;

const sizeStyle = css`
  ${({ size = 'md' }) => {
    if (size === 'sm') {
      return css`
        padding: 8px 10px;
        font-size: ${DEFAULT_FONT_SIZES.b2}px;
      `;
    }

    if (size === 'lg') {
        return css`
        padding: 12px 48px;
        font-size: ${DEFAULT_FONT_SIZES.b2}px;
      `;
    }

    if (size === 'xl') {
      return css`
        padding: 12px 60px;
        font-size: ${DEFAULT_FONT_SIZES.b1}px;
      `;
    }

    // ๊ธฐ๋ณธ์ ์œผ๋กœ 'md'์ผ ๋•Œ์˜ ์Šคํƒ€์ผ (default)
    return css`
      padding: 10px 12px;
      font-size: ${DEFAULT_FONT_SIZES.b2}px;
    `;
  }}
`;

const ButtonBase = styled.button`
    display: inline-flex;
    gap: 4px;
    justify-content: center;
    align-items: center;
    vertical-align: center;
    position: relative;
    min-width: 64px;
    border: none;
    border-radius: 6px;
    padding: 10px 12px;
    cursor: pointer;

    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;

    /* button์˜ Content๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์—†๋„๋ก ํ•œ๋‹ค.*/
    user-select: none;  

    transition: background-color 0.1s ease;

    /* ๋ฐฐ๊ฒฝ์ƒ‰ ์Šคํƒ€์ผ ์ ์šฉ */
    ${buttonRoleStyle}
    /* ์‚ฌ์ด์ฆˆ ์Šคํƒ€์ผ ์ ์šฉ */
    ${sizeStyle}
`;

function Button(props) {
    return <div>
        <ButtonBase variant={props.variant} size={props.size} disabled={props.disabled}>{props.label}</ButtonBase>
    </div>;
}

export default Button;

⇒๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉ

import Button from "./components/common/ButtonBase";

function App() {
  return (
    <div>
      <Button label="๊ธฐ๋ณธ๋ฒ„ํŠผ" variant="primary" size="lg"></Button>
      <Button label="์—๋Ÿฌ๋ฒ„ํŠผ" variant="error"></Button>
      <Button label="๋น„ํ™œ์„ฑํ™”๋ฒ„ํŠผ" variant="warning" disabled="disabled"></Button>
    </div>
  );
}

export default App;

 

3. Storybook

Storybook์ด๋ž€ UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ๋„๊ตฌ์ด๋‹ค.

๐Ÿ”งReact์— Storybook ์ ์šฉ๋ฐฉ๋ฒ•

1๋‹จ๊ณ„: Storybook ์„ค์น˜

React ํ”„๋กœ์ ํŠธ์˜ ๋ฃจํŠธ ๋””๋ ‰ํ„ฐ๋ฆฌ์—์„œ ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜์—ฌ Storybook์„ ์„ค์น˜ํ•œ๋‹ค.

npm install -g @storybook/cli
getstorybook -V //๋ฒ„์ „ ํ™•์ธ
getstorybook init

2๋‹จ๊ณ„: ์Šคํฌ๋ฆฝํŠธ ์„ค์ •

package.json ํŒŒ์ผ์„ ์—ด๊ณ , ๋‹ค์Œ๊ณผ ๊ฐ™์ด scripts ์„น์…˜์— Storybook์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

"scripts": {
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook"
}

3๋‹จ๊ณ„: Storybook ๊ตฌ์„ฑํ•˜๊ธฐ

.storybook ํด๋” ์•ˆ์— main.js ํŒŒ์ผ์„ ํ†ตํ•ด Storybook์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด ํŒŒ์ผ์—์„œ ํ”„๋กœ์ ํŠธ์˜ ์Šคํ† ๋ฆฌ ํŒŒ์ผ๋“ค์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋Š” ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•œ๋‹ค.

javascript์ฝ”๋“œ ๋ณต์‚ฌ
module.exports = {
  stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'],
};

4๋‹จ๊ณ„: ์Šคํ† ๋ฆฌ ์ž‘์„ฑํ•˜๊ธฐ

์ปดํฌ๋„ŒํŠธ ๋ณ„๋กœ ์Šคํ† ๋ฆฌ ํŒŒ์ผ์„ ์ž‘์„ฑํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, Button ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ์Šคํ† ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๋ ค๋ฉด ButtonBase.stories.jsx ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

์œ„์น˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

// src/ButtonBase.stories.js

import React from "react";
import Button from "../components/common/ButtonBase";

export default {
  title: "Components/Button",
  component: Button,
  argTypes: {
    variant: {
      control: {
        type: "select",
        options: ["default", "primary", "secondary"], // variant ์˜ต์…˜์— ๋งž๊ฒŒ ์ˆ˜์ •
      },
    },
    size: {
      control: {
        type: "select",
        options: ["sm", "md", "lg", "xl"],
      },
    },
    disabled: {
      control: "boolean",
    },
    label: {
      control: "text",
    },
  },
};

const Template = (args) => <Button {...args} />;

export const Default = Template.bind({});
Default.args = {
  variant: "default",
  size: "md",
  disabled: false,
  label: "Button",
};

export const Primary = Template.bind({});
Primary.args = {
  variant: "primary",
  size: "md",
  disabled: false,
  label: "Primary Button",
};

export const Large = Template.bind({});
Large.args = {
  variant: "error",
  size: "lg",
  disabled: false,
  label: "Large ERROR Button",
};

export const Disabled = Template.bind({});
Disabled.args = {
  variant: "default",
  size: "md",
  disabled: true,
  label: "Disabled Button",
};

*Storybook ๋ฌธ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

1) export default ๊ฐ์ฒด

: Stroybook์—์„œ์˜ ์ปดํฌ๋„ŒํŠธ ์„ค์ •์„ ์˜

  • title: Storybook ์‚ฌ์ด๋“œ๋ฐ”์—์„œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ด๋–ป๊ฒŒ ํ‘œ์‹œ๋ ์ง€ ์ •์˜
  • component: ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฌธ์„œํ™”ํ• ์ง€ ์ง€์ •
  • argTypes์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ต์…˜๋“ค์„ “select” ์ปจํŠธ๋กค๋กœ ์ œ๊ณตboolean/text ํƒ€์ž…์œผ๋กœ ํ‘œ์‹œ
  • disabled: { control: "boolean", }, label: { control: "text", },
  • variant: { control: { type: "select", options: ["default", "primary", "secondary"], // variant ์˜ต์…˜์— ๋งž๊ฒŒ ์ˆ˜์ • }, },

 

2) Template ํ•จ์ˆ˜

: ์‹ค์ œ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•  ๋•Œ ์‚ฌ์šฉํ•  ํ…œํ”Œ๋ฆฟ ํ•จ์ˆ˜

const Template = (args) => <Button {...args} />;

 

3) Story ์ •์˜

: Template.bind({})๋ฅผ ํ†ตํ•ด ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ ํ•จ์ˆ˜๋ฅผ ๋ณต์ œํ•˜๊ณ , ๊ฐ ์Šคํ† ๋ฆฌ๋ณ„๋กœ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ์„ค์ •ํ•œ๋‹ค.

  • Default, Primary, Large, Disabled ๋“ฑ ๊ฐ๊ฐ์˜ ์Šคํ† ๋ฆฌ๋Š” ๋‹ค๋ฅธ args ๊ฐ’์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.
export const Primary = Template.bind({});
Primary.args = {
  variant: "primary",
  size: "md",
  disabled: false,
  label: "Primary Button",
};

 

 

5๋‹จ๊ณ„: Storybook ์‹คํ–‰ํ•˜๊ธฐ

๋ชจ๋“  ์„ค์ •์„ ๋งˆ์นœ ํ›„, ์•„๋ž˜ ๋ช…๋ น์–ด๋กœ Storybook์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

npm run storybook

์ด ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €์—์„œ Storybook์ด ์‹œ์ž‘๋˜๋ฉฐ, ๊ตฌ์„ฑํ•œ ์Šคํ† ๋ฆฌ๋“ค์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

์ฐธ๊ณ 

[React] ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปดํฌ๋„ŒํŠธ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•˜๊ธฐ

์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ ๋งŒ๋“ค๊ธฐ - React

React StoryBook์˜ ๊ธฐ์ดˆ(์Šคํ† ๋ฆฌ ์ž‘์„ฑ / ์Šคํ† ๋ฆฌ๋ถ ์‹คํ–‰)

 

728x90