Skip to content

chore(website): make playground code editor horizontally resizable #5667

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@
"prism-react-renderer": "^1.3.3",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-split-pane": "^0.1.92",
"remark-docusaurus-tabs": "^0.2.0",
"typescript": "*"
},
"resolutions": {
"react": "^18.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "~2.0.1",
"@types/react": "^18.0.9",
Expand Down
9 changes: 5 additions & 4 deletions packages/website/src/components/Playground.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,29 @@
}

.options {
width: 20rem;
background: var(--playground-main-color);
overflow: auto;
z-index: 1;
}

.sourceCode {
height: 100%;
width: 50%;
border: 1px solid var(--playground-secondary-color);
}

.codeBlocks {
display: flex;
flex-grow: 1;
flex-shrink: 1;
flex-basis: 0;
flex-direction: row;
height: 100%;
width: calc(100vw - 20rem);
}

.astViewer {
height: 100%;
width: 50%;
width: 100%;
border: 1px solid var(--playground-secondary-color);
padding: 0;
overflow: auto;
Expand Down Expand Up @@ -98,7 +100,6 @@
.astViewer,
.sourceCode {
height: 30rem;
width: 100%;
}

.astViewer {
Expand Down
146 changes: 83 additions & 63 deletions packages/website/src/components/Playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type Monaco from 'monaco-editor';
import React, { useCallback, useReducer, useState } from 'react';
import type { SourceFile } from 'typescript';

import { useMediaQuery } from '../hooks/useMediaQuery';
import ASTViewerESTree from './ASTViewerESTree';
import ASTViewerTS from './ASTViewerTS';
import { EditorEmbed } from './editor/EditorEmbed';
Expand All @@ -23,6 +24,7 @@ import Loader from './layout/Loader';
import { shallowEqual } from './lib/shallowEqual';
import OptionsSelector from './OptionsSelector';
import styles from './Playground.module.css';
import ConditionalSplitPane from './SplitPane/ConditionalSplitPane';
import type {
ConfigModel,
ErrorGroup,
Expand Down Expand Up @@ -70,6 +72,7 @@ function Playground(): JSX.Element {
const [position, setPosition] = useState<Monaco.Position | null>(null);
const [activeTab, setTab] = useState<TabType>('code');
const [showModal, setShowModal] = useState<TabType | false>(false);
const enableSplitPanes = useMediaQuery('(min-width: 996px)');

const updateModal = useCallback(
(config?: Partial<ConfigModel>) => {
Expand All @@ -96,73 +99,90 @@ function Playground(): JSX.Element {
config={state.tsconfig}
onClose={updateModal}
/>
<div className={clsx(styles.options, 'thin-scrollbar')}>
<OptionsSelector
isLoading={isLoading}
state={state}
tsVersions={tsVersions}
setState={setState}
/>
</div>
<div className={styles.codeBlocks}>
<div className={clsx(styles.sourceCode)}>
{isLoading && <Loader />}
<EditorTabs
tabs={['code', 'tsconfig', 'eslintrc']}
activeTab={activeTab}
change={setTab}
showModal={(): void => setShowModal(activeTab)}
/>
<div className={styles.tabCode}>
<EditorEmbed />
</div>
<LoadingEditor
ts={state.ts}
jsx={state.jsx}
activeTab={activeTab}
code={state.code}
tsconfig={state.tsconfig}
eslintrc={state.eslintrc}
darkTheme={colorMode === 'dark'}
sourceType={state.sourceType}
showAST={state.showAST}
onEsASTChange={setEsAst}
onTsASTChange={setTsAST}
onScopeChange={setScope}
onMarkersChange={setMarkers}
decoration={selectedRange}
onChange={setState}
onLoaded={(ruleNames, tsVersions): void => {
setRuleNames(ruleNames);
setTSVersion(tsVersions);
setIsLoading(false);
}}
onSelect={setPosition}
/>
</div>
<div className={styles.astViewer}>
{(state.showAST === 'ts' && tsAst && (
<ASTViewerTS
value={tsAst}
position={position}
onSelectNode={setSelectedRange}
<ConditionalSplitPane
render={enableSplitPanes}
split="vertical"
minSize="10%"
defaultSize="20rem"
maxSize={
20 * parseFloat(getComputedStyle(document.documentElement).fontSize)
}
>
<div className={clsx(styles.options, 'thin-scrollbar')}>
<OptionsSelector
isLoading={isLoading}
state={state}
tsVersions={tsVersions}
setState={setState}
/>
)) ||
(state.showAST === 'scope' && scope && (
<ASTViewerScope
value={scope}
position={position}
onSelectNode={setSelectedRange}
</div>
<ConditionalSplitPane
render={enableSplitPanes}
split="vertical"
minSize="10%"
defaultSize="50%"
>
<div className={clsx(styles.sourceCode)}>
{isLoading && <Loader />}
<EditorTabs
tabs={['code', 'tsconfig', 'eslintrc']}
activeTab={activeTab}
change={setTab}
showModal={(): void => setShowModal(activeTab)}
/>
)) ||
(state.showAST === 'es' && esAst && (
<ASTViewerESTree
value={esAst}
position={position}
onSelectNode={setSelectedRange}
<div className={styles.tabCode}>
<EditorEmbed />
</div>
<LoadingEditor
ts={state.ts}
jsx={state.jsx}
activeTab={activeTab}
code={state.code}
tsconfig={state.tsconfig}
eslintrc={state.eslintrc}
darkTheme={colorMode === 'dark'}
sourceType={state.sourceType}
showAST={state.showAST}
onEsASTChange={setEsAst}
onTsASTChange={setTsAST}
onScopeChange={setScope}
onMarkersChange={setMarkers}
decoration={selectedRange}
onChange={setState}
onLoaded={(ruleNames, tsVersions): void => {
setRuleNames(ruleNames);
setTSVersion(tsVersions);
setIsLoading(false);
}}
onSelect={setPosition}
/>
)) || <ErrorsViewer value={markers} />}
</div>
</div>
<div className={styles.astViewer}>
{(state.showAST === 'ts' && tsAst && (
<ASTViewerTS
value={tsAst}
position={position}
onSelectNode={setSelectedRange}
/>
)) ||
(state.showAST === 'scope' && scope && (
<ASTViewerScope
value={scope}
position={position}
onSelectNode={setSelectedRange}
/>
)) ||
(state.showAST === 'es' && esAst && (
<ASTViewerESTree
value={esAst}
position={position}
onSelectNode={setSelectedRange}
/>
)) || <ErrorsViewer value={markers} />}
</div>
</ConditionalSplitPane>
</ConditionalSplitPane>
</div>
</div>
);
Expand Down
28 changes: 28 additions & 0 deletions packages/website/src/components/SplitPane/ConditionalSplitPane.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import clsx from 'clsx';
import React from 'react';
import SplitPane, { type SplitPaneProps } from 'react-split-pane';

import splitPaneStyles from './SplitPane.module.css';

export interface ConditionalSplitPaneProps {
render: boolean;
}

function ConditionalSplitPane({
render,
children,
...props
}: ConditionalSplitPaneProps & SplitPaneProps): JSX.Element {
return render ? (
<SplitPane
resizerClassName={clsx(splitPaneStyles.resizer, splitPaneStyles.vertical)}
{...props}
>
{children}
</SplitPane>
) : (
<>{children}</>
);
}

export default ConditionalSplitPane;
24 changes: 24 additions & 0 deletions packages/website/src/components/SplitPane/SplitPane.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.resizer {
background: var(--ifm-color-emphasis-700);
opacity: 0.2;
z-index: 1;
box-sizing: border-box;
background-clip: padding-box;
}

.resizer:hover {
transition: all 2s ease;
}

.resizer.vertical {
width: 11px;
margin: 0 -5px;
border-left: 5px solid rgba(255, 255, 255, 0);
border-right: 5px solid rgba(255, 255, 255, 0);
cursor: col-resize;
}

.resizer.vertical:hover {
border-left: 5px solid var(--ifm-color-emphasis-700);
border-right: 5px solid var(--ifm-color-emphasis-700);
}
16 changes: 16 additions & 0 deletions packages/website/src/components/editor/LoadedEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,22 @@ export const LoadedEditor: React.FC<LoadedEditorProps> = ({
resize();
}, [resize, showAST]);

const domNode = sandboxInstance.editor.getContainerDomNode();
const resizeObserver = useMemo(() => {
return new ResizeObserver(() => {
resize();
});
}, []);

useEffect(() => {
if (domNode) {
resizeObserver.observe(domNode);

return (): void => resizeObserver.unobserve(domNode);
}
return (): void => {};
}, [domNode]);

useEffect(() => {
window.addEventListener('resize', resize);
return (): void => {
Expand Down
46 changes: 46 additions & 0 deletions packages/website/src/hooks/useMediaQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Modified from https://github.com/antonioru/beautiful-react-hooks/blob/master/src/useMediaQuery.ts

import { useEffect, useState } from 'react';

/**
* Accepts a media query string then uses the
* [window.matchMedia](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) API to determine if it
* matches with the current document.<br />
* It also monitor the document changes to detect when it matches or stops matching the media query.<br />
* Returns the validity state of the given media query.
*
*/
const useMediaQuery = (mediaQuery: string): boolean => {
const [isVerified, setIsVerified] = useState<boolean>(
!!window.matchMedia(mediaQuery).matches,
);

useEffect(() => {
const mediaQueryList = window.matchMedia(mediaQuery);
const documentChangeHandler = (): void =>
setIsVerified(!!mediaQueryList.matches);

try {
mediaQueryList.addEventListener('change', documentChangeHandler);
} catch (e) {
// Safari isn't supporting mediaQueryList.addEventListener
// eslint-disable-next-line deprecation/deprecation
mediaQueryList.addListener(documentChangeHandler);
}

documentChangeHandler();
return () => {
try {
mediaQueryList.removeEventListener('change', documentChangeHandler);
} catch (e) {
// Safari isn't supporting mediaQueryList.removeEventListener
// eslint-disable-next-line deprecation/deprecation
mediaQueryList.removeListener(documentChangeHandler);
}
};
}, [mediaQuery]);

return isVerified;
};

export { useMediaQuery };
12 changes: 12 additions & 0 deletions patches/react-split-pane+0.1.92.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
diff --git a/node_modules/react-split-pane/index.d.ts b/node_modules/react-split-pane/index.d.ts
index d116f54..9329094 100644
--- a/node_modules/react-split-pane/index.d.ts
+++ b/node_modules/react-split-pane/index.d.ts
@@ -25,6 +25,7 @@ export type SplitPaneProps = {
pane2Style?: React.CSSProperties;
resizerClassName?: string;
step?: number;
+ children?: React.ReactNode;
};

export type SplitPaneState = {
Loading