たくさんの自由帳

Tailwind CSS で作り直そうとしている

投稿日 : | 0 日前

文字数(だいたい) : 4655

どうもこんばんわ。
最近ずっとこれ聞いてる

Imgur

「偶然」で片付けたくない 運命にしたい

ゲームやってないけどCDだけ買った。!!!

Imgur

本題

Material-UIをやめて、Tailwind CSSにしようと思います...

Imgur

Imgur

Imgur

Imgur

最近のAndroidというか、Material 3というかを真似てみました。
角を結構丸くしたり、リストとか。

移行前

移行前のmain ブランチのコードをmaterial_uiブランチに切ってあります。
https://github.com/takusan23/ziyuutyou-next/tree/material_ui

引き続き参照可能です。

Tailwind CSS

賛否両論。わたしはすき、
というかWebフロントよく知らないので、Next.jsに入ってるやつ(いつの間にか選択できるようになったらしい)を大人しく使う。

/** RoundedCornerBox へ渡すデータ */
type RoundedCornerBoxProps = {
    /** どれだけ丸くするか */
    rounded?: 'small' | 'medium' | 'large'
    /** Tailwind CSS のクラス名 */
    className?: string
    /** 子要素 */
    children: ReactNode
}
 
/** 角丸なBox */
export default function RoundedCornerBox({ className, rounded, children }: RoundedCornerBoxProps) {
    // 変数埋め込みとかは使えない
    // https://tailwindcss.com/docs/content-configuration#class-detection-in-depth
    const colorOrDefault = className ?? 'bg-container-primary-light dark:bg-container-primary-dark'
    let roundedClassName: string
    switch (rounded ?? 'small') {
        case 'small':
            roundedClassName = 'rounded-md'
            break
        case 'medium':
            roundedClassName = 'rounded-xl'
            break
        case 'large':
            roundedClassName = 'rounded-3xl'
            break
    }
 
    return (
        <div className={`${roundedClassName} ${colorOrDefault}`}>
            {children}
        </div>
    )
}

なんで

時代はRSC ( React Server Components )に突入したらしい。ブラウザ側で実行されるJavaScriptを減らす時代がやってきた!!!
理由ですが...

  • CSS絶対書きたくないからMaterial-UI入れたんだけど、Tailwind CSSが結構良さそうだった
    • これなら書きたい
      • なんか.css書きたくないし...
      • scopedcssとかvue ?にあった気がするけどあんまり使いこなせなかった...(私のせい
    • よく使うやつがすぐ使えるflex-row/flex-colとか
      • 角を丸くするとか
      • px-/py-とか便利
        • space-x-2 これも
    • コンポーネントごとに区切ってるのでそんなにclass長くてもそこまで、、、って感じな気がする
  • ダークモードや、レスポンシブデザイン、テーマ機能がある
    • 現状使っているので必要
    • この辺もCSSをそのまま書くのは、、、うーんやりたくないかなあ
  • 賛否両論あるけど特に困ってない!!!
    • (代替案を使ったこと無いので)

以上です。
あと、Tailwind CSS関係なく、ブログ(静的サイト)なので、リッチな UI より読み込みが早いほうが良いかなあってのもあります。
ゼロランタイム CSSとかいうやつ)

ただ、ここでRSCTailwind CSS賭けて良いのかは分からない、、、Webフロントエンドってなんか流行の移り変わりが早すぎる。

ちなみにApp Routerで動くMaterial-UIのサポートは入ったらしい。これでひたすら"use client"しなくても済むのかな?
https://github.com/mui/material-ui/releases/tag/v5.14.0

あとは、移行したときに良いなと思ったこととかをつらつらと...

良かった点

サーバーコンポーネント、クライアントコンポーネント どっちでも書ける

(最終的にはCSSが吐き出される?)のが良さそう
クライアントコンポーネントしか無いなら他のスタイリングを選んだほうがいいのか・・・も?
ただ、ぶっちゃけサーバーコンポーネントもよくわからない、、、けど面白そうだし試している

リセット CSS

https://tailwindcss.com/docs/preflight

これ、h1h2、、の大きさはすべて同じ大きさに揃えられます。
ulpmarginがデフォルトでかかっていません。
aタグの色もかかってません!!!

aタグだけはリセットされないようにするため、なんか初期値に戻せ!みたいなCSSを書きました。
大人しくスタイリングしても良かったかもしれない。

/* Tailwind CSS で aタグ の色が消えてしまったので、もとに戻す */
a {
    color: revert;
    text-decoration: revert;
}

