🚀

Astroでclass:listを結合する

2023/04/27に公開

Astroではclass:listという属性があります。
これは以下のようにclass名を配列やオブジェクトとして指定できます。

class:list={['my-class', 'flex', { 'text-red-500': isFailed }]}

class:listは以下のように型定義されています。

'class:list'?: Record<string, boolean> | Record<any, any> | Iterable<string> | Iterable<any> | string;

オブジェクト、配列、文字列、そしてundefinedの可能性があります。
そのため、親クラスから指定されたclass:listと自分のclass:listを結合するときに単純にスプレッド構文で展開するというわけにはいきません。
というわけで、class:listを結合する関数を作成してみました。

import type { HTMLAttributes, HTMLTag } from "astro/types"

export const combine = (a: HTMLAttributes<HTMLTag>['class:list'], b: HTMLAttributes<HTMLTag>['class:list']): string => {
  const extract = (e: unknown): string => {
    if ( !e ) return ''
    if ( typeof e === 'string' ) {
      return e
    } else if ( typeof e === 'object' ) {
      if ( e instanceof Array ) {
        if ( e.length === 0 ) return ''
        return e.map(item => extract(item))
          .filter( item => item !== '' )
          .join(' ')
      } else if ( e instanceof Set ) {
        return Array.from(e).map(item => extract(item))
        .filter( item => item !== '' )
        .join(' ')
      } else {
        return Object.entries(e)
          .filter(([,v]) => v === true)
          .map(([k]) => extract(k))
          .join(' ')
      }
    } else {
      return ''
    }
  }
  const classList = [extract(a), extract(b)]
  return classList
    .filter( item => item !== '' )
    .join(' ')
}

Discussion