diff --git a/packages/website/package.json b/packages/website/package.json index 5004c37fcf4e..67c1436025cb 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -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", diff --git a/packages/website/src/components/Playground.module.css b/packages/website/src/components/Playground.module.css index 982899552094..d6189b238f55 100644 --- a/packages/website/src/components/Playground.module.css +++ b/packages/website/src/components/Playground.module.css @@ -4,19 +4,21 @@ } .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); @@ -24,7 +26,7 @@ .astViewer { height: 100%; - width: 50%; + width: 100%; border: 1px solid var(--playground-secondary-color); padding: 0; overflow: auto; @@ -98,7 +100,6 @@ .astViewer, .sourceCode { height: 30rem; - width: 100%; } .astViewer { diff --git a/packages/website/src/components/Playground.tsx b/packages/website/src/components/Playground.tsx index 4626a7504664..3330e4998617 100644 --- a/packages/website/src/components/Playground.tsx +++ b/packages/website/src/components/Playground.tsx @@ -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'; @@ -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, @@ -70,6 +72,7 @@ function Playground(): JSX.Element { const [position, setPosition] = useState(null); const [activeTab, setTab] = useState('code'); const [showModal, setShowModal] = useState(false); + const enableSplitPanes = useMediaQuery('(min-width: 996px)'); const updateModal = useCallback( (config?: Partial) => { @@ -96,73 +99,90 @@ function Playground(): JSX.Element { config={state.tsconfig} onClose={updateModal} /> -
- -
-
- {isLoading && } - setShowModal(activeTab)} - /> -
- -
- { - setRuleNames(ruleNames); - setTSVersion(tsVersions); - setIsLoading(false); - }} - onSelect={setPosition} - /> -
-
- {(state.showAST === 'ts' && tsAst && ( - +
+ - )) || - (state.showAST === 'scope' && scope && ( - + +
+ {isLoading && } + setShowModal(activeTab)} /> - )) || - (state.showAST === 'es' && esAst && ( - + +
+ { + setRuleNames(ruleNames); + setTSVersion(tsVersions); + setIsLoading(false); + }} + onSelect={setPosition} /> - )) || } -
+
+
+ {(state.showAST === 'ts' && tsAst && ( + + )) || + (state.showAST === 'scope' && scope && ( + + )) || + (state.showAST === 'es' && esAst && ( + + )) || } +
+ +
); diff --git a/packages/website/src/components/SplitPane/ConditionalSplitPane.tsx b/packages/website/src/components/SplitPane/ConditionalSplitPane.tsx new file mode 100644 index 000000000000..22c9aa14b5d6 --- /dev/null +++ b/packages/website/src/components/SplitPane/ConditionalSplitPane.tsx @@ -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 ? ( + + {children} + + ) : ( + <>{children} + ); +} + +export default ConditionalSplitPane; diff --git a/packages/website/src/components/SplitPane/SplitPane.module.css b/packages/website/src/components/SplitPane/SplitPane.module.css new file mode 100644 index 000000000000..09537f1b1b5b --- /dev/null +++ b/packages/website/src/components/SplitPane/SplitPane.module.css @@ -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); +} diff --git a/packages/website/src/components/editor/LoadedEditor.tsx b/packages/website/src/components/editor/LoadedEditor.tsx index c65e6343cb21..3b959580193d 100644 --- a/packages/website/src/components/editor/LoadedEditor.tsx +++ b/packages/website/src/components/editor/LoadedEditor.tsx @@ -232,6 +232,22 @@ export const LoadedEditor: React.FC = ({ 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 => { diff --git a/packages/website/src/hooks/useMediaQuery.ts b/packages/website/src/hooks/useMediaQuery.ts new file mode 100644 index 000000000000..1bd928c4a4b2 --- /dev/null +++ b/packages/website/src/hooks/useMediaQuery.ts @@ -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.
+ * It also monitor the document changes to detect when it matches or stops matching the media query.
+ * Returns the validity state of the given media query. + * + */ +const useMediaQuery = (mediaQuery: string): boolean => { + const [isVerified, setIsVerified] = useState( + !!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 }; diff --git a/patches/react-split-pane+0.1.92.patch b/patches/react-split-pane+0.1.92.patch new file mode 100644 index 000000000000..b00ff2919e7c --- /dev/null +++ b/patches/react-split-pane+0.1.92.patch @@ -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 = { diff --git a/yarn.lock b/yarn.lock index 2b0e60253b00..97da872b7880 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11907,7 +11907,7 @@ promzard@^0.3.0: dependencies: read "1" -prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.5.4, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -12199,6 +12199,22 @@ react-router@5.3.3, react-router@^5.3.3: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" +react-split-pane@^0.1.92: + version "0.1.92" + resolved "https://registry.yarnpkg.com/react-split-pane/-/react-split-pane-0.1.92.tgz#68242f72138aed95dd5910eeb9d99822c4fc3a41" + integrity sha512-GfXP1xSzLMcLJI5BM36Vh7GgZBpy+U/X0no+VM3fxayv+p1Jly5HpMofZJraeaMl73b3hvlr+N9zJKvLB/uz9w== + dependencies: + prop-types "^15.7.2" + react-lifecycles-compat "^3.0.4" + react-style-proptype "^3.2.2" + +react-style-proptype@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/react-style-proptype/-/react-style-proptype-3.2.2.tgz#d8e998e62ce79ec35b087252b90f19f1c33968a0" + integrity sha512-ywYLSjNkxKHiZOqNlso9PZByNEY+FTyh3C+7uuziK0xFXu9xzdyfHwg4S9iyiRRoPCR4k2LqaBBsWVmSBwCWYQ== + dependencies: + prop-types "^15.5.4" + react-textarea-autosize@^8.3.2: version "8.3.3" resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz#f70913945369da453fd554c168f6baacd1fa04d8"