ナビゲーション

ビュー分割切替

一覧、カード、コンパクトの3表現を切り替えるビュー切替の挙動を確認するレシピです。

動きを試す

プレビュー

プレビュー設定

プレビューを準備しています。

必要なファイルを選ぶ

コード

index.html

利用前に変更する箇所

  • データ項目名と表示密度の閾値を運用に合わせる

<!doctype html>
<html lang="ja" data-theme="light">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>ビュー分割切替</title>
    <style>
      :root { --bg:#f7f8fa; --surface:#ffffff; --text:#18212f; --muted:#5d6b7a; --border:#d7dee8; --focus:#0077cc; --accent:#245fd6; }
      :root[data-theme="dark"] { --bg:#0f141a; --surface:#171d26; --text:#f2f5f8; --muted:#aab4c0; --border:#354152; --focus:#65c7ff; --accent:#7ea7ff; }
      * { box-sizing: border-box; }
      body { margin: 0; padding: 16px; background: var(--bg); color: var(--text); font-family: Arial, sans-serif; }
      .card { max-width: 760px; margin: 0 auto; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 12px; }
      .controls { display: flex; gap: 8px; flex-wrap: wrap; }
      button {
        min-height: 44px;
        padding: 8px 12px;
        border: 1px solid var(--border);
        border-radius: 8px;
        background: var(--surface);
        color: var(--text);
      }
      button:focus-visible { outline: 3px solid var(--focus); }
      button[aria-pressed="true"] { border-color: var(--accent); font-weight: 700; }
      [data-state-panel][hidden] { display: none; }
      .list-item, .card-item, .compact-item { list-style: none; margin: 0; padding: 8px; border-bottom: 1px solid var(--border); }
      .card-view {
        display: grid;
        gap: 8px;
        margin: 1em 0;
        padding: 0;
      }
      .card-view li { list-style: none; padding: 10px; border: 1px solid var(--border); border-radius: 8px; }
      .compact-view .compact-item { font-size: 0.9rem; }
      .status { min-height: 1.2em; margin-top: 8px; }
      @media (max-width: 360px) { body { padding: 10px; } }
      @media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
    </style>
  </head>
  <body>
    <main class="card">
      <h1>ビュー表示切替</h1>
      <div class="controls">
        <button type="button" data-set-state="list" aria-pressed="true">一覧</button>
        <button type="button" data-set-state="card" aria-pressed="false">カード</button>
        <button type="button" data-set-state="compact" aria-pressed="false">コンパクト</button>
      </div>
      <section data-state-panel="list">
        <h2>リストビュー</h2>
        <ul>
          <li class="list-item" data-record-id="record-a">受付A / 完了</li>
          <li class="list-item" data-record-id="record-b">受付B / 確認中</li>
          <li class="list-item" data-record-id="record-c">受付C / 未着手</li>
        </ul>
      </section>
      <section data-state-panel="card" hidden>
        <h2>カードビュー</h2>
        <ul class="card-view">
          <li data-record-id="record-a">受付A / 完了</li>
          <li data-record-id="record-b">受付B / 確認中</li>
          <li data-record-id="record-c">受付C / 未着手</li>
        </ul>
      </section>
      <section data-state-panel="compact" hidden>
        <h2>コンパクトビュー</h2>
        <ul>
          <li class="compact-item" data-record-id="record-a">受付A / 完了</li>
          <li class="compact-item" data-record-id="record-b">受付B / 確認中</li>
          <li class="compact-item" data-record-id="record-c">受付C / 未着手</li>
        </ul>
      </section>
      <p role="status" data-status class="status"></p>
    </main>
    <script>
      (() => {
        const params = new URLSearchParams(window.location.search);
        const instance = params.get('instance') ?? 'missing-instance';
        const statePanels = Array.from(document.querySelectorAll('[data-state-panel]'));
        const status = document.querySelector('[data-status]');
        const buttons = Array.from(document.querySelectorAll('[data-set-state]'));
        const validStates = new Set(['list', 'card', 'compact']);

        const setTheme = (theme) => {
          if (theme === 'dark' || theme === 'light') document.documentElement.dataset.theme = theme;
        };
        const setState = (nextState) => {
          if (!validStates.has(nextState)) return;
          document.documentElement.dataset.state = nextState;
          for (const panel of statePanels) panel.hidden = panel.dataset.statePanel !== nextState;
          for (const button of buttons) {
            button.setAttribute('aria-pressed', String(button.dataset.setState === nextState));
          }
          const selectedButton = buttons.find((button) => button.dataset.setState === nextState);
          if (status && selectedButton) status.textContent = `${selectedButton.textContent?.trim()}表示に切り替えました。`;
        };

        for (const button of buttons) {
          button.addEventListener('click', () => setState(button.dataset.setState ?? 'list'));
        }
        window.addEventListener('message', (event) => {
          if (event.source !== window.parent || typeof event.data !== 'object' || event.data === null) return;
          if (event.data.source !== 'wpl-host' || event.data.instance !== instance) return;
          if (event.data.type === 'wpl-preview-state') setState(event.data.value);
          if (event.data.type === 'wpl-preview-theme') setTheme(event.data.value);
        });
        setTheme(params.get('theme') === 'dark' ? 'dark' : 'light');
        setState(validStates.has(params.get('state')) ? (params.get('state') ?? 'list') : 'list');
        window.parent.postMessage({ source: 'wpl-preview', type: 'wpl-preview-ready', instance }, '*');
      })();
    </script>
  </body>
</html>
プレビューで試せる動き
  • リスト: 情報を行単位で見せるベース表示です。
  • カード: 項目を情報量付きカードで見やすく表示します。
  • コンパクト: 折りたたみ情報で縦幅を節約します。

使い分けを確認する

このレシピが合う場面

適する場面

  • 同一データを一覧/カード/コンパクトで見せ分ける
  • 画面幅と用途で表示型を切り替える
  • 切替時の状態保持と選択復元を確認する

適さない場面

  • 表示型ごとの差分が巨大で再構築が重い場合
  • 一度の操作で複数データを同時編集する画面