Skip to content

Commit fd9e261

Browse files
feat: layout options.
1 parent 0079a6d commit fd9e261

3 files changed

Lines changed: 163 additions & 1 deletion

File tree

src/app.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { createCodeMirrorEditor } from './editor-codemirror.js'
33
import { defaultCss, defaultJsx } from './defaults.js'
44

55
const statusNode = document.getElementById('status')
6+
const appGrid = document.querySelector('.app-grid')
7+
const appGridLayoutButtons = document.querySelectorAll('[data-app-grid-layout]')
68
const renderMode = document.getElementById('render-mode')
79
const autoRenderToggle = document.getElementById('auto-render')
810
const renderButton = document.getElementById('render-button')
@@ -44,6 +46,7 @@ let pendingClearAction = null
4446
let hasCompletedInitialRender = false
4547
let previewBackgroundColor = null
4648
const clipboardSupported = Boolean(navigator.clipboard?.writeText)
49+
const appGridLayoutStorageKey = 'knighted-develop:app-grid-layout'
4750

4851
const styleLabels = {
4952
css: 'Native CSS',
@@ -142,6 +145,43 @@ const setStatus = text => {
142145
statusNode.textContent = text
143146
}
144147

148+
const appGridLayouts = ['default', 'preview-right', 'preview-left']
149+
150+
const applyAppGridLayout = (layout, { persist = true } = {}) => {
151+
if (!appGrid || !appGridLayouts.includes(layout)) {
152+
return
153+
}
154+
155+
appGrid.classList.toggle('app-grid--preview-right', layout === 'preview-right')
156+
appGrid.classList.toggle('app-grid--preview-left', layout === 'preview-left')
157+
158+
for (const button of appGridLayoutButtons) {
159+
const isActive = button.dataset.appGridLayout === layout
160+
button.setAttribute('aria-pressed', isActive ? 'true' : 'false')
161+
}
162+
163+
if (persist) {
164+
try {
165+
localStorage.setItem(appGridLayoutStorageKey, layout)
166+
} catch {
167+
/* Ignore storage write errors in restricted browsing modes. */
168+
}
169+
}
170+
}
171+
172+
const getInitialAppGridLayout = () => {
173+
try {
174+
const value = localStorage.getItem(appGridLayoutStorageKey)
175+
if (appGridLayouts.includes(value)) {
176+
return value
177+
}
178+
} catch {
179+
/* Ignore storage read errors in restricted browsing modes. */
180+
}
181+
182+
return 'default'
183+
}
184+
145185
const setCdnLoading = isLoading => {
146186
if (!cdnLoading) return
147187
cdnLoading.hidden = !isLoading
@@ -892,6 +932,18 @@ clearStylesButton.addEventListener('click', () => {
892932
jsxEditor.addEventListener('input', maybeRender)
893933
cssEditor.addEventListener('input', maybeRender)
894934

935+
for (const button of appGridLayoutButtons) {
936+
button.addEventListener('click', () => {
937+
const nextLayout = button.dataset.appGridLayout
938+
if (!nextLayout) {
939+
return
940+
}
941+
applyAppGridLayout(nextLayout)
942+
})
943+
}
944+
945+
applyAppGridLayout(getInitialAppGridLayout(), { persist: false })
946+
895947
updateRenderButtonVisibility()
896948
setStyleCompiling(false)
897949
setCdnLoading(true)

src/index.html

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,54 @@ <h1>
2424
</header>
2525

2626
<main class="app-grid">
27+
<div class="app-grid-layout-controls" role="group" aria-label="App grid layout">
28+
<button
29+
class="layout-toggle"
30+
id="layout-default"
31+
type="button"
32+
data-app-grid-layout="default"
33+
aria-pressed="true"
34+
title="Component + Styles on top, Preview below"
35+
aria-label="Use stacked preview layout"
36+
>
37+
<svg viewBox="0 0 24 24" aria-hidden="true">
38+
<rect x="3" y="3" width="8" height="7" rx="1"></rect>
39+
<rect x="13" y="3" width="8" height="7" rx="1"></rect>
40+
<rect x="3" y="12" width="18" height="9" rx="1"></rect>
41+
</svg>
42+
</button>
43+
<button
44+
class="layout-toggle"
45+
id="layout-preview-right"
46+
type="button"
47+
data-app-grid-layout="preview-right"
48+
aria-pressed="false"
49+
title="Component + Styles on left, Preview on right"
50+
aria-label="Use side preview layout"
51+
>
52+
<svg viewBox="0 0 24 24" aria-hidden="true">
53+
<rect x="3" y="3" width="8" height="8" rx="1"></rect>
54+
<rect x="3" y="13" width="8" height="8" rx="1"></rect>
55+
<rect x="13" y="3" width="8" height="18" rx="1"></rect>
56+
</svg>
57+
</button>
58+
<button
59+
class="layout-toggle"
60+
id="layout-preview-left"
61+
type="button"
62+
data-app-grid-layout="preview-left"
63+
aria-pressed="false"
64+
title="Preview on left, Component + Styles on right"
65+
aria-label="Use left preview layout"
66+
>
67+
<svg viewBox="0 0 24 24" aria-hidden="true">
68+
<rect x="3" y="3" width="8" height="18" rx="1"></rect>
69+
<rect x="13" y="3" width="8" height="8" rx="1"></rect>
70+
<rect x="13" y="13" width="8" height="8" rx="1"></rect>
71+
</svg>
72+
</button>
73+
</div>
74+
2775
<section class="panel component-panel">
2876
<div class="panel-header panel-header--grid">
2977
<h2>Component</h2>

src/styles.css

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,69 @@ body {
7878
display: grid;
7979
grid-template-columns: repeat(2, minmax(320px, 1fr));
8080
grid-template-areas:
81+
'layout-controls layout-controls'
8182
'component styles'
8283
'preview preview';
8384
gap: 18px;
8485
padding: 24px;
8586
}
8687

88+
.app-grid--preview-right {
89+
grid-template-areas:
90+
'layout-controls layout-controls'
91+
'component preview'
92+
'styles preview';
93+
}
94+
95+
.app-grid--preview-left {
96+
grid-template-areas:
97+
'layout-controls layout-controls'
98+
'preview component'
99+
'preview styles';
100+
}
101+
102+
.app-grid-layout-controls {
103+
grid-area: layout-controls;
104+
display: inline-flex;
105+
justify-self: end;
106+
gap: 10px;
107+
}
108+
109+
.layout-toggle {
110+
display: inline-grid;
111+
place-content: center;
112+
width: 36px;
113+
height: 36px;
114+
border: 1px solid rgba(255, 255, 255, 0.16);
115+
border-radius: 10px;
116+
background: rgba(255, 255, 255, 0.06);
117+
color: #d8e0ef;
118+
cursor: pointer;
119+
}
120+
121+
.layout-toggle svg {
122+
width: 18px;
123+
height: 18px;
124+
fill: none;
125+
stroke: currentColor;
126+
stroke-width: 1.5;
127+
}
128+
129+
.layout-toggle:hover {
130+
background: rgba(255, 255, 255, 0.12);
131+
}
132+
133+
.layout-toggle[aria-pressed='true'] {
134+
border-color: rgba(122, 107, 255, 0.65);
135+
background: rgba(122, 107, 255, 0.2);
136+
color: #f1f5ff;
137+
}
138+
139+
.layout-toggle:focus-visible {
140+
outline: 2px solid rgba(122, 107, 255, 0.85);
141+
outline-offset: 2px;
142+
}
143+
87144
.component-panel {
88145
grid-area: component;
89146
max-height: min(64vh, 620px);
@@ -105,10 +162,15 @@ body {
105162
.app-grid {
106163
grid-template-columns: minmax(0, 1fr);
107164
grid-template-areas:
165+
'layout-controls'
108166
'component'
109167
'styles'
110168
'preview';
111169
}
170+
171+
.app-grid-layout-controls {
172+
justify-self: start;
173+
}
112174
}
113175

114176
.panel {
@@ -271,7 +333,7 @@ textarea:focus {
271333
}
272334

273335
.preview-host {
274-
flex: 0 1 auto;
336+
flex: 1 1 auto;
275337
min-height: 180px;
276338
padding: 18px;
277339
overflow: auto;

0 commit comments

Comments
 (0)