Slots
コンポーネントがひとつの要素だけで完結することはまれです。カードには root、title、footer があり、size のような variant はたいていそのうちの複数を同時に変更します。slots を使うと、ひとつの ev() 呼び出しですべてのパーツをスタイリングできます。パーツが同じ variants を共有するので、互いにずれることがありません。
パーツを宣言する
base の代わりに slots を使います。各キーはコンポーネントのパーツの名前で、そのパーツの基本スタイルを持ちます。
import { ev } from 'emotion-variants';
const card = ev({
slots: {
root: {
borderRadius: 12,
padding: 16,
},
title: {
fontWeight: 600,
},
footer: {
display: 'flex',
gap: 8,
},
},
});これで card() を呼び出すと、slot ごとにスタイル関数がひとつずつ返されます。それぞれの要素をレンダリングする場所で呼び出しましょう。
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
type CardProps = ComponentPropsWithoutRef<'section'> & {
heading: ReactNode;
};
function Card({ heading, children, ...rest }: CardProps) {
const { root, title, footer } = card();
return (
<section css={root()} {...rest}>
<h3 css={title()}>{heading}</h3>
<div css={footer()}>{children}</div>
</section>
);
}slot はスタイルではなく関数です。css={root()} と書いてください。Emotion は css に渡された関数を theme を引数にして呼び出すため、css={root} と書くと theme オブジェクトが slot の variants として読み取られてしまいます。
slot ごとの variants
slots がある場合、variant の値は slot 名とスタイルの対応になります。値には変更する slot だけを書けば十分です。
const card = ev({
slots: {
root: {
display: 'flex',
flexDirection: 'column',
gap: 6,
width: 320,
border: '1px solid #e4e4e7',
borderRadius: 12,
background: 'white',
color: '#09090b',
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)',
},
title: {
fontWeight: 600,
},
footer: {
display: 'flex',
gap: 8,
fontSize: 14,
color: '#71717a',
},
},
variants: {
size: {
small: {
root: {
padding: 16,
},
title: {
fontSize: 16,
},
},
large: {
root: {
padding: 24,
},
title: {
fontSize: 18,
},
},
},
tone: {
neutral: {},
accent: {
title: {
color: '#d36ac2',
},
},
},
},
defaultVariants: {
size: 'small',
tone: 'neutral',
},
});
const { root, title, footer } = card({
size: 'large',
tone: 'accent',
});
<div css={root()}>
<h3 css={title()}>Weekly report</h3>
<div css={footer()}>Updated 2 hours ago</div>
</div>1 回の呼び出しで、すべての slot の variants が解決されます。root() には large の padding、title() には large の font size と accent の color が適用され、footer() はどの variant にも登場しないので基本スタイルだけが適用されます。
ひとつの slot だけ variant を上書きする
各 slot 関数は、呼び出し時と同じ variant props を受け取ります。slot の props は呼び出しの props より優先され、呼び出しの props は defaultVariants より優先されます。
const { root, title } = card({
size: 'large',
});
root(); // large
title({
size: 'small',
}); // title だけ small他の場所と同じく、undefined は次のレベルにフォールバックするので、省略可能な prop をそのまま slot に渡しても安全です。
Compound variants
compoundVariants も同じように動作しますが、style は slot 名ごとに書きます。
compoundVariants: [
{
variants: {
size: 'large',
tone: 'accent',
},
style: {
title: {
textTransform: 'uppercase',
},
},
},
],compound は slot ごとに、その slot で解決された値に対して照合されます。そのため、ひとつの slot で variant を上書きすると、その slot だけで compound が有効になったり無効になったりします。
const { title } = card({
tone: 'accent',
});
title(); // small + accent: compound なし
title({
size: 'large',
}); // large + accent: uppercase複数の slot に同じスタイルを適用する
compoundSlots は、ひとつのスタイルを slot のリストに適用します。slot 名ごとに同じスタイルを繰り返し書く手間が省けます。
compoundSlots: [
{
slots: ['title', 'footer'],
style: {
paddingInline: 4,
},
},
{
slots: ['root', 'footer'],
variants: {
size: 'large',
},
style: {
gap: 16,
},
},
],variants は省略可能です。省略するとエントリは常に適用され、指定すると配列の条件も含めて compoundVariants と同じ照合ルールに従います。
順序
各 slot で、スタイルは次の順に追加され、CSS が競合した場合は後のエントリが優先されます。
slotsに書いたその slot の基本スタイル- 一致した
variantsのエントリ(軸が宣言された順) - 一致した
compoundVariantsのエントリ(配列の順) - 一致した
compoundSlotsのエントリ(配列の順)
Class name
className() は slot ごとに class name をひとつずつ返します。
const classNames = card.className({
size: 'large',
});
<section className={classNames.root}>
<h3 className={classNames.title}>Title</h3>
</section>;slots を宣言した場合、classNames のルックアップテーブルはありません。
ルール
baseとslotsは併用できません。スタイルは slot に、一般的にはrootという名前の slot に書きましょう。- variant の値、compound の
style、compoundSlotsのエントリには、宣言済みの slot 名しか書けません。それ以外は型エラーになるので、タイプミスを検出できます。 '&:hover'のようなセレクターは、slot 名と並べるのではなく slot のスタイルの中に書きます。compoundSlotsにはslotsが必要です。
Props
VariantProps は slot 関数にも使え、すべての slot で共有される variant props を取得できます。
import type { VariantProps } from 'emotion-variants';
type CardVariants = VariantProps<typeof card>;
// { size?: 'small' | 'large'; tone?: 'neutral' | 'accent' }関連項目
最終更新