【お遊び？】Emotion で Utility First してみた

Emotion の機能を使って、こんな感じで Utility First な記述を試してみました。


```tsx:uf-emotion.tsx
<div css={limitWidth()}>
  <main
    css={[
      minHeight(640),
      pcOnly([paper, my(4)]),
      mbOnly(bgColor("#fafafa")),
      contentsStyle,
    ]}
  >
    <section css={[responsive({ mb: p(2), pc: p(3) })]}>
      <h1 css={[typography.h1]}>ああああ</h1>
      <p>
        Irofa nifofedo tirinuruwo wagayo tarezo tunenaram uwino okuyama kefu
        koete asaki yumemisi wefimo sezu
      </p>
    </section>
  </main>
</div>
```

（共通化は漏れが起きやすいので、こういう小さなユーティリティの組み合わせが楽なんですよね... ）

https://scrapbox.io/mrsekut-p/%E5%86%8D%E5%88%A9%E7%94%A8%E7%9B%AE%E7%9A%84%E3%81%A7%E5%B0%8F%E3%81%95%E3%81%8F%E4%BD%9C%E3%82%8B%E3%82%8F%E3%81%91%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%84

<details><summary>利用しているライブラリのバージョン</summary>

| パッケージ | バージョン |
|-|-|
| @emotion/react | 11.9.3 |
| next | 12.1.6 |
| react, react-dom | 18.1.0 |
</details>



# Emotion の `css` Prop について

まず、`css` Prop の型は `Interpolation<Theme>` 型になっています。そして、`Interpolation` は以下のように定義されています。

```ts
export type Interpolation<Props> =
  | InterpolationPrimitive
  | ArrayInterpolation<Props>
  | FunctionInterpolation<Props>
```

## css タグリテラル（基本形）

 <code>css&#96;&#96;</code> の式の結果は、`InterpolationPrimitive` 型（のサブタイプ）になるので、

```tsx
<div css={css`background-color: red;`}></div>
```

と記述できるわけです。

## テーマを使うこともできる。

１つ飛ばして、次は３つ目の `FunctionInterpolation` を見てみましょう。

```ts
export interface FunctionInterpolation<Props> {
  (props: Props): Interpolation<Props>
}
```

となっているので、 `Theme` (つまり、Emotionで宣言したテーマ)を引数に取ることが出来ることが分かります。

```tsx
<div
  css={(theme) =>
    css`background-color: ${theme.colors.primary};`
  }
>
  ああああ
</div>
```

## 配列も指定できる。

 `ArrayInterpolation` を見てみましょう。これがミソです。

```ts
export interface ArrayInterpolation<Props>
  extends Array<Interpolation<Props>> {}
```

となっているので、配列の形で渡すことが出来ます。

```tsx
<div
  css={[
    css`color: white;`,
    // 1) テーマを引数に取る指定も含められる
    (theme) => css`background-color: ${theme.colors.primary};`,
    // 2) && etc. による条件付き指定も可
    selected && css`background-color: red;`,
  ]}
>
  いいいい
</div>
```

コメントで記述したように、**1)** テーマを引数にとることで、テーマを利用することも出来ますし、**2)** `&&` 演算子等による、条件つきの指定もできるようになっています。(もちろん三項演算子も使用可)

`&&` が使えるお陰で、clsx によるクラス指定みたいに、シンプルな表記で 適用/不適用 を制御できるのが嬉しいですね。


# これを応用すれば...

勘の良い皆さまはもうお気づきでしょう。この配列を使った指定方法を取れば、`Interpolation<Theme>`型のを返す関数として切り出し、それらを呼び出して並べることで、スタイルを合成することができます。

これが、emotion で Utility First 的な記述が出来るカラクリです。

## ユーティリティー関数群

最初に示した記述に使われているUtility群は、このように定義されています。

```ts:tokens.ts
type CSSInterpolation = Parameters<typeof css>[number];

const breakpoint = "640px";

export const pcOnly = (s: CSSInterpolation) =>
  css`
    @media screen and (min-width: ${breakpoint}) {
      ${s}
    }
  `;

export const mbOnly = (s: CSSInterpolation) =>
  css`
    @media screen and (max-width: ${breakpoint}) {
      ${s}
    }
  `;

export const spacing = (value: number | string) =>
  typeof value === "string" ? value : `${8 * value}px`;

// system

export const minHeight = (px: number) =>
  css`
    min-height: ${px}px;
  `;

export const my = (units: number | string) =>
  css`
    margin-top: ${spacing(units)};
    margin-bottom: ${spacing(units)};
  `;

export const mx = (units: number | string) =>
  css`
    margin-right: ${spacing(units)};
    margin-left: ${spacing(units)};
  `;

export const p = (units: number | string) =>
  css`
    padding: ${spacing(units)};
  `;

// typography
export const typography = {
  h1: [
    css`
      font-size: 2.25rem;
    `,
    pcOnly(css`
      font-size: 3rem;
    `),
  ],
};

// color
export const bgColor = (color: string) =>
  css`
    background-color: ${color};
  `;

// concrete

export const limitWidth = (width: number = 960) => css`
  max-width: ${width}px;
  padding: 0 ${spacing(2)}; // === 16px
  margin: 0 auto;
`;

export const paper = css`
  background-color: white;
  border-radius: 4px;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.02),　0 8px 32px rgba(0, 0, 0, 0.1);
`;
```


# 参考記事（公式Doc）

https://emotion.sh/docs/composition