VSCode に Tailwind CSS 拡張機能を入れよう

これです
https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss

快適すぎて草、クラス名の補充や、自前で定義したプライマリカラーとかの色まで見れるの強すぎやろ

Imgur

最終的なCSSも見れる。すごい

peer-checked が便利

マテリアルコンポーネントにもあるSwitch()を作ろうと思います。
スイッチを押したらつまみ(Thumb)を右側へ移動するためのJavaScriptを書こうと思ってたんですけど、これJSなしで再現できるんですね!!

以下が該当のコードですが、チェック状態を保持しておくための<input>をユーザーに見えない形で置いておきます。
<label>で囲っているので、囲っている<div><span>を押したときにも、<input>にチェックが入ります(上記の通り見えませんが)

で、ここからです。
<input>の後の要素のクラス名にpeer-checked:を先頭につけると、その<input>にチェックが付いたかどうかでスタイルを切り替えることが出来ます。
この方法を使い、チェックがついてないときは左へjustify-start<input>にチェックが入ったら右へpeer-checked:justify-endすることが出来ます。CSSだけで完結!!!

export default function DarkmodeSwitch() {
    const [isDarkmode, setDarkmode] = useState(false)
 
    return (
        <label className="flex flex-row items-center select-none cursor-pointer p-2">
            {/* peer をつけておくと、チェック true / false 時にそれぞれ指定する CSS をセットできる( JS で動的に className を変化させる必要がない ) */}
            <input
                className="sr-only peer"
                type="checkbox"
                checked={isDarkmode}
                onChange={(ev) => setDarkmode(ev.target.checked)} />
            <span className="text-content-text-light dark:text-content-text-dark flex grow">
                ダークモード
            </span>
            {/* チェックが付いたら左に寄せる、丸を大きくする(peer-checked:justify-end) */}
            <div className="h-8 w-14 flex flex-row items-center rounded-full border-content-primary-light dark:border-content-primary-dark border-2 p-1.5 peer-checked:p-0.5 justify-start peer-checked:justify-end">
                <div className="h-full aspect-square bg-content-primary-light dark:bg-content-primary-dark rounded-full" />
            </div>
        </label>
    )
}

いや、公式の説明のがわかりやすかったわ、JSなしで表示・非表示を切り替えてる
https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-sibling-state

つまずく点

動的な値はできない

つまり、こういう文字列に変数を埋め込むとかは出来ないです。

const isEnable = true
const width = isEnable ? 100 : 50
const className = `w-${width}`
 
return (<div className={className}/>)

もし、状態によって切り替えたい場合は、完全なクラス名としてソースコードに記述する必要があります。

const isEnable = true
const className = isEnable ? "w-100" : "w-50"
 
return (<div className={className}/>)

というのも、Tailwind CSSは文字列を探してCSSを生成する、結構シンプルな仕組みで動いているらしい?ので、文字列として存在しないといけないんですよね。 https://tailwindcss.com/docs/content-configuration#class-detection-in-depth

動的な値は取れないので、あらかじめいくつかパターンを用意しておく必要があるわけですね。

let roundedClassName: string
switch (rounded ?? 'small') {
    case 'small':
        roundedClassName = 'rounded-md'
        break
    case 'medium':
        roundedClassName = 'rounded-xl'
        break
    case 'large':
        roundedClassName = 'rounded-3xl'
        break
}

Kotlinwhenというか式ほしい...このためだけにletにするのなんか負けた感あってくやしい)

Material-UIみたいに Ripple Effect みたいなのはなくなっちゃった

押してる!!!感はなくなっちゃった

アイコン

マテリアルアイコンは自分でsvgを持ってくる必要があります、もしくはnpmからよしなにライブラリを入れるかです。

そのほか

Mastodon / Misskey シェアボタン を置いた

Twitterの代わりにMastodon / Misskeyでシェアできるボタンを置きました。
TweetDeckなくなってからTwitterあんまり見なくなっちゃった、、、

Imgur

Imgur

Imgur

MisskeyMastodonも以下のようなURLでそれぞれのインスタンスの共有画面を出せるので、それを使っています。

https://{serverName}/share?text={シェアしたい文字}

TypeScript / JavaScriptでやる場合はこんな感じです多分

// インスタンス名
const serverName = 'diary.negitoro.dev'
// シェアしたい文字
const shareText = encodeURIComponent('シェアしたい文字')
// 新しいタブで開く
const shareUrl = `https://${serverName}/share?text=${shareText}`
window.open(shareUrl, '__blank')

