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',
}); // 모든 너비에서 smallclass 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의 것이에요.
함께 보기
마지막 업데이트