Skip to content
95 changes: 61 additions & 34 deletions compiler/apps/playground/components/AccordionWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@
*/

import {Resizable} from 're-resizable';
import React, {useCallback} from 'react';
import React, {
useCallback,
useId,
unstable_ViewTransition as ViewTransition,
unstable_addTransitionType as addTransitionType,
startTransition,
} from 'react';
import {EXPAND_ACCORDION_TRANSITION} from '../lib/transitionTypes';

type TabsRecord = Map<string, React.ReactNode>;

Expand Down Expand Up @@ -50,49 +57,69 @@ function AccordionWindowItem({
setTabsOpen: (newTab: Set<string>) => void;
hasChanged: boolean;
}): React.ReactElement {
const id = useId();
const isShow = tabsOpen.has(name);

const toggleTabs = useCallback(() => {
const nextState = new Set(tabsOpen);
if (nextState.has(name)) {
nextState.delete(name);
} else {
nextState.add(name);
}
setTabsOpen(nextState);
}, [tabsOpen, name, setTabsOpen]);
const transitionName = `accordion-window-item-${id}`;

const toggleTabs = () => {
startTransition(() => {
addTransitionType(EXPAND_ACCORDION_TRANSITION);
const nextState = new Set(tabsOpen);
if (nextState.has(name)) {
nextState.delete(name);
} else {
nextState.add(name);
}
setTabsOpen(nextState);
});
};

// Replace spaces with non-breaking spaces
const displayName = name.replace(/ /g, '\u00A0');

return (
<div key={name} className="flex flex-row">
{isShow ? (
<Resizable className="border-r" minWidth={550} enable={{right: true}}>
<h2
title="Minimize tab"
aria-label="Minimize tab"
onClick={toggleTabs}
className={`p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 ${
hasChanged ? 'font-bold' : 'font-light'
} text-secondary hover:text-link`}>
- {displayName}
</h2>
{tabs.get(name) ?? <div>No output for {name}</div>}
</Resizable>
<ViewTransition
name={transitionName}
update={{
[EXPAND_ACCORDION_TRANSITION]: 'expand-accordion',
default: 'none',
}}>
<Resizable className="border-r" minWidth={550} enable={{right: true}}>
<h2
title="Minimize tab"
aria-label="Minimize tab"
onClick={toggleTabs}
className={`p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 ${
hasChanged ? 'font-bold' : 'font-light'
} text-secondary hover:text-link`}>
- {displayName}
</h2>
{tabs.get(name) ?? <div>No output for {name}</div>}
</Resizable>
</ViewTransition>
) : (
<div className="relative items-center h-full px-1 py-6 align-middle border-r border-grey-200">
<button
title={`Expand compiler tab: ${name}`}
aria-label={`Expand compiler tab: ${name}`}
style={{transform: 'rotate(90deg) translate(-50%)'}}
onClick={toggleTabs}
className={`flex-grow-0 w-5 transition-colors duration-150 ease-in ${
hasChanged ? 'font-bold' : 'font-light'
} text-secondary hover:text-link`}>
{displayName}
</button>
</div>
<ViewTransition
name={transitionName}
update={{
[EXPAND_ACCORDION_TRANSITION]: 'expand-accordion',
default: 'none',
}}>
<div className="relative items-center h-full px-1 py-6 align-middle border-r border-grey-200">
<button
title={`Expand compiler tab: ${name}`}
aria-label={`Expand compiler tab: ${name}`}
style={{transform: 'rotate(90deg) translate(-50%)'}}
onClick={toggleTabs}
className={`flex-grow-0 w-5 transition-colors duration-150 ease-in ${
hasChanged ? 'font-bold' : 'font-light'
} text-secondary hover:text-link`}>
{displayName}
</button>
</div>
</ViewTransition>
)}
</div>
);
Expand Down
7 changes: 6 additions & 1 deletion compiler/apps/playground/components/Editor/Output.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ import AccordionWindow from '../AccordionWindow';
import TabbedWindow from '../TabbedWindow';
import {monacoOptions} from './monacoOptions';
import {BabelFileResult} from '@babel/core';
import {CONFIG_PANEL_TRANSITION} from '../../lib/transitionTypes';
import {
CONFIG_PANEL_TRANSITION,
TOGGLE_INTERNALS_TRANSITION,
} from '../../lib/transitionTypes';
import {LRUCache} from 'lru-cache';

const MemoizedOutput = memo(Output);
Expand Down Expand Up @@ -291,6 +294,7 @@ function OutputContent({store, compilerOutput}: Props): JSX.Element {
<ViewTransition
update={{
[CONFIG_PANEL_TRANSITION]: 'container',
[TOGGLE_INTERNALS_TRANSITION]: '',
default: 'none',
}}>
<TabbedWindow
Expand All @@ -306,6 +310,7 @@ function OutputContent({store, compilerOutput}: Props): JSX.Element {
<ViewTransition
update={{
[CONFIG_PANEL_TRANSITION]: 'accordion-container',
[TOGGLE_INTERNALS_TRANSITION]: '',
default: 'none',
}}>
<AccordionWindow
Expand Down
14 changes: 12 additions & 2 deletions compiler/apps/playground/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ import {CheckIcon} from '@heroicons/react/solid';
import clsx from 'clsx';
import Link from 'next/link';
import {useSnackbar} from 'notistack';
import {useState} from 'react';
import {
useState,
startTransition,
unstable_addTransitionType as addTransitionType,
} from 'react';
import {defaultStore} from '../lib/defaultStore';
import {IconGitHub} from './Icons/IconGitHub';
import Logo from './Logo';
import {useStore, useStoreDispatch} from './StoreContext';
import {TOGGLE_INTERNALS_TRANSITION} from '../lib/transitionTypes';

export default function Header(): JSX.Element {
const [showCheck, setShowCheck] = useState(false);
Expand Down Expand Up @@ -62,7 +67,12 @@ export default function Header(): JSX.Element {
<input
type="checkbox"
checked={store.showInternals}
onChange={() => dispatchStore({type: 'toggleInternals'})}
onChange={() =>
startTransition(() => {
addTransitionType(TOGGLE_INTERNALS_TRANSITION);
dispatchStore({type: 'toggleInternals'});
})
}
className="absolute opacity-0 cursor-pointer h-full w-full m-0"
/>
<span
Expand Down
2 changes: 2 additions & 0 deletions compiler/apps/playground/lib/transitionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@

export const CONFIG_PANEL_TRANSITION = 'config-panel';
export const TOGGLE_TAB_TRANSITION = 'toggle-tab';
export const TOGGLE_INTERNALS_TRANSITION = 'toggle-internals';
export const EXPAND_ACCORDION_TRANSITION = 'open-accordion';
8 changes: 8 additions & 0 deletions compiler/apps/playground/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,11 @@
::view-transition-group(.tab-text) {
z-index: 1;
}

::view-transition-old(.expand-accordion),
::view-transition-new(.expand-accordion) {
width: auto;
}
::view-transition-group(.expand-accordion) {
overflow: clip;
}
Original file line number Diff line number Diff line change
Expand Up @@ -1485,6 +1485,85 @@ const tests = {
}
`,
},
{
// Test settings-based additionalHooks - should work with settings
code: normalizeIndent`
function MyComponent(props) {
useCustomEffect(() => {
console.log(props.foo);
});
}
`,
settings: {
'react-hooks': {
additionalEffectHooks: 'useCustomEffect',
},
},
},
{
// Test settings-based additionalHooks - should work with dependencies
code: normalizeIndent`
function MyComponent(props) {
useCustomEffect(() => {
console.log(props.foo);
}, [props.foo]);
}
`,
settings: {
'react-hooks': {
additionalEffectHooks: 'useCustomEffect',
},
},
},
{
// Test that rule-level additionalHooks takes precedence over settings
code: normalizeIndent`
function MyComponent(props) {
useCustomEffect(() => {
console.log(props.foo);
}, []);
}
`,
options: [{additionalHooks: 'useAnotherEffect'}],
settings: {
'react-hooks': {
additionalEffectHooks: 'useCustomEffect',
},
},
},
{
// Test settings with multiple hooks pattern
code: normalizeIndent`
function MyComponent(props) {
useCustomEffect(() => {
console.log(props.foo);
}, [props.foo]);
useAnotherEffect(() => {
console.log(props.bar);
}, [props.bar]);
}
`,
settings: {
'react-hooks': {
additionalEffectHooks: '(useCustomEffect|useAnotherEffect)',
},
},
},
{
code: normalizeIndent`
function MyComponent({ theme }) {
const onStuff = useEffectEvent(() => {
showNotification(theme);
});
useEffect(() => {
onStuff();
}, []);
React.useEffect(() => {
onStuff();
}, []);
}
`,
},
],
invalid: [
{
Expand Down Expand Up @@ -3714,6 +3793,40 @@ const tests = {
},
],
},
{
// Test settings-based additionalHooks - should detect missing dependency
code: normalizeIndent`
function MyComponent(props) {
useCustomEffect(() => {
console.log(props.foo);
}, []);
}
`,
settings: {
'react-hooks': {
additionalEffectHooks: 'useCustomEffect',
},
},
errors: [
{
message:
"React Hook useCustomEffect has a missing dependency: 'props.foo'. " +
'Either include it or remove the dependency array.',
suggestions: [
{
desc: 'Update the dependencies array to be: [props.foo]',
output: normalizeIndent`
function MyComponent(props) {
useCustomEffect(() => {
console.log(props.foo);
}, [props.foo]);
}
`,
},
],
},
],
},
{
code: normalizeIndent`
function MyComponent() {
Expand Down Expand Up @@ -7721,31 +7834,6 @@ const tests = {
},
],
},
],
};

if (__EXPERIMENTAL__) {
tests.valid = [
...tests.valid,
{
code: normalizeIndent`
function MyComponent({ theme }) {
const onStuff = useEffectEvent(() => {
showNotification(theme);
});
useEffect(() => {
onStuff();
}, []);
React.useEffect(() => {
onStuff();
}, []);
}
`,
},
];

tests.invalid = [
...tests.invalid,
{
code: normalizeIndent`
function MyComponent({ theme }) {
Expand Down Expand Up @@ -7809,8 +7897,8 @@ if (__EXPERIMENTAL__) {
},
],
},
];
}
],
};

// Tests that are only valid/invalid across parsers supporting Flow
const testsFlow = {
Expand Down
Loading
Loading