@k8ordo/color-scheme

保存

訪問者の設定は、@k8ordo/state のふつうのローカル状態として localStorage に保存されます。このパッケージが持つのは定義 1 つだけで、キーも行の形もタブ間の同期も @k8ordo/state のものです。行の中身、hook を通さずに読む方法、タブ同士の揃い方、アプリのほかの設定との並べ方を説明します。

定義は colorSchemeState 1 つ

設定の保存先の定義は colorSchemeState として export されており、中身はこれですべてです。

ts
import { defineLocalState } from '@k8ordo/state';
import * as z from 'zod/mini';

export const colorSchemeState = defineLocalState(
  'color-scheme',
  z.object({ preference: z.optional(z.enum(['light', 'dark'])) }),
);

キーは color-scheme なので、localStorage のキーは k8ordo-state:color-scheme です(colorSchemeState.storageKey)。preference は省略可能で、省略されていることが「選んでいない」を表します。

保存される行

行が書かれるのは setPreference を呼んだときだけです。Provider の既定値は保存されません。

操作保存される行
一度も選んでいない行なし(getItem は null)
setPreference('dark'){"preference":"dark"}
setPreference('light'){"preference":"light"}
setPreference('system'){}

'system' に戻しても行は消えず、preference の無い {} が残ります。preference を読む側にとっては、行が無いのと同じです。

ほかの場所から読む

hook を通さずに行を読みたいときは、どのクライアントコンポーネントからでも useAppState(colorSchemeState) を呼べます。@k8ordo/state には Provider が無く、ストアはキーごとに 1 つなので、<ColorSchemeProvider> と同じ値を読みます。

tsx
// src/components/stored-preference.tsx
'use client';

import { colorSchemeState } from '@k8ordo/color-scheme';
import { useAppState } from '@k8ordo/state';

export function StoredPreference() {
  const [{ preference }] = useAppState(colorSchemeState);

  return <output>{preference ?? 'system'}</output>;
}

返るのは保存された preference('light' | 'dark' | undefined)で、解決した scheme ではありません。画面に出ているものが欲しいなら useColorScheme() を使います。サーバーでの描画と hydrate の描画では、何も保存されていないときの値 undefined を返します。

最初の描画の前に同じ行を読みたい自前のインラインスクリプトには colorSchemeState.inlineRead() があります。保存されたオブジェクト(読めなければ null)に評価される JavaScript の式を返します。スキーマは走らないので、使うフィールドは自分で確かめます。 @k8ordo/state: ハイドレーション前に読む

タブ同士が揃う

@k8ordo/state のローカル状態は、自分のキーの storage イベントを購読しています。別のタブで選んだ設定も、別のタブで localStorage を消したことも、このタブの Provider に届き、クラスもその場で変わります。

storage イベントは、書き込んだタブ自身には届きません。同じタブで localStorage.setItem('k8ordo-state:color-scheme', …) と直接書いても、Provider は再読み込みまで気づきません。変えるときは setPreference を通します。

ほかの設定と並べる

アプリがほかにも見た目の設定を持つなら、別のキーで自分の defineLocalState を定義します。このサイトの縦書き・横書きの設定がそうで、カラースキームとは別の行に保存されます。定義は 'use client' の無いモジュールに置きます。'use client' のファイルから export すると、Server Component には定義ではなく client reference が届くからです。

ts
// src/theme/state.ts
import { defineLocalState } from '@k8ordo/state';
import * as z from 'zod/mini';

export const writingModeState = defineLocalState(
  'writing-mode',
  z.object({ mode: z.optional(z.enum(['horizontal', 'vertical'])) }),
);

アプリで defineLocalState('color-scheme', …) を別に定義しないでください。@k8ordo/state のストアはキーで共有されるので、2 つの定義が同じ行と同じストアを取り合います。

@k8ordo/state の状態の置き場所