Skip to Content

Extend

어떤 컴포넌트는 다른 컴포넌트에 조금만 더한 것이에요. icon button은 너비가 고정된 button이고, danger card는 tone이 하나 더 있는 card예요. extend를 쓰면 ev() 결과 위에 다른 ev() 결과를 쌓을 수 있어서, 공통 스타일과 variant를 한 번만 선언하면 돼요.

다른 함수 위에 쌓기

extend에 ev() 결과를 넘기세요. 새 함수는 부모가 선언한 것을 모두 물려받아요.

import { ev } from 'emotion-variants';

const button = ev({
  base: {
    border: '1px solid transparent',
    borderRadius: 8,
    cursor: 'pointer',
    fontWeight: 600,
    transition: 'all 0.2s ease',
  },
  variants: {
    variant: {
      filled: {
        background: 'var(--button-color)',
        color: 'white',
      },
      outlined: {
        background: 'transparent',
        borderColor: 'var(--button-color)',
        color: 'var(--button-color)',
      },
      text: {
        background: 'transparent',
        color: 'var(--button-color)',
      },
    },
    size: {
      small: {
        padding: '8px 16px',
        fontSize: 12,
      },
      medium: {
        padding: '10px 20px',
        fontSize: 14,
      },
      large: {
        padding: '12px 24px',
        fontSize: 16,
      },
    },
    color: {
      primary: {
        '--button-color': '#d36ac2',
      },
      secondary: {
        '--button-color': '#475569',
      },
    },
  },
  defaultVariants: {
    variant: 'filled',
    size: 'medium',
    color: 'primary',
  },
});

const iconButton = ev({
  extend: button,
  base: {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    width: 44,
    height: 44,
  },
});

<button css={button()}>Button</button>
<button aria-label="Close" css={iconButton()}>
  ✕
</button>

iconButton은 variant, size, color를 받고, 부모의 기본값을 적용하고, 부모의 base 뒤에 자신의 base를 더해요. button 자체는 바뀌지 않아요.

값과 축 추가하기

자식은 물려받은 축에 값을 추가할 수 있고, 물려받은 값에 스타일을 더할 수 있고, 새 축을 선언할 수도 있어요.

const iconButton = ev({
  extend: button,
  variants: {
    size: {
      large: {
        width: 48,
      },
      huge: {
        fontSize: 24,
      },
    },
    round: {
      true: {
        borderRadius: '50%',
      },
    },
  },
});

iconButton.variants(); // ['variant', 'size', 'color', 'round']
  • size: 'small'은 그대로 물려받아요.
  • size: 'large'는 부모의 font size를 받고, 그 뒤에 자식의 width를 받아요.
  • size: 'huge'와 round 축은 iconButton에만 있어요.

물려받은 축은 제자리를 지키고, 새 축은 그 뒤에 와요.

기본값과 compound variants

defaultVariants는 축마다 합쳐지고, 자식의 값이 이겨요. 자식의 compoundVariants와 defaultVariants에는 물려받은 축과 값을 쓸 수 있어요.

const iconButton = ev({
  extend: button,
  variants: {
    round: {
      true: {
        borderRadius: '50%',
      },
    },
  },
  compoundVariants: [
    {
      variants: {
        variant: 'outlined',
        round: true,
      },
      style: {
        borderWidth: 2,
      },
    },
  ],
  defaultVariants: {
    size: 'large',
  },
});

부모의 복합 variant도 물려받아요. 일치 여부는 자식에서 정해진 값을 기준으로 따져요.

순서

단계마다 부모의 스타일이 자식의 스타일보다 먼저 와요. CSS가 충돌하면 뒤에 오는 게 이겨요.

  1. base (부모, 자식 순서)
  2. 일치하는 variants 항목 (축 순서대로, 값마다 부모 다음 자식)
  3. 일치하는 compoundVariants 항목 (부모, 자식 순서)

복합 variant는 모두 variant 스타일보다 뒤에 와요. 그래서 자식이 같은 variant 값에 스타일을 선언해도 부모의 복합 variant가 여전히 이겨요. 이걸 덮어쓰려면 자식에 같은 조건의 복합 variant를 추가하세요.

몇 단계든 이어서 쌓을 수 있어요. iconButton을 다시 extend하면 button이 먼저, iconButton이 그다음, 새 스타일 정의가 마지막에 와요.

slots와 함께 쓰기

slots 함수는 다른 slots 함수를 extend해요. 자식은 slot을 추가할 수 있고, 물려받은 slot에 스타일을 더할 수 있어요.

const card = ev({
  slots: {
    root: {
      padding: 16,
    },
    title: {
      fontWeight: 600,
    },
  },
});

const mediaCard = ev({
  extend: card,
  slots: {
    root: {
      overflow: 'hidden',
    },
    footer: {
      display: 'flex',
    },
  },
});

const { root, title, footer } = mediaCard();

자식이 slots를 선언하지 않으면 부모의 slot을 그대로 써요. 이때도 variant 값은 slot 이름으로 나눠 적어요.

class name

className()에는 물려받은 스타일이 들어 있어요. classNames의 각 항목은 그 키에 해당하는 부모와 자식의 스타일을 합친 것이고, 자식이 손대지 않은 값은 부모와 같은 class name을 써요.

규칙

  • extend에는 ev() 결과 하나만 넘길 수 있어요. 그 외의 값은 ev()가 실행될 때 에러를 던져요. ev()에서 나온 값이 맞다면 emotion-variants가 두 벌 설치됐을 가능성이 높아요.
  • slots가 있는 스타일 정의는 slots가 있는 스타일 정의만 extend할 수 있어요. 일반 함수를 extend하면 에러를 던지고, 타입 에러도 나요.
  • 부모에 slots가 있으면 자식은 base를 쓸 수 없어요. 스타일은 slot에 넣으세요. 타입 에러가 나요.

Props

extend한 함수에 VariantProps를 쓰면 합쳐진 variant를 돌려줘요.

import type { VariantProps } from 'emotion-variants';

type IconButtonVariants = VariantProps<typeof iconButton>;
// {
//   variant?: 'filled' | 'outlined' | 'text';
//   size?: 'small' | 'medium' | 'large';
//   color?: 'primary' | 'secondary';
//   round?: true;
// }

함께 보기

GitHub에서 이 페이지 편집

마지막 업데이트

이 페이지에서