Fix playground handling of visible scrollbars (#1514)
Summary: Pull Request resolved: https://github.com/facebook/yoga/pull/1514 On machines with scrollbars, we shouldn't show them unconditionally, and the toolbar should be in the area within them. This also fixes a couple bugs: 1. Preview not rendering based on correct code when light/dark mode changes 2. Crash on start on mobile safari 3. Incorrect rendering of preview on mobile safari This also fixes a bug where the playground re-rendering (e.g. on theme change) makes the preview snap back to the initial code passes. https://yoga-website-next-git-fork-nickgerleman-exp-194d90-fbopensource.vercel.app/ Reviewed By: shwanton Differential Revision: D52145666 fbshipit-source-id: 50184305987aab4cbcd066f37582997dfdc78c02
This commit is contained in:
committed by
Facebook GitHub Bot
parent
738d04fcb0
commit
43cb24fdce
@@ -8,9 +8,6 @@
|
|||||||
.toolbar {
|
.toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
column-gap: 8px;
|
column-gap: 8px;
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar button {
|
.toolbar button {
|
||||||
|
@@ -18,9 +18,15 @@ import styles from './EditorToolbar.module.css';
|
|||||||
|
|
||||||
export type Props = Readonly<{
|
export type Props = Readonly<{
|
||||||
getCode: () => string;
|
getCode: () => string;
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export default function EditorToolbar({getCode}: Props): JSX.Element {
|
export default function EditorToolbar({
|
||||||
|
getCode,
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
}: Props): JSX.Element {
|
||||||
const handleCopy = useCallback(
|
const handleCopy = useCallback(
|
||||||
() => navigator.clipboard.writeText(getCode()),
|
() => navigator.clipboard.writeText(getCode()),
|
||||||
[],
|
[],
|
||||||
@@ -36,7 +42,7 @@ export default function EditorToolbar({getCode}: Props): JSX.Element {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx(styles.toolbar)}>
|
<div className={clsx(styles.toolbar, className)} style={style}>
|
||||||
<ToolbarButton Icon={CopyIcon} label="Copy" onClick={handleCopy} />
|
<ToolbarButton Icon={CopyIcon} label="Copy" onClick={handleCopy} />
|
||||||
<ToolbarButton Icon={LinkIcon} label="Share" onClick={handleShare} />
|
<ToolbarButton Icon={LinkIcon} label="Share" onClick={handleShare} />
|
||||||
</div>
|
</div>
|
||||||
|
@@ -26,30 +26,42 @@ html[data-theme='dark'] {
|
|||||||
background-color: var(--yg-color-playground-background);
|
background-color: var(--yg-color-playground-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.editorColumn {
|
|
||||||
position: relative;
|
|
||||||
flex: 8;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playgroundRow {
|
.playgroundRow {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
column-gap: 16px;
|
column-gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editorColumn {
|
||||||
|
flex: 8;
|
||||||
|
min-width: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid var(--yg-color-editor-border);
|
||||||
|
border-radius: var(--ifm-pre-border-radius);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorScroll {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorToolbar {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.playgroundEditor {
|
.playgroundEditor {
|
||||||
font: var(--ifm-code-font-size) / var(--ifm-pre-line-height)
|
font: var(--ifm-code-font-size) / var(--ifm-pre-line-height)
|
||||||
var(--ifm-font-family-monospace) !important;
|
var(--ifm-font-family-monospace) !important;
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
height: calc(var(--yg-playground-height, 400px) - 32px);
|
height: calc(var(--yg-playground-height, 400px) - 32px);
|
||||||
overflow: scroll;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.playgroundEditor :global(.prism-code) {
|
.playgroundEditor :global(.prism-code) {
|
||||||
height: 100%;
|
|
||||||
box-shadow: var(--ifm-global-shadow-lw);
|
box-shadow: var(--ifm-global-shadow-lw);
|
||||||
border: 1px solid var(--yg-color-editor-border);
|
min-height: 100%;
|
||||||
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.previewColumn {
|
.previewColumn {
|
||||||
@@ -86,8 +98,7 @@ html[data-theme='dark'] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.playgroundEditor {
|
.playgroundEditor {
|
||||||
height: max-content;
|
height: unset;
|
||||||
overflow: visible;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.playgroundRow {
|
.playgroundRow {
|
||||||
@@ -97,12 +108,13 @@ html[data-theme='dark'] {
|
|||||||
|
|
||||||
.editorColumn {
|
.editorColumn {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
flex: 0 !important;
|
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
flex: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.previewColumn {
|
.previewColumn {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
flex: unset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,7 @@ import React, {
|
|||||||
lazy,
|
lazy,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
@@ -45,7 +46,11 @@ export type Props = Readonly<{
|
|||||||
export default function Playground({code, height, autoFocus}: Props) {
|
export default function Playground({code, height, autoFocus}: Props) {
|
||||||
const prismTheme = usePrismTheme();
|
const prismTheme = usePrismTheme();
|
||||||
const playgroundRef = useRef<HTMLDivElement>(null);
|
const playgroundRef = useRef<HTMLDivElement>(null);
|
||||||
|
const editorScrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const [isLoaded, setIsLoaded] = useState(false);
|
const [isLoaded, setIsLoaded] = useState(false);
|
||||||
|
const [liveCode, setLiveCode] = useState(code ?? defaultCode);
|
||||||
|
const [scrollbarWidth, setScrollbarWidth] = useState(0);
|
||||||
|
|
||||||
const LivePreviewWrapper = useCallback(
|
const LivePreviewWrapper = useCallback(
|
||||||
(props: React.ComponentProps<'div'>) => {
|
(props: React.ComponentProps<'div'>) => {
|
||||||
@@ -64,35 +69,52 @@ export default function Playground({code, height, autoFocus}: Props) {
|
|||||||
if (isLoaded && autoFocus) {
|
if (isLoaded && autoFocus) {
|
||||||
const codeElem = playgroundRef?.current?.querySelector('.prism-code');
|
const codeElem = playgroundRef?.current?.querySelector('.prism-code');
|
||||||
const sel = window.getSelection();
|
const sel = window.getSelection();
|
||||||
if (codeElem != null && sel != null) {
|
if (codeElem?.clientHeight && sel != null) {
|
||||||
sel.selectAllChildren(codeElem);
|
sel.selectAllChildren(codeElem);
|
||||||
sel.collapseToStart();
|
sel.collapseToStart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isLoaded, autoFocus]);
|
}, [isLoaded, autoFocus]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
// The toolbar is positioned relative to the outside of the scrolling
|
||||||
|
// container so it stays in the same place when scrolling, but this means
|
||||||
|
// it isn't automatically adjusted for scrollbar width
|
||||||
|
if (editorScrollRef.current) {
|
||||||
|
setScrollbarWidth(
|
||||||
|
editorScrollRef.current.offsetWidth -
|
||||||
|
editorScrollRef.current.clientWidth,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const heightStyle = height
|
const heightStyle = height
|
||||||
? ({'--yg-playground-height': height} as React.CSSProperties)
|
? ({'--yg-playground-height': height} as React.CSSProperties)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const resolvedCode = code ?? defaultCode;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LiveProvider code={resolvedCode} theme={prismTheme} scope={{Layout, Node}}>
|
<LiveProvider code={liveCode} theme={prismTheme} scope={{Layout, Node}}>
|
||||||
<div className={styles.wrapper} ref={playgroundRef} style={heightStyle}>
|
<div className={styles.wrapper} ref={playgroundRef} style={heightStyle}>
|
||||||
<div className={clsx(styles.playgroundRow, 'container')}>
|
<div className={clsx(styles.playgroundRow, 'container')}>
|
||||||
<div className={clsx(styles.editorColumn)}>
|
<div className={clsx(styles.editorColumn, 'playground-editor')}>
|
||||||
<EditorToolbar
|
<div className={styles.editorScroll} ref={editorScrollRef}>
|
||||||
getCode={useCallback(
|
<EditorToolbar
|
||||||
() =>
|
className={styles.editorToolbar}
|
||||||
nullthrows(
|
style={{paddingRight: scrollbarWidth + 'px'}}
|
||||||
playgroundRef.current?.querySelector('.prism-code')
|
getCode={useCallback(
|
||||||
?.textContent,
|
() =>
|
||||||
),
|
nullthrows(
|
||||||
[],
|
playgroundRef.current?.querySelector('.prism-code')
|
||||||
)}
|
?.textContent,
|
||||||
/>
|
),
|
||||||
<LiveEditor className={clsx(styles.playgroundEditor)} />
|
[],
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<LiveEditor
|
||||||
|
className={clsx(styles.playgroundEditor)}
|
||||||
|
onChange={setLiveCode}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={clsx(styles.previewColumn)}>
|
<div className={clsx(styles.previewColumn)}>
|
||||||
<LivePreview
|
<LivePreview
|
||||||
|
@@ -22,11 +22,11 @@
|
|||||||
@media (max-width: 996px) {
|
@media (max-width: 996px) {
|
||||||
.heroLogo {
|
.heroLogo {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playgroundSection :global(.prism-code) {
|
.playgroundSection :global(.playground-editor) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg {
|
.bg {
|
||||||
|
Reference in New Issue
Block a user