@@ -99,7 +99,10 @@ const VIEWER_HTML = `<!doctype html>
9999 <div class="nav-label">Tokens</div>
100100 <ul id="nav"></ul>
101101 <div class="nav-label nav-label-spaced">Playground</div>
102- <ul><li><a href="#tool-contrast" data-cat="tool-contrast">Contrast</a></li></ul>
102+ <ul>
103+ <li><a href="#tool-contrast" data-cat="tool-contrast">Contrast</a></li>
104+ <li><a href="#tool-typography" data-cat="tool-typography">Typography</a></li>
105+ </ul>
103106 </nav>
104107 </aside>
105108 <main class="content">
@@ -193,6 +196,10 @@ section h2 { font-size: 12px; font-weight: 600; text-transform: uppercase; lette
193196.badge { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; padding: 5px 10px; border-radius: 999px; border: 1px solid var(--border); }
194197.badge.pass { background: color-mix(in srgb, #16a34a 20%, transparent); border-color: #16a34a; }
195198.badge.fail { background: color-mix(in srgb, #dc2626 15%, transparent); border-color: #dc2626; opacity: 0.7; }
199+ .type-play { display: flex; flex-direction: column; gap: 12px; }
200+ .type-play-preview { display: flex; align-items: center; border: 1px solid var(--border); border-radius: 12px; background: var(--card); padding: 32px; min-height: 200px; overflow-wrap: anywhere; }
201+ .type-play-css { border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; font-family: ui-monospace, monospace; font-size: 12px; color: var(--muted); line-height: 1.7; white-space: pre; overflow-x: auto; }
202+ .tool-controls textarea { resize: vertical; min-height: 64px; }
196203`
197204
198205const VIEWER_JS = `const root = document.documentElement
@@ -556,9 +563,70 @@ function renderContrast(container, colors) {
556563 update()
557564}
558565
566+ function optionsFor(tokens, category) {
567+ return tokens
568+ .filter((token) => token.category === category)
569+ .map((token) => '<option value="' + token.value + '">' + token.name + ' (' + token.value + ')</option>')
570+ .join('')
571+ }
572+
573+ function renderTypographyPlayground(container, tokens) {
574+ const fields = [
575+ { prop: 'font-size', category: 'fontSizes', label: 'Font size' },
576+ { prop: 'font-weight', category: 'fontWeights', label: 'Font weight' },
577+ { prop: 'font-family', category: 'fonts', label: 'Font family' },
578+ { prop: 'line-height', category: 'lineHeights', label: 'Line height' },
579+ { prop: 'letter-spacing', category: 'letterSpacings', label: 'Letter spacing' },
580+ ].filter((field) => tokens.some((token) => token.category === field.category))
581+ if (fields.length === 0) return
582+
583+ const section = document.createElement('section')
584+ section.id = 'tool-typography'
585+ section.dataset.cat = 'tool-typography'
586+ const heading = document.createElement('h2')
587+ heading.textContent = 'typography'
588+
589+ const tool = document.createElement('div')
590+ tool.className = 'tool'
591+
592+ const controls = document.createElement('div')
593+ controls.className = 'tool-controls'
594+ controls.innerHTML =
595+ fields
596+ .map(
597+ (field) =>
598+ '<label>' + field.label + '<select data-prop="' + field.prop + '">' + optionsFor(tokens, field.category) + '</select></label>',
599+ )
600+ .join('') + '<label>Sample text<textarea id="type-play-text">The quick brown fox jumps over the lazy dog</textarea></label>'
601+
602+ const output = document.createElement('div')
603+ output.className = 'type-play'
604+ const preview = document.createElement('div')
605+ preview.className = 'type-play-preview'
606+ const cssOut = document.createElement('div')
607+ cssOut.className = 'type-play-css'
608+ output.append(preview, cssOut)
609+
610+ tool.append(controls, output)
611+ section.append(heading, tool)
612+ container.appendChild(section)
613+
614+ const selects = [...controls.querySelectorAll('select')]
615+ const text = controls.querySelector('#type-play-text')
616+ function update() {
617+ for (const select of selects) preview.style.setProperty(select.dataset.prop, select.value)
618+ preview.textContent = text.value
619+ cssOut.textContent = selects.map((select) => select.dataset.prop + ': ' + select.value + ';').join('\\n')
620+ }
621+ for (const select of selects) select.addEventListener('change', update)
622+ text.addEventListener('input', update)
623+ update()
624+ }
625+
559626function renderTools(tokens) {
560627 const tools = document.getElementById('tools')
561628 renderContrast(tools, tokens.filter((token) => token.category === 'colors'))
629+ renderTypographyPlayground(tools, tokens)
562630 updateActiveNav()
563631}
564632
0 commit comments