Skip to content

Commit 8f5b61e

Browse files
committed
feat(cli): add typography playground to panda studio
1 parent bb0b3ab commit 8f5b61e

2 files changed

Lines changed: 74 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@pandacss/cli': patch
3+
---
4+
5+
Add a typography playground to `panda studio`. Combine font-size, weight, family, line-height, and letter-spacing tokens on your own sample text to preview a text style before you build it.

packages/cli/src/studio-codegen.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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
<button class="theme" id="theme" type="button" aria-label="Toggle color theme"></button>
105108
</aside>
@@ -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

198205
const VIEWER_JS = `const root = document.documentElement
@@ -547,9 +554,70 @@ function renderContrast(container, colors) {
547554
update()
548555
}
549556
557+
function optionsFor(tokens, category) {
558+
return tokens
559+
.filter((token) => token.category === category)
560+
.map((token) => '<option value="' + token.value + '">' + token.name + ' (' + token.value + ')</option>')
561+
.join('')
562+
}
563+
564+
function renderTypographyPlayground(container, tokens) {
565+
const fields = [
566+
{ prop: 'font-size', category: 'fontSizes', label: 'Font size' },
567+
{ prop: 'font-weight', category: 'fontWeights', label: 'Font weight' },
568+
{ prop: 'font-family', category: 'fonts', label: 'Font family' },
569+
{ prop: 'line-height', category: 'lineHeights', label: 'Line height' },
570+
{ prop: 'letter-spacing', category: 'letterSpacings', label: 'Letter spacing' },
571+
].filter((field) => tokens.some((token) => token.category === field.category))
572+
if (fields.length === 0) return
573+
574+
const section = document.createElement('section')
575+
section.id = 'tool-typography'
576+
section.dataset.cat = 'tool-typography'
577+
const heading = document.createElement('h2')
578+
heading.textContent = 'typography'
579+
580+
const tool = document.createElement('div')
581+
tool.className = 'tool'
582+
583+
const controls = document.createElement('div')
584+
controls.className = 'tool-controls'
585+
controls.innerHTML =
586+
fields
587+
.map(
588+
(field) =>
589+
'<label>' + field.label + '<select data-prop="' + field.prop + '">' + optionsFor(tokens, field.category) + '</select></label>',
590+
)
591+
.join('') + '<label>Sample text<textarea id="type-play-text">The quick brown fox jumps over the lazy dog</textarea></label>'
592+
593+
const output = document.createElement('div')
594+
output.className = 'type-play'
595+
const preview = document.createElement('div')
596+
preview.className = 'type-play-preview'
597+
const cssOut = document.createElement('div')
598+
cssOut.className = 'type-play-css'
599+
output.append(preview, cssOut)
600+
601+
tool.append(controls, output)
602+
section.append(heading, tool)
603+
container.appendChild(section)
604+
605+
const selects = [...controls.querySelectorAll('select')]
606+
const text = controls.querySelector('#type-play-text')
607+
function update() {
608+
for (const select of selects) preview.style.setProperty(select.dataset.prop, select.value)
609+
preview.textContent = text.value
610+
cssOut.textContent = selects.map((select) => select.dataset.prop + ': ' + select.value + ';').join('\\n')
611+
}
612+
for (const select of selects) select.addEventListener('change', update)
613+
text.addEventListener('input', update)
614+
update()
615+
}
616+
550617
function renderTools(tokens) {
551618
const tools = document.getElementById('tools')
552619
renderContrast(tools, tokens.filter((token) => token.category === 'colors'))
620+
renderTypographyPlayground(tools, tokens)
553621
updateActiveNav()
554622
}
555623

0 commit comments

Comments
 (0)