Nuxt.jsのときは置いてたんですが、、、、Next.jsに移行した際にTwitterに置き換えちゃったんですよね。
Twitter改悪によりMastodon / Misskey シェア再実装です

Imgur

Imgur

(奇跡的にNuxt.jsのときのビルド成果物が残ってたので起動してみた)

本番に投入する

きたきた

Imgur

今回も差分大きいというか影響大きいのでPull Request作ってみました。

https://github.com/takusan23/ziyuutyou-next/pull/2

差分ですが、npm installした時とかに自動で更新されるpackage-lock.jsonとかも差分に入ってしまったので、私が書き足したのはそこまでではないと思う・・・

Imgur

まーじします!うおおおお!

Imgur

GitHub Actionsが終わるのを見守ります

Imgur

Material-UI → Tailwind CSS

ま、まぁ、、あんまりMaterial-UIのコンポーネント使ってたわけじゃないので、もっといろんなコンポーネントを使ってればもっと増えてたのかも

Material-UI

Route (app)                                   Size     First Load JS
┌ ○ /                                         4.96 kB         124 kB
├ ○ /_not-found                               0 B                0 B
├ ○ /favicon.ico                              0 B                0 B
├ ○ /icon.png                                 0 B                0 B
├ ● /pages/[page]                             1.36 kB         112 kB
├   └ /pages/about
├ ● /posts/[blog]                             5 kB            124 kB
├   ├ /posts/akashic_engine_pwa_cache
├   ├ /posts/android11_devicecontrol
├   ├ /posts/android11_release_devicecontrol
├   └ [+135 more paths]
├ ● /posts/page/[page]                        4.28 kB         120 kB
├   ├ /posts/page/1
├   ├ /posts/page/2
├   ├ /posts/page/3
├   └ [+11 more paths]
├ ● /posts/tag/[tag]                          2.46 kB         119 kB
├   ├ /posts/tag/Android
├   ├ /posts/tag/Kotlin
├   ├ /posts/tag/JetpackCompose
├   └ [+96 more paths]
├ ○ /posts/tag/all_tags                       616 B           117 kB
└ ○ /sitemap.xml                              0 B                0 B
+ First Load JS shared by all                 96.2 kB
  ├ chunks/681-35cd67eb9c939efc.js            18.5 kB
  ├ chunks/698-43273f17e9c681c8.js            25.2 kB
  ├ chunks/bce60fc1-0593c640cdea54db.js       50.5 kB
  ├ chunks/main-app-67d9599d5de0ee24.js       213 B
  └ chunks/webpack-47c91c3dea7cfcb6.js        1.73 kB

Tailwind CSS

Route (app)                                   Size     First Load JS
┌ ○ /                                         3.57 kB        87.2 kB
├ ○ /_not-found                               0 B                0 B
├ ○ /favicon.ico                              0 B                0 B
├ ○ /icon.png                                 0 B                0 B
├ ● /pages/[page]                             3.44 kB          87 kB
├   └ /pages/about
├ ● /posts/[blog]                             3.44 kB          87 kB
├   ├ /posts/akashic_engine_pwa_cache
├   ├ /posts/android11_devicecontrol
├   ├ /posts/android11_release_devicecontrol
├   └ [+135 more paths]
├ ● /posts/page/[page]                        764 B          84.4 kB
├   ├ /posts/page/1
├   ├ /posts/page/2
├   ├ /posts/page/3
├   └ [+11 more paths]
├ ● /posts/tag/[tag]                          764 B          84.4 kB
├   ├ /posts/tag/Android
├   ├ /posts/tag/Kotlin
├   ├ /posts/tag/JetpackCompose
├   └ [+96 more paths]
├ ○ /posts/tag/all_tags                       624 B          84.2 kB
├ ○ /search                                   3.26 kB        86.9 kB
└ ○ /sitemap.xml                              0 B                0 B
+ First Load JS shared by all                 77.8 kB
  ├ chunks/698-0d5add4b5e93c16b.js            25.2 kB
  ├ chunks/bce60fc1-33201d061f5d18d8.js       50.5 kB
  ├ chunks/main-app-67d9599d5de0ee24.js       213 B
  └ chunks/webpack-6989dea18b00da7b.js        1.8 kB

おわりに

Netlifyだと日本からのアクセスが遅いので、つぎはホスティングサービスを移行したい・・!