Skip to content

Commit d902e17

Browse files
Save code editor size preference as percentage (#363)
Co-authored-by: Michael Taranto <michaeltaranto@users.noreply.github.com>
1 parent ee73b75 commit d902e17

9 files changed

+126
-46
lines changed

.changeset/clever-keys-act.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'playroom': minor
3+
---
4+
5+
Save editor height and width preferences as a percentage of the viewport size, rather than a fixed pixel value.
6+
This prevents the editor from obscuring preview panels when toggling the browser tools on/off or resizing the window.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
"@types/react-dom": "^18.0.9",
7979
"@vanilla-extract/css": "^1.9.2",
8080
"@vanilla-extract/css-utils": "^0.1.3",
81+
"@vanilla-extract/dynamic": "^2.1.2",
8182
"@vanilla-extract/sprinkles": "^1.5.1",
8283
"@vanilla-extract/webpack-plugin": "^2.3.6",
8384
"babel-loader": "^9.1.0",

pnpm-lock.yaml

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Playroom/Playroom.css.ts

+29-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1-
import { style, globalStyle } from '@vanilla-extract/css';
1+
import {
2+
style,
3+
globalStyle,
4+
styleVariants,
5+
createVar,
6+
} from '@vanilla-extract/css';
27
import { sprinkles, colorPaletteVars } from './sprinkles.css';
38
import { vars } from './vars.css';
49
import { toolbarItemSize } from './ToolbarItem/ToolbarItem.css';
10+
import { toolbarItemCount, toolbarOpenSize } from './toolbarConstants';
11+
12+
export const MIN_HEIGHT = toolbarItemSize * toolbarItemCount;
13+
export const MIN_WIDTH = toolbarOpenSize + toolbarItemSize + 80;
514

615
globalStyle('html', {
716
width: '100%',
@@ -28,7 +37,19 @@ export const previewContainer = sprinkles({
2837
inset: 0,
2938
});
3039

31-
export const resizeableContainer = style([
40+
export const editorSize = createVar();
41+
42+
export const previewContainerPosition = styleVariants({
43+
right: {
44+
right: `max(${editorSize}, ${MIN_WIDTH}px)`,
45+
},
46+
bottom: {
47+
bottom: `max(${editorSize}, ${MIN_HEIGHT}px)`,
48+
},
49+
undocked: {},
50+
});
51+
52+
export const resizableContainer = style([
3253
sprinkles({
3354
bottom: 0,
3455
right: 0,
@@ -38,34 +59,34 @@ export const resizeableContainer = style([
3859
}),
3960
// @ts-expect-error Shouldnt need to but types do not like `!important`
4061
{
41-
position: 'absolute !important', // override re-resizeable's inline style
62+
position: 'absolute !important', // override re-resizable's inline style
4263
},
4364
]);
4465

45-
export const resizeableContainer_isHidden = style({});
66+
export const resizableContainer_isHidden = style({});
4667

47-
export const resizeableContainer_isRight = style([
68+
export const resizableContainer_isRight = style([
4869
sprinkles({
4970
top: 0,
5071
}),
5172
{
5273
maxWidth: '90vw',
5374
selectors: {
54-
[`&${resizeableContainer_isHidden}`]: {
75+
[`&${resizableContainer_isHidden}`]: {
5576
transform: 'translateX(100%)',
5677
},
5778
},
5879
},
5980
]);
6081

61-
export const resizeableContainer_isBottom = style([
82+
export const resizableContainer_isBottom = style([
6283
sprinkles({
6384
left: 0,
6485
}),
6586
{
6687
maxHeight: '90vh',
6788
selectors: {
68-
[`&${resizeableContainer_isHidden}`]: {
89+
[`&${resizableContainer_isHidden}`]: {
6990
transform: 'translateY(100%)',
7091
},
7192
},

src/Playroom/Playroom.tsx

+22-24
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,20 @@ import Frames from './Frames/Frames';
77
import { WindowPortal } from './WindowPortal';
88
import type { Snippets } from '../../utils';
99
import componentsToHints from '../utils/componentsToHints';
10-
import Toolbar, { toolbarItemCount } from './Toolbar/Toolbar';
10+
import Toolbar from './Toolbar/Toolbar';
1111
import ChevronIcon from './icons/ChevronIcon';
1212
import { StatusMessage } from './StatusMessage/StatusMessage';
1313
import {
1414
StoreContext,
1515
type EditorPosition,
1616
} from '../StoreContext/StoreContext';
1717

18-
const MIN_HEIGHT = toolbarItemSize * toolbarItemCount;
19-
const MIN_WIDTH = toolbarOpenSize + toolbarItemSize + 80;
20-
2118
import { CodeEditor } from './CodeEditor/CodeEditor';
2219

2320
import * as styles from './Playroom.css';
24-
import { toolbarOpenSize } from './Toolbar/Toolbar.css';
25-
import { toolbarItemSize } from './ToolbarItem/ToolbarItem.css';
21+
import { Box } from './Box/Box';
22+
23+
import { assignInlineVars } from '@vanilla-extract/dynamic';
2624

2725
const resizableConfig = (position: EditorPosition = 'bottom') => ({
2826
top: position === 'bottom',
@@ -136,8 +134,8 @@ export default ({ components, themes, widths, snippets }: PlayroomProps) => {
136134
const isVerticalEditor = editorPosition === 'right';
137135
const isHorizontalEditor = editorPosition === 'bottom';
138136
const sizeStyles = {
139-
height: isHorizontalEditor ? `${editorHeight}px` : 'auto', // issue in ff & safari when not a string
140-
width: isVerticalEditor ? `${editorWidth}px` : 'auto',
137+
height: isHorizontalEditor ? editorHeight : 'auto',
138+
width: isVerticalEditor ? editorWidth : 'auto',
141139
};
142140
const editorContainer =
143141
editorPosition === 'undocked' ? (
@@ -151,15 +149,15 @@ export default ({ components, themes, widths, snippets }: PlayroomProps) => {
151149
</WindowPortal>
152150
) : (
153151
<Resizable
154-
className={classnames(styles.resizeableContainer, {
155-
[styles.resizeableContainer_isRight]: isVerticalEditor,
156-
[styles.resizeableContainer_isBottom]: isHorizontalEditor,
157-
[styles.resizeableContainer_isHidden]: editorHidden,
152+
className={classnames(styles.resizableContainer, {
153+
[styles.resizableContainer_isRight]: isVerticalEditor,
154+
[styles.resizableContainer_isBottom]: isHorizontalEditor,
155+
[styles.resizableContainer_isHidden]: editorHidden,
158156
})}
159157
defaultSize={sizeStyles}
160158
size={sizeStyles}
161-
minWidth={isVerticalEditor ? MIN_WIDTH : undefined}
162-
minHeight={MIN_HEIGHT}
159+
minWidth={isVerticalEditor ? styles.MIN_WIDTH : undefined}
160+
minHeight={styles.MIN_HEIGHT}
163161
onResize={(_event, _direction, { offsetWidth, offsetHeight }) => {
164162
updateEditorSize({ isVerticalEditor, offsetWidth, offsetHeight });
165163
}}
@@ -176,17 +174,17 @@ export default ({ components, themes, widths, snippets }: PlayroomProps) => {
176174
<title>{displayedTitle}</title>
177175
</Helmet>
178176
)}
179-
<div
180-
className={styles.previewContainer}
181-
style={
177+
<Box
178+
className={[
179+
styles.previewContainer,
182180
editorHidden
183181
? undefined
184-
: {
185-
right: { right: editorWidth },
186-
bottom: { bottom: editorHeight },
187-
undocked: undefined,
188-
}[editorPosition]
189-
}
182+
: styles.previewContainerPosition[editorPosition],
183+
]}
184+
style={assignInlineVars({
185+
[styles.editorSize]:
186+
editorPosition === 'right' ? editorWidth : editorHeight,
187+
})}
190188
>
191189
<Frames
192190
code={previewRenderCode || code}
@@ -215,7 +213,7 @@ export default ({ components, themes, widths, snippets }: PlayroomProps) => {
215213
/>
216214
</button>
217215
</div>
218-
</div>
216+
</Box>
219217
{editorContainer}
220218
</div>
221219
);

src/Playroom/Toolbar/Toolbar.css.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { calc } from '@vanilla-extract/css-utils';
22
import { style } from '@vanilla-extract/css';
33
import { sprinkles, colorPaletteVars } from '../sprinkles.css';
44
import { toolbarItemSize } from '../ToolbarItem/ToolbarItem.css';
5+
import { toolbarOpenSize } from '../toolbarConstants';
56

6-
export const toolbarOpenSize = 320;
77
const toolbarBorderThickness = '1px';
88

99
export const isOpen = style({});

src/Playroom/Toolbar/Toolbar.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ interface Props {
2525
snippets: PlayroomProps['snippets'];
2626
}
2727

28-
export const toolbarItemCount = 5;
2928
const ANIMATION_TIMEOUT = 300;
3029

3130
export default ({ themes: allThemes, widths: allWidths, snippets }: Props) => {

src/Playroom/toolbarConstants.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Isolating these constants from React files so they can be used in Vanilla Extract styles
2+
3+
export const toolbarItemCount = 5;
4+
export const toolbarItemSize = 60;
5+
export const toolbarOpenSize = 320;

src/StoreContext/StoreContext.tsx

+49-12
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const store = localforage.createInstance({
2525
version: 1,
2626
});
2727

28+
const defaultEditorSize = '40%';
2829
const defaultPosition = 'bottom';
2930

3031
export type EditorPosition = 'bottom' | 'right' | 'undocked';
@@ -36,6 +37,24 @@ const applyColorScheme = (colorScheme: Exclude<ColorScheme, 'system'>) => {
3637
]('data-playroom-dark', '');
3738
};
3839

40+
function convertAndStoreSizeAsPercentage(
41+
mode: 'height' | 'width',
42+
size: number
43+
): string {
44+
const viewportSize =
45+
mode === 'height' ? window.innerHeight : window.innerWidth;
46+
47+
const sizePercentage = (size / viewportSize) * 100;
48+
const roundedSizePercentage = `${Math.round(sizePercentage)}%`;
49+
50+
store.setItem(
51+
`${mode === 'height' ? 'editorHeight' : 'editorWidth'}`,
52+
roundedSizePercentage
53+
);
54+
55+
return `${sizePercentage}%`;
56+
}
57+
3958
interface DebounceUpdateUrl {
4059
code?: string;
4160
themes?: string[];
@@ -65,8 +84,8 @@ interface State {
6584
cursorPosition: CursorPosition;
6685
editorHidden: boolean;
6786
editorPosition: EditorPosition;
68-
editorHeight: number;
69-
editorWidth: number;
87+
editorHeight: string;
88+
editorWidth: string;
7089
statusMessage?: StatusMessage;
7190
visibleThemes?: string[];
7291
visibleWidths?: number[];
@@ -328,21 +347,28 @@ const createReducer =
328347

329348
case 'updateEditorHeight': {
330349
const { size } = action.payload;
331-
store.setItem('editorHeight', size);
350+
351+
const updatedHeightPercentage = convertAndStoreSizeAsPercentage(
352+
'height',
353+
size
354+
);
332355

333356
return {
334357
...state,
335-
editorHeight: size,
358+
editorHeight: updatedHeightPercentage,
336359
};
337360
}
338361

339362
case 'updateEditorWidth': {
340363
const { size } = action.payload;
341-
store.setItem('editorWidth', size);
364+
const updatedWidthPercentage = convertAndStoreSizeAsPercentage(
365+
'width',
366+
size
367+
);
342368

343369
return {
344370
...state,
345-
editorWidth: size,
371+
editorWidth: updatedWidthPercentage,
346372
};
347373
}
348374

@@ -408,8 +434,8 @@ const initialState: State = {
408434
cursorPosition: { line: 0, ch: 0 },
409435
editorHidden: false,
410436
editorPosition: defaultPosition,
411-
editorHeight: 300,
412-
editorWidth: 360,
437+
editorHeight: defaultEditorSize,
438+
editorWidth: defaultEditorSize,
413439
ready: false,
414440
colorScheme: 'light',
415441
};
@@ -473,8 +499,8 @@ export const StoreProvider = ({
473499
Promise.all([
474500
store.getItem<string>('code'),
475501
store.getItem<EditorPosition>('editorPosition'),
476-
store.getItem<number>('editorHeight'),
477-
store.getItem<number>('editorWidth'),
502+
store.getItem<string | number>('editorHeight'), // Number type deprecated
503+
store.getItem<string | number>('editorWidth'), // Number type deprecated
478504
store.getItem<number[]>('visibleWidths'),
479505
store.getItem<string[]>('visibleThemes'),
480506
store.getItem<ColorScheme>('colorScheme'),
@@ -490,17 +516,28 @@ export const StoreProvider = ({
490516
]) => {
491517
const code = codeFromQuery || storedCode || exampleCode;
492518
const editorPosition = storedPosition;
493-
const editorHeight = storedHeight;
494-
const editorWidth = storedWidth;
519+
520+
const editorHeight =
521+
(typeof storedHeight === 'number'
522+
? convertAndStoreSizeAsPercentage('height', storedHeight)
523+
: storedHeight) || defaultEditorSize;
524+
525+
const editorWidth =
526+
(typeof storedWidth === 'number'
527+
? convertAndStoreSizeAsPercentage('width', storedWidth)
528+
: storedWidth) || defaultEditorSize;
529+
495530
const visibleWidths =
496531
widthsFromQuery ||
497532
storedVisibleWidths ||
498533
playroomConfig?.defaultVisibleWidths;
534+
499535
const visibleThemes =
500536
hasThemesConfigured &&
501537
(themesFromQuery ||
502538
storedVisibleThemes ||
503539
playroomConfig?.defaultVisibleThemes);
540+
504541
const colorScheme = storedColorScheme;
505542

506543
dispatch({

0 commit comments

Comments
 (0)