Device preview + responsive spacing in the Style panel

Style panel gets a real-viewport iframe preview (390/820/1280px) tied to the
breakpoint tab, reloaded after each edit, and spacing/gap controls per
breakpoint. The editor does not boot inside the preview frame. Mock and
tests mirror the new k6n layout template.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
matsandClaude Sonnet 5.5 committed 2026-11-01 11:02:20 +01:00
1 parent cb7a7a1fab
commit ec4212c113
7 files changed
+136 -9

No files matched your search

+10 -3
View File
@@ -53,6 +53,9 @@ export const schemas: Record<string, any> = {
properties: {
flow: { 'x-style': { kind: 'flow' }, type: 'string', enum: ['row', 'stack', 'actions'] },
columns: { 'x-style': { kind: 'columns' }, type: 'integer', minimum: 1, maximum: 12 },
gap: { 'x-style': { kind: 'gap', token: '--k6n-sp-{value}' }, type: 'string', enum: ['none', 'xs', 'sm', 'md', 'lg', 'xl'] },
paddingTop: { 'x-style': { kind: 'spacing', box: 'padding', side: 'top', overrides: 'padding', token: '--k6n-sp-{value}' }, type: 'string', enum: ['none', 'xs', 'sm', 'md', 'lg', 'xl'] },
paddingBottom: { 'x-style': { kind: 'spacing', box: 'padding', side: 'bottom', overrides: 'padding', token: '--k6n-sp-{value}' }, type: 'string', enum: ['none', 'xs', 'sm', 'md', 'lg', 'xl'] },
},
additionalProperties: false,
},
@@ -193,9 +196,13 @@ export function render(g: Graph, id: string): string {
.map(([k, c]) => ` k6n-${c}-${d[k]}`)
.join('')
const r = d.responsive ?? {}
const resp = [['mobile', 'm'], ['tablet', 't'], ['desktop', 'd']]
.flatMap(([bp, c]) => [r[bp]?.flow ? ` k6n-layout--${c}-${r[bp].flow}` : '', r[bp]?.columns ? ` k6n-layout--${c}c-${r[bp].columns}` : ''])
.join('')
// Mirrors data/k6n/templates/layout.gohtml: tablet flow class only when it differs; spacing as k6n-<t|m>-<prop>-<size>.
const norm = (f: string) => ({ vertical: 'stack', horizontal: 'row', grid: 'row' } as Record<string, string>)[f] ?? f
const tf = r.tablet?.flow ? norm(r.tablet.flow) : flow
const resp = (tf !== flow ? ` k6n-layout--t-${tf}` : '') +
[['tablet', 't'], ['mobile', 'm']]
.flatMap(([bp, c]) => [['gap', 'gap'], ['padding', 'p'], ['paddingTop', 'pt'], ['paddingBottom', 'pb']].filter(([k]) => r[bp]?.[k]).map(([k, n]) => ` k6n-${c}-${n}-${r[bp][k]}`))
.join('')
const tone = d.tone && d.tone !== 'none' ? ` k6n-${d.tone}` : ''
const cells = g.children(id).map((e) => `<div class="k6n-layout__cell">${render(g, e.to)}</div>`).join('\n')
return `<div class="k6n-layout k6n-layout--${flow} k6n-gap-${d.gap ?? 'md'}${sp}${tone}${resp}" style="--k6n-cols:${d.columns ?? 2}" ${attrs} data-cms-container="true">\n${cells}\n</div>`