各ステージのアクションは、そのプロンプトを Set 内で切り替え、aria-pressed を更新し、ラベルを「保存」から「削除」に変更します。リーディングリストの件数と項目は、この Set から導出します。保存済みのステージに戻ると、選択状態が正しく表示されます。「リーディングリストをクリア」を選ぶとすべての選択が解除され、クリアする項目がない場合はボタンが無効になります。
Vite の vanilla プロジェクトを作成し、nsw-design-system 3.27.0 と Public Sans をインストールして、提供されたエントリーファイルと nsw.css を保存します。ルートのマウント要素を用意してください。3 つのステージの全コンテンツ、独自のプロンプト、ネイティブ Tabs の初期化、リーディングリストのイベント処理が含まれています。保存やリセットの処理を疑似コードで代用していません。
バージョンを固定した NSW パッケージと Public Sans のフォントファイルをインストールします。
ルートのマウント要素を用意し、エントリーファイル全体と nsw.css を保存します。
異なる 2 つのタブでプロンプトを保存し、状態が保持されることを確認します。
ArrowRight を使って移動し、リストをクリアして空の状態を確認します。
タブの移動と保存済みコンテンツを別々に管理する
リンク先とパネルが揃ってから、ネイティブの Tabs クラスを初期化します。対応するフラグメント ID が拡張機能の前提です。タブラベルが正しく見えていても、パネルの ID を変更して href を更新しなければ関連付けが壊れます。複数のグループを同じページに配置する場合は、それぞれに異なる ID を付けてください。
import Tabs from 'nsw-design-system/src/components/tabs/tabs.js';
import 'nsw-design-system/css';
import '@fontsource/public-sans/latin-400.css';
import '@fontsource/public-sans/latin-700.css';
import './nsw.css';
const sessions=[['Prepare','Choose one question to explore','Start with a clear outcome rather than a long list of activities.','Write a one-sentence purpose. Identify the people who can answer the question and the evidence you already have.'],['Discuss','Make room for different views','Use a short, structured discussion to compare observations.','Ask participants to describe what they noticed before proposing solutions. Keep a separate record of assumptions that need evidence.'],['Review','Turn observations into a next step','End with an action that has an owner and a clear reason.','Choose one improvement to investigate. Record what would count as success and when the group will review the result.']];
document.getElementById('root').innerHTML=`<main><p class="eyebrow">NSW DESIGN SYSTEM / ORIGINAL WORKSHOP STUDY</p><h1>Plan a focused discussion</h1><p class="nsw-intro">Move between three stages and save useful prompts to a local reading list.</p><div class="layout"><section class="nsw-tabs js-tabs"><ul class="nsw-tabs__list">${sessions.map(([name],i)=>`<li><a href="#session-${i}">${name}</a></li>`).join('')}</ul>${sessions.map(([name,title,lead,body],i)=>`<section id="session-${i}" class="nsw-tabs__content"><p class="step">STAGE ${i+1} / 3</p><h2>${title}</h2><p class="nsw-intro">${lead}</p><p>${body}</p><div class="prompt"><h3>Prompt to keep</h3><blockquote>${['What do we need to understand before choosing a solution?','Which observations are shared, and where do they differ?','What is the smallest next step that would reduce uncertainty?'][i]}</blockquote><button class="nsw-button nsw-button--dark save" data-index="${i}" aria-pressed="false">Save this prompt</button></div></section>`).join('')}</section><aside><h2>Your reading list</h2><p id="count" role="status">0 prompts saved</p><ul id="saved"></ul><p id="empty">Save a prompt from any stage. Changing tabs keeps your selection.</p><button class="nsw-button nsw-button--white" id="clear" disabled>Clear reading list</button><p class="note">This is an original local study using NSW 3.27.0 tabs and CSS. Nothing is stored after reloading.</p></aside></div></main>`;
new Tabs(document.querySelector('.js-tabs')).init();const saved=new Set();const $=id=>document.getElementById(id);function render(){$('count').textContent=saved.size+' prompt'+(saved.size===1?'':'s')+' saved';$('saved').replaceChildren(...[...saved].sort().map(i=>{const li=document.createElement('li');li.textContent=sessions[i][0]+': '+sessions[i][1];return li}));$('empty').hidden=saved.size>0;$('clear').disabled=!saved.size;document.querySelectorAll('.save').forEach(b=>{const yes=saved.has(Number(b.dataset.index));b.setAttribute('aria-pressed',String(yes));b.textContent=yes?'Remove saved prompt':'Save this prompt'})}document.querySelectorAll('.save').forEach(b=>b.addEventListener('click',()=>{const i=Number(b.dataset.index);saved.has(i)?saved.delete(i):saved.add(i);render()}));$('clear').addEventListener('click',()=>{saved.clear();render()});