Skip to content

Commit

Permalink
Save code editor size preference as percentage (#363)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Taranto <[email protected]>
  • Loading branch information
felixhabib and michaeltaranto authored Sep 17, 2024
1 parent ee73b75 commit d902e17
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 46 deletions.
6 changes: 6 additions & 0 deletions .changeset/clever-keys-act.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'playroom': minor
---

Save editor height and width preferences as a percentage of the viewport size, rather than a fixed pixel value.
This prevents the editor from obscuring preview panels when toggling the browser tools on/off or resizing the window.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"@types/react-dom": "^18.0.9",
"@vanilla-extract/css": "^1.9.2",
"@vanilla-extract/css-utils": "^0.1.3",
"@vanilla-extract/dynamic": "^2.1.2",
"@vanilla-extract/sprinkles": "^1.5.1",
"@vanilla-extract/webpack-plugin": "^2.3.6",
"babel-loader": "^9.1.0",
Expand Down
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 29 additions & 8 deletions src/Playroom/Playroom.css.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { style, globalStyle } from '@vanilla-extract/css';
import {
style,
globalStyle,
styleVariants,
createVar,
} from '@vanilla-extract/css';
import { sprinkles, colorPaletteVars } from './sprinkles.css';
import { vars } from './vars.css';
import { toolbarItemSize } from './ToolbarItem/ToolbarItem.css';
import { toolbarItemCount, toolbarOpenSize } from './toolbarConstants';

export const MIN_HEIGHT = toolbarItemSize * toolbarItemCount;
export const MIN_WIDTH = toolbarOpenSize + toolbarItemSize + 80;

globalStyle('html', {
width: '100%',
Expand All @@ -28,7 +37,19 @@ export const previewContainer = sprinkles({
inset: 0,
});

export const resizeableContainer = style([
export const editorSize = createVar();

export const previewContainerPosition = styleVariants({
right: {
right: `max(${editorSize}, ${MIN_WIDTH}px)`,
},
bottom: {
bottom: `max(${editorSize}, ${MIN_HEIGHT}px)`,
},
undocked: {},
});

export const resizableContainer = style([
sprinkles({
bottom: 0,
right: 0,
Expand All @@ -38,34 +59,34 @@ export const resizeableContainer = style([
}),
// @ts-expect-error Shouldnt need to but types do not like `!important`
{
position: 'absolute !important', // override re-resizeable's inline style
position: 'absolute !important', // override re-resizable's inline style
},
]);

export const resizeableContainer_isHidden = style({});
export const resizableContainer_isHidden = style({});

export const resizeableContainer_isRight = style([
export const resizableContainer_isRight = style([
sprinkles({
top: 0,
}),
{
maxWidth: '90vw',
selectors: {
[`&${resizeableContainer_isHidden}`]: {
[`&${resizableContainer_isHidden}`]: {
transform: 'translateX(100%)',
},
},
},
]);

export const resizeableContainer_isBottom = style([
export const resizableContainer_isBottom = style([
sprinkles({
left: 0,
}),
{
maxHeight: '90vh',
selectors: {
[`&${resizeableContainer_isHidden}`]: {
[`&${resizableContainer_isHidden}`]: {
transform: 'translateY(100%)',
},
},
Expand Down
46 changes: 22 additions & 24 deletions src/Playroom/Playroom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,20 @@ import Frames from './Frames/Frames';
import { WindowPortal } from './WindowPortal';
import type { Snippets } from '../../utils';
import componentsToHints from '../utils/componentsToHints';
import Toolbar, { toolbarItemCount } from './Toolbar/Toolbar';
import Toolbar from './Toolbar/Toolbar';
import ChevronIcon from './icons/ChevronIcon';
import { StatusMessage } from './StatusMessage/StatusMessage';
import {
StoreContext,
type EditorPosition,
} from '../StoreContext/StoreContext';

const MIN_HEIGHT = toolbarItemSize * toolbarItemCount;
const MIN_WIDTH = toolbarOpenSize + toolbarItemSize + 80;

import { CodeEditor } from './CodeEditor/CodeEditor';

import * as styles from './Playroom.css';
import { toolbarOpenSize } from './Toolbar/Toolbar.css';
import { toolbarItemSize } from './ToolbarItem/ToolbarItem.css';
import { Box } from './Box/Box';

import { assignInlineVars } from '@vanilla-extract/dynamic';

const resizableConfig = (position: EditorPosition = 'bottom') => ({
top: position === 'bottom',
Expand Down Expand Up @@ -136,8 +134,8 @@ export default ({ components, themes, widths, snippets }: PlayroomProps) => {
const isVerticalEditor = editorPosition === 'right';
const isHorizontalEditor = editorPosition === 'bottom';
const sizeStyles = {
height: isHorizontalEditor ? `${editorHeight}px` : 'auto', // issue in ff & safari when not a string
width: isVerticalEditor ? `${editorWidth}px` : 'auto',
height: isHorizontalEditor ? editorHeight : 'auto',
width: isVerticalEditor ? editorWidth : 'auto',
};
const editorContainer =
editorPosition === 'undocked' ? (
Expand All @@ -151,15 +149,15 @@ export default ({ components, themes, widths, snippets }: PlayroomProps) => {
</WindowPortal>
) : (
<Resizable
className={classnames(styles.resizeableContainer, {
[styles.resizeableContainer_isRight]: isVerticalEditor,
[styles.resizeableContainer_isBottom]: isHorizontalEditor,
[styles.resizeableContainer_isHidden]: editorHidden,
className={classnames(styles.resizableContainer, {
[styles.resizableContainer_isRight]: isVerticalEditor,
[styles.resizableContainer_isBottom]: isHorizontalEditor,
[styles.resizableContainer_isHidden]: editorHidden,
})}
defaultSize={sizeStyles}
size={sizeStyles}
minWidth={isVerticalEditor ? MIN_WIDTH : undefined}
minHeight={MIN_HEIGHT}
minWidth={isVerticalEditor ? styles.MIN_WIDTH : undefined}
minHeight={styles.MIN_HEIGHT}
onResize={(_event, _direction, { offsetWidth, offsetHeight }) => {
updateEditorSize({ isVerticalEditor, offsetWidth, offsetHeight });
}}
Expand All @@ -176,17 +174,17 @@ export default ({ components, themes, widths, snippets }: PlayroomProps) => {
<title>{displayedTitle}</title>
</Helmet>
)}
<div
className={styles.previewContainer}
style={
<Box
className={[
styles.previewContainer,
editorHidden
? undefined
: {
right: { right: editorWidth },
bottom: { bottom: editorHeight },
undocked: undefined,
}[editorPosition]
}
: styles.previewContainerPosition[editorPosition],
]}
style={assignInlineVars({
[styles.editorSize]:
editorPosition === 'right' ? editorWidth : editorHeight,
})}
>
<Frames
code={previewRenderCode || code}
Expand Down Expand Up @@ -215,7 +213,7 @@ export default ({ components, themes, widths, snippets }: PlayroomProps) => {
/>
</button>
</div>
</div>
</Box>
{editorContainer}
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/Playroom/Toolbar/Toolbar.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { calc } from '@vanilla-extract/css-utils';
import { style } from '@vanilla-extract/css';
import { sprinkles, colorPaletteVars } from '../sprinkles.css';
import { toolbarItemSize } from '../ToolbarItem/ToolbarItem.css';
import { toolbarOpenSize } from '../toolbarConstants';

export const toolbarOpenSize = 320;
const toolbarBorderThickness = '1px';

export const isOpen = style({});
Expand Down
1 change: 0 additions & 1 deletion src/Playroom/Toolbar/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ interface Props {
snippets: PlayroomProps['snippets'];
}

export const toolbarItemCount = 5;
const ANIMATION_TIMEOUT = 300;

export default ({ themes: allThemes, widths: allWidths, snippets }: Props) => {
Expand Down
5 changes: 5 additions & 0 deletions src/Playroom/toolbarConstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Isolating these constants from React files so they can be used in Vanilla Extract styles

export const toolbarItemCount = 5;
export const toolbarItemSize = 60;
export const toolbarOpenSize = 320;
61 changes: 49 additions & 12 deletions src/StoreContext/StoreContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const store = localforage.createInstance({
version: 1,
});

const defaultEditorSize = '40%';
const defaultPosition = 'bottom';

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

function convertAndStoreSizeAsPercentage(
mode: 'height' | 'width',
size: number
): string {
const viewportSize =
mode === 'height' ? window.innerHeight : window.innerWidth;

const sizePercentage = (size / viewportSize) * 100;
const roundedSizePercentage = `${Math.round(sizePercentage)}%`;

store.setItem(
`${mode === 'height' ? 'editorHeight' : 'editorWidth'}`,
roundedSizePercentage
);

return `${sizePercentage}%`;
}

interface DebounceUpdateUrl {
code?: string;
themes?: string[];
Expand Down Expand Up @@ -65,8 +84,8 @@ interface State {
cursorPosition: CursorPosition;
editorHidden: boolean;
editorPosition: EditorPosition;
editorHeight: number;
editorWidth: number;
editorHeight: string;
editorWidth: string;
statusMessage?: StatusMessage;
visibleThemes?: string[];
visibleWidths?: number[];
Expand Down Expand Up @@ -328,21 +347,28 @@ const createReducer =

case 'updateEditorHeight': {
const { size } = action.payload;
store.setItem('editorHeight', size);

const updatedHeightPercentage = convertAndStoreSizeAsPercentage(
'height',
size
);

return {
...state,
editorHeight: size,
editorHeight: updatedHeightPercentage,
};
}

case 'updateEditorWidth': {
const { size } = action.payload;
store.setItem('editorWidth', size);
const updatedWidthPercentage = convertAndStoreSizeAsPercentage(
'width',
size
);

return {
...state,
editorWidth: size,
editorWidth: updatedWidthPercentage,
};
}

Expand Down Expand Up @@ -408,8 +434,8 @@ const initialState: State = {
cursorPosition: { line: 0, ch: 0 },
editorHidden: false,
editorPosition: defaultPosition,
editorHeight: 300,
editorWidth: 360,
editorHeight: defaultEditorSize,
editorWidth: defaultEditorSize,
ready: false,
colorScheme: 'light',
};
Expand Down Expand Up @@ -473,8 +499,8 @@ export const StoreProvider = ({
Promise.all([
store.getItem<string>('code'),
store.getItem<EditorPosition>('editorPosition'),
store.getItem<number>('editorHeight'),
store.getItem<number>('editorWidth'),
store.getItem<string | number>('editorHeight'), // Number type deprecated
store.getItem<string | number>('editorWidth'), // Number type deprecated
store.getItem<number[]>('visibleWidths'),
store.getItem<string[]>('visibleThemes'),
store.getItem<ColorScheme>('colorScheme'),
Expand All @@ -490,17 +516,28 @@ export const StoreProvider = ({
]) => {
const code = codeFromQuery || storedCode || exampleCode;
const editorPosition = storedPosition;
const editorHeight = storedHeight;
const editorWidth = storedWidth;

const editorHeight =
(typeof storedHeight === 'number'
? convertAndStoreSizeAsPercentage('height', storedHeight)
: storedHeight) || defaultEditorSize;

const editorWidth =
(typeof storedWidth === 'number'
? convertAndStoreSizeAsPercentage('width', storedWidth)
: storedWidth) || defaultEditorSize;

const visibleWidths =
widthsFromQuery ||
storedVisibleWidths ||
playroomConfig?.defaultVisibleWidths;

const visibleThemes =
hasThemesConfigured &&
(themesFromQuery ||
storedVisibleThemes ||
playroomConfig?.defaultVisibleThemes);

const colorScheme = storedColorScheme;

dispatch({
Expand Down

0 comments on commit d902e17

Please sign in to comment.