Skip to Content

Responsive

variant는 화면 너비에 따라 다른 값이 필요할 때가 많아요. 휴대폰에서는 작은 button, 데스크톱에서는 큰 button처럼요. createEv로 breakpoint를 한 번 선언하면, 모든 variant prop이 breakpoint마다 값을 하나씩 받을 수 있어요.

breakpoint 선언하기

createEv를 한 번 호출하고, 반환된 ev를 export하세요. ev는 emotion-variants 대신 이 파일에서 import해요.

// ev.ts
import { createEv } from 'emotion-variants';

export const { ev } = createEv({
  breakpoints: {
    md: '@media (min-width: 768px)',
    lg: '@media (min-width: 1024px)',
  },
});

키는 직접 정하는 이름이고, 값은 그 이름이 가리키는 media query예요. 너비가 작은 것부터 큰 순서로 적으세요.

스타일 정의는 그대로예요. 컴포넌트의 스타일은 아무것도 바꾸지 않아도 돼요.

import { ev } from './ev';

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',
  },
});

breakpoint마다 값 넘기기

variant prop에는 지금까지처럼 값을 넘기거나, initial과 breakpoint 이름을 키로 쓰는 객체를 넘길 수 있어요.

<button
  css={button({
    size: {
      initial: 'small',
      md: 'large',
    },
  })}
>
  Small below 768px, large above
</button>

initial은 media query 밖에 놓여서 모든 너비에 적용돼요. md는 large의 스타일을 md query 안에 넣어서, 768px부터 그 스타일이 이겨요. 결과는 여전히 css prop에 넘기는 스타일 배열이에요.

  • 생략한 breakpoint는 아무것도 더하지 않아요. 그래서 앞 breakpoint의 값이 이어져요.
  • initial을 생략하면 그 축의 기본 variant가 대신 쓰여요. 기본값도 없으면, 넘긴 첫 breakpoint 아래에서는 그 축의 스타일이 없어요.
button({
  size: {
    lg: 'large',
  },
}); // 1024px까지는 medium(기본값), 그 위로는 large

컴포넌트에서 쓰기

VariantProps에 반응형 형태가 들어 있어서, prop을 그대로 넘기기만 하면 돼요.

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

type ButtonProps = ComponentPropsWithoutRef<'button'> & VariantProps<typeof button>;

function Button({ variant, size, color, ...rest }: ButtonProps) {
  return <button css={button({ variant, size, color })} {...rest} />;
}
<Button
  size={{
    initial: 'small',
    md: 'large',
  }}
>
  Save
</Button>

순서

breakpoint 스타일은 넘긴 키의 순서와 상관없이 breakpoint를 선언한 순서대로 쌓여요. 작은 것부터 선언하는 이유예요. 두 query가 함께 일치하면 뒤에 오는 게 이겨요.

stylesheet에서 media query 안의 규칙은 일반 스타일이 담긴 규칙보다 뒤에 와요. 그래서 breakpoint 스타일은 그 breakpoint 안에서 같은 속성의 일반 스타일을 모두 이겨요. 뒤에 선언한 축의 스타일도, 복합 variant의 스타일도 마찬가지예요.

두 축이 media query 안에서 같은 속성을 지정하면, 두 query가 함께 일치하는 구간에서는 뒤에 선언한 축이 이겨요.

Compound variants

복합 variant는 축마다 값이 하나여야 일치 여부를 따질 수 있는데, 반응형 값은 여러 개예요. 그래서 반응형 값을 받은 축은 그 호출에서 어떤 복합 variant와도 일치하지 않아요. 그 축을 언급하지 않는 복합 variant는 그대로 적용돼요.

button({
  size: 'large',
  color: 'primary',
}); // large + primary 복합 variant가 적용돼요

button({
  size: {
    initial: 'large',
  },
  color: 'primary',
}); // 적용되지 않아요. primary만 조건으로 둔 복합 variant는 적용돼요

객체에 initial만 있어도 마찬가지예요. 값이 하나뿐이면 객체 대신 값을 그대로 넘기세요.

slots와 함께 쓰기

slots에서는 호출과 각 slot 함수가 모두 반응형 값을 받아요. slot prop은 그 축에 대한 호출의 값을 통째로 대체해요. 둘을 breakpoint별로 합치지 않아요.

const { root, title } = card({
  size: {
    initial: 'small',
    md: 'large',
  },
});

root(); // small, md부터 large
title({
  size: 'small',
}); // 모든 너비에서 small

class name

className()도 반응형 값을 받고, 반환하는 class에 media 규칙이 들어 있어요.

button.className({
  size: {
    initial: 'small',
    md: 'large',
  },
});

classNames는 variant 값마다 class를 하나씩 갖고 있고, breakpoint에 해당하는 항목은 없어요.

규칙

  • defaultVariants에는 값 하나만 쓸 수 있어요. 반응형 기본값은 타입 에러예요.
  • initial은 예약된 이름이라 breakpoint 이름으로 쓸 수 없어요. 타입 에러가 나요.
  • 선언하지 않은 breakpoint 키는 타입 에러이고, 런타임에서는 무시돼요.
  • breakpoint 이름은 '768'처럼 숫자로 읽히면 안 돼요. JavaScript가 그런 키를 객체의 맨 앞으로 옮기기 때문에 선언한 순서가 유지되지 않아요.
  • emotion-variants에서 export하는 ev에는 breakpoint가 없어서 일반 값만 받아요.
  • 한 ev로 만든 함수를 다른 ev로 extend할 수 있어요. 이때 적용되는 breakpoint는 호출하는 함수를 만든 ev의 것이에요.

함께 보기

GitHub에서 이 페이지 편집

마지막 업데이트

이 페이지에서