Skip to content

Commit 31fd291

Browse files
authored
internal: Clean up Playground component interfaces and data flow (#3781)
* internal: Clean up Playground component types and logic Fix parseCodeBlockPath accessing wrong regex named group (groups.title instead of groups.path). Add missing TypeScript prop types to Boundary, transformCode, PlaygroundLiveEditor, and PlaygroundTextEdit sub-components. Simplify collapsed/column metastring regexes. Fix MonacoPreloads guard flag placement. Made-with: Cursor * bugbot * internal: Simplify Playground interfaces and data flow - Extract shared PreviewProps<T> and FixtureOrInterceptor<T> types - Inline PreviewWithHeader into PreviewWithScope (trivial passthrough) - Consolidate bot/mobile detection into single module - Type PlaygroundProps properly (remove any) - Derive selectedValue from storage instead of render-time setState - Simplify Reversible component logic Made-with: Cursor
1 parent a796642 commit 31fd291

File tree

12 files changed

+83
-116
lines changed

12 files changed

+83
-116
lines changed

website/src/components/Playground/FixturePreview.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import type { Fixture, Interceptor } from '@data-client/test';
21
import BrowserOnly from '@docusaurus/BrowserOnly';
32
import CodeBlock from '@theme/CodeBlock';
43
import { memo, type ReactElement } from 'react';
54

65
import styles from './styles.module.css';
6+
import type { FixtureOrInterceptor } from './types';
77

8-
function FixturePreview({ fixtures }: { fixtures: (Fixture | Interceptor)[] }) {
8+
function FixturePreview({ fixtures }: { fixtures: FixtureOrInterceptor[] }) {
99
return (
1010
<div className={styles.fixtureBlock}>
1111
{fixtures.map((fixture, i) => (
@@ -19,7 +19,7 @@ export default memo(FixturePreview);
1919
function FixtureResponse({
2020
fixture,
2121
}: {
22-
fixture: Fixture | Interceptor;
22+
fixture: FixtureOrInterceptor;
2323
}): ReactElement {
2424
return (
2525
'fetchResponse' in fixture ?
@@ -47,7 +47,7 @@ function FixtureResponse({
4747
function FixtureOrInterceptor({
4848
fixture,
4949
}: {
50-
fixture: Fixture | Interceptor;
50+
fixture: FixtureOrInterceptor;
5151
}): JSX.Element {
5252
if ('args' in fixture) {
5353
return (

website/src/components/Playground/MonacoPreloads.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const workerPreloads = [
3131
`https://cdn.jsdelivr.net/npm/monaco-editor@${MONACO_VERSION}/min/vs/assets/editor.worker-Be8ye1pW.js`,
3232
];
3333

34+
// Regex must match isMobileOrBot in isMobileOrBot.ts (cannot import into script string)
3435
const preloadScript = `
3536
if (!/bot|googlebot|crawler|spider|robot|crawling|Mobile|Android|BlackBerry/i.test(
3637
navigator.userAgent,

website/src/components/Playground/PlaygroundTextEdit.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { Fixture, Interceptor } from '@data-client/test';
21
import BrowserOnly from '@docusaurus/BrowserOnly';
32
import Translate from '@docusaurus/Translate';
43
import clsx from 'clsx';
@@ -14,9 +13,11 @@ import { LiveEditor } from 'react-live';
1413

1514
import FixturePreview from './FixturePreview';
1615
import Header from './Header';
17-
import { isGoogleBot } from './isGoogleBot';
16+
import { isGoogleBot } from './isMobileOrBot';
1817
import PlaygroundEditor from './PlaygroundEditor';
1918
import styles from './styles.module.css';
19+
import type { FixtureOrInterceptor } from './types';
20+
import type { CodeTab, UseCodeReturn } from './useCode';
2021
import CodeTabContext from '../Demo/CodeTabContext';
2122

2223
export function PlaygroundTextEdit({
@@ -108,11 +109,11 @@ export function PlaygroundTextEdit({
108109
);
109110
}
110111
interface PlaygroundProps {
111-
fixtures: (Fixture | Interceptor)[];
112+
fixtures: FixtureOrInterceptor[];
112113
row: boolean;
113-
codeTabs: any;
114-
handleCodeChange: any;
115-
codes: any;
114+
codeTabs: CodeTab[];
115+
handleCodeChange: UseCodeReturn['handleCodeChange'];
116+
codes: UseCodeReturn['codes'];
116117
large?: boolean;
117118
isPlayground?: boolean;
118119
}
@@ -281,7 +282,7 @@ function EditorHeader({
281282
fixtures = [],
282283
}: {
283284
title?: React.ReactNode;
284-
fixtures?: (Fixture | Interceptor)[];
285+
fixtures?: FixtureOrInterceptor[];
285286
}) {
286287
const { values } = useContext(CodeTabContext);
287288
const hasTabs = values.length > 0;

website/src/components/Playground/Preview.tsx

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,15 @@ import {
44
SubscriptionManager,
55
NetworkManager,
66
} from '@data-client/react';
7-
import {
8-
type Fixture,
9-
type Interceptor,
10-
MockResolver,
11-
} from '@data-client/test/browser';
7+
import { MockResolver } from '@data-client/test/browser';
128
import { useScrollPositionBlocker } from '@docusaurus/theme-common/internal';
139
import clsx from 'clsx';
14-
import React, { memo, useCallback, useState, useMemo, lazy } from 'react';
10+
import React, { memo, useCallback, useMemo, lazy } from 'react';
1511

1612
import Boundary from './Boundary';
1713
import StoreInspector from './StoreInspector';
1814
import styles from './styles.module.css';
15+
import type { PreviewProps } from './types';
1916
import { useTabStorage } from '../../utils/tabStorage';
2017

2118
function Preview<T>({
@@ -24,31 +21,21 @@ function Preview<T>({
2421
row,
2522
fixtures,
2623
getInitialInterceptorData,
27-
}: {
28-
groupId: string;
29-
row: boolean;
30-
defaultOpen: 'y' | 'n';
31-
fixtures: (Fixture | Interceptor<T>)[];
32-
getInitialInterceptorData?: () => T;
33-
}) {
24+
}: PreviewProps<T>) {
3425
const [choice, setTabGroupChoice] = useTabStorage(
3526
`docusaurus.tab.${groupId}`,
3627
);
37-
const [selectedValue, setSelectedValue] = useState(defaultOpen);
28+
const selectedValue = (choice ?? defaultOpen) as 'y' | 'n';
3829
const { blockElementScrollPositionUntilNextRender } =
3930
useScrollPositionBlocker();
4031

41-
if (choice != null && choice !== selectedValue) {
42-
setSelectedValue(choice as any);
43-
}
44-
4532
const toggle = useCallback(
4633
(
4734
event: React.FocusEvent<HTMLLIElement> | React.MouseEvent<HTMLLIElement>,
4835
) => {
4936
blockElementScrollPositionUntilNextRender(event.currentTarget);
50-
setSelectedValue(open => (open === 'y' ? 'n' : 'y'));
51-
setTabGroupChoice(selectedValue === 'y' ? 'n' : 'y');
37+
const next = selectedValue === 'y' ? 'n' : 'y';
38+
setTabGroupChoice(next);
5239
},
5340
[
5441
blockElementScrollPositionUntilNextRender,

website/src/components/Playground/PreviewWithHeader.tsx

Lines changed: 0 additions & 34 deletions
This file was deleted.

website/src/components/Playground/PreviewWithScope.tsx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ import * as graphql from '@data-client/graphql';
22
import * as rhReact from '@data-client/react';
33
import * as rhReactNext from '@data-client/react/next';
44
import * as rest from '@data-client/rest';
5-
import type { Fixture, Interceptor } from '@data-client/test';
65
import { Temporal, Intl as PolyIntl } from '@js-temporal/polyfill';
76
import BigNumber from 'bignumber.js';
87
import { use } from 'react';
98
import { LiveProvider } from 'react-live';
109
import { v4 as uuid } from 'uuid';
1110

1211
import * as designSystem from './DesignSystem';
13-
import PreviewWithHeader from './PreviewWithHeader';
12+
import Preview from './Preview';
13+
import PreviewWrapper from './PreviewWrapper';
1414
import transformCode from './transformCode';
15+
import type { PreviewProps } from './types';
1516
import ResetableErrorBoundary from '../ResettableErrorBoundary';
1617

1718
function randomFloatInRange(min, max, decimals) {
@@ -55,14 +56,7 @@ const scope = {
5556
export default function PreviewWithScope<T>({
5657
code,
5758
...props
58-
}: {
59-
code: string;
60-
groupId: string;
61-
defaultOpen: 'y' | 'n';
62-
row: boolean;
63-
fixtures: (Fixture | Interceptor<T>)[];
64-
getInitialInterceptorData?: () => T;
65-
}) {
59+
}: PreviewProps<T> & { code: string }) {
6660
return (
6761
<LiveProvider
6862
key="preview"
@@ -72,7 +66,9 @@ export default function PreviewWithScope<T>({
7266
noInline
7367
scope={scope}
7468
>
75-
<PreviewWithHeader key="preview" {...props} />
69+
<PreviewWrapper>
70+
<Preview key="preview" {...props} />
71+
</PreviewWrapper>
7672
</LiveProvider>
7773
);
7874
}

website/src/components/Playground/index.tsx

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import type { Fixture, Interceptor } from '@data-client/test';
21
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
32
import clsx from 'clsx';
43
import type { Language, PrismTheme } from 'prism-react-renderer';
54
import React, { lazy } from 'react';
65
import { LiveProvider } from 'react-live';
76

87
import Boundary from './Boundary';
9-
import { isGoogleBot } from './isGoogleBot';
8+
import { isGoogleBot } from './isMobileOrBot';
109
import MonacoPreloads from './MonacoPreloads';
1110
import { PlaygroundTextEdit } from './PlaygroundTextEdit';
1211
import PreviewWrapper from './PreviewWrapper';
1312
import styles from './styles.module.css';
13+
import type { PreviewProps } from './types';
1414
import { useCode } from './useCode';
1515
import { useReactLiveTheme } from './useReactLiveTheme';
1616

@@ -38,15 +38,12 @@ export default function Playground<T>({
3838
getInitialInterceptorData,
3939
defaultTab,
4040
...props
41-
}: Omit<LiveProviderProps, 'ref'> & {
42-
groupId: string;
43-
defaultOpen: 'y' | 'n';
44-
row?: boolean;
45-
children: string | any[];
46-
fixtures: (Fixture | Interceptor<T>)[];
47-
getInitialInterceptorData?: () => T;
48-
defaultTab?: string;
49-
}) {
41+
}: Omit<LiveProviderProps, 'ref'> &
42+
Omit<PreviewProps<T>, 'row'> & {
43+
row?: boolean;
44+
children: string | any[];
45+
defaultTab?: string;
46+
}) {
5047
const {
5148
liveCodeBlock: { playgroundPosition },
5249
} = useDocusaurusContext().siteConfig.themeConfig as any;
@@ -119,14 +116,9 @@ function PlaygroundContent<T>({
119116
</Reversible>
120117
);
121118
}
122-
interface ContentProps<T = any> {
123-
groupId: string;
124-
defaultOpen: 'y' | 'n';
125-
row: boolean;
126-
fixtures: (Fixture | Interceptor<T>)[];
119+
interface ContentProps<T = any> extends PreviewProps<T> {
127120
children: React.ReactNode;
128121
reverse?: boolean;
129-
getInitialInterceptorData?: () => T;
130122
defaultTab?: string;
131123
}
132124

@@ -157,10 +149,5 @@ function Reversible({
157149
children: React.ReactNode[];
158150
reverse?: boolean;
159151
}): React.ReactElement {
160-
const newchild = [...children];
161-
newchild.reverse();
162-
if (reverse) {
163-
return newchild as any;
164-
}
165-
return children as any;
152+
return (reverse ? [...children].reverse() : children) as React.ReactElement;
166153
}

website/src/components/Playground/isGoogleBot.tsx

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1+
/** Shared user-agent patterns for bot/mobile detection. Inline copy in MonacoPreloads.tsx script string. */
2+
const BOT_UA_REGEX = /bot|googlebot|crawler|spider|robot|crawling/i;
3+
const MOBILE_OR_BOT_UA_REGEX =
4+
/bot|googlebot|crawler|spider|robot|crawling|Mobile|Android|BlackBerry/i;
5+
6+
/** True if crawler/bot (SSR-safe). Used for skipping Monaco/Preview load. */
7+
export const isGoogleBot =
8+
typeof navigator === 'object' &&
9+
BOT_UA_REGEX.test(navigator?.userAgent ?? '');
10+
111
/** Check if current browser is mobile or a bot - must be called client-side only */
212
export function isMobileOrBot(): boolean {
3-
return /bot|googlebot|crawler|spider|robot|crawling|Mobile|Android|BlackBerry/i.test(
4-
navigator.userAgent,
5-
);
13+
return MOBILE_OR_BOT_UA_REGEX.test(navigator.userAgent);
614
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { Fixture, Interceptor } from '@data-client/test';
2+
3+
export type FixtureOrInterceptor<T = any> = Fixture | Interceptor<T>;
4+
5+
export interface PreviewProps<T = any> {
6+
groupId: string;
7+
defaultOpen: 'y' | 'n';
8+
row: boolean;
9+
fixtures: FixtureOrInterceptor<T>[];
10+
getInitialInterceptorData?: () => T;
11+
}

0 commit comments

Comments
 (0)