1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-23 15:32:59 +02:00
Files
slate/site/pages/examples/[example].tsx
Joe Anderson fb87646e86 Experimental chunking optimisation and other performance improvements (#5871)
* Chunking optimization

* Fix comments

* Remove redundant `insertionsMinusRemovals` variable

* Fix typo

* Unblock Netlify builds

* Add placeholder

* Upgrade Playwright (fixes crash when debugging)

* Fix `autoFocus` not working

* Fix huge document test

* Fix the previous issue without changing `useSlateSelector`

* Retry `test:integration`

* Re-implement `useSlateWithV`

* Retry `test:integration`

* Update docs

* Update JS examples to match TS examples

* Upload Playwright's `test-results` directory in CI to access traces

* Change trace mode to `retain-on-first-failure`

* Fix: `Locator.fill(text)` is flaky on Editable

* Add changesets

* Increase minimum `slate-dom` version

* Update changeset

* Update 09-performance.md

* Deprecate the `useSlateWithV` hook

* Fix errors and improve clarity in 09-performance.md

* Minimum `slate-dom` version is now 0.116

* Update `yarn.lock`
2025-06-06 16:42:11 -07:00

434 lines
11 KiB
TypeScript

import React, { useState, PropsWithChildren, Ref, ErrorInfo } from 'react'
import { cx, css } from '@emotion/css'
import Head from 'next/head'
import Link from 'next/link'
import dynamic from 'next/dynamic'
import { ErrorBoundary } from 'react-error-boundary'
import { Icon } from '../../examples/ts/components/index'
import AndroidTests from '../../examples/ts/android-tests'
import CheckLists from '../../examples/ts/check-lists'
import CodeHighlighting from '../../examples/ts/code-highlighting'
import EditableVoids from '../../examples/ts/editable-voids'
import Embeds from '../../examples/ts/embeds'
import ForcedLayout from '../../examples/ts/forced-layout'
import HoveringToolbar from '../../examples/ts/hovering-toolbar'
import HugeDocument from '../../examples/ts/huge-document'
import Images from '../../examples/ts/images'
import Inlines from '../../examples/ts/inlines'
import MarkdownPreview from '../../examples/ts/markdown-preview'
import MarkdownShortcuts from '../../examples/ts/markdown-shortcuts'
import Mentions from '../../examples/ts/mentions'
import PasteHtml from '../../examples/ts/paste-html'
import PlainText from '../../examples/ts/plaintext'
import ReadOnly from '../../examples/ts/read-only'
import RichText from '../../examples/ts/richtext'
import SearchHighlighting from '../../examples/ts/search-highlighting'
import ShadowDOM from '../../examples/ts/shadow-dom'
import Styling from '../../examples/ts/styling'
import Tables from '../../examples/ts/tables'
import IFrames from '../../examples/ts/iframe'
import CustomPlaceholder from '../../examples/ts/custom-placeholder'
// node
import { getAllExamples } from '../api'
type ExampleTuple = [name: string, component: React.ComponentType, path: string]
const EXAMPLES: ExampleTuple[] = [
['Android Tests', AndroidTests, 'android-tests'],
['Checklists', CheckLists, 'check-lists'],
['Code Highlighting', CodeHighlighting, 'code-highlighting'],
['Custom Placeholder', CustomPlaceholder, 'custom-placeholder'],
['Editable Voids', EditableVoids, 'editable-voids'],
['Embeds', Embeds, 'embeds'],
['Forced Layout', ForcedLayout, 'forced-layout'],
['Hovering Toolbar', HoveringToolbar, 'hovering-toolbar'],
['Huge Document', HugeDocument, 'huge-document'],
['Images', Images, 'images'],
['Inlines', Inlines, 'inlines'],
['Markdown Preview', MarkdownPreview, 'markdown-preview'],
['Markdown Shortcuts', MarkdownShortcuts, 'markdown-shortcuts'],
['Mentions', Mentions, 'mentions'],
['Paste HTML', PasteHtml, 'paste-html'],
['Plain Text', PlainText, 'plaintext'],
['Read-only', ReadOnly, 'read-only'],
['Rendering in iframes', IFrames, 'iframe'],
['Rich Text', RichText, 'richtext'],
['Search Highlighting', SearchHighlighting, 'search-highlighting'],
['Shadow DOM', ShadowDOM, 'shadow-dom'],
['Styling', Styling, 'styling'],
['Tables', Tables, 'tables'],
]
const HIDDEN_EXAMPLES = ['android-tests']
const NON_HIDDEN_EXAMPLES = EXAMPLES.filter(
([, , path]) => !HIDDEN_EXAMPLES.includes(path)
)
const Header = (props: React.HTMLAttributes<HTMLDivElement>) => (
<div
{...props}
className={css`
align-items: center;
background: #000;
color: #aaa;
display: flex;
height: 42px;
position: relative;
z-index: 3; /* To appear above the underlay */
`}
/>
)
const Title = (props: React.HTMLAttributes<HTMLSpanElement>) => (
<span
{...props}
className={css`
margin-left: 1em;
`}
/>
)
const LinkList = (props: React.HTMLAttributes<HTMLDivElement>) => (
<div
{...props}
className={css`
margin-left: auto;
margin-right: 1em;
`}
/>
)
const A = (props: React.AnchorHTMLAttributes<HTMLAnchorElement>) => (
<a
{...props}
className={css`
margin-left: 1em;
color: #aaa;
text-decoration: none;
&:hover {
color: #fff;
text-decoration: underline;
}
`}
/>
)
const Pill = (props: React.HTMLAttributes<HTMLSpanElement>) => (
<span
{...props}
className={css`
background: #333;
border-radius: 9999px;
color: #aaa;
padding: 0.2em 0.5em;
`}
/>
)
const TabList = ({
isVisible,
...props
}: React.HTMLAttributes<HTMLDivElement> & { isVisible?: boolean }) => (
<div
{...props}
className={css`
background-color: #222;
display: flex;
flex-direction: column;
overflow: auto;
padding-top: 0.2em;
position: absolute;
transition: width 0.2s;
width: ${isVisible ? '200px' : '0'};
white-space: nowrap;
max-height: 70vh;
z-index: 3; /* To appear above the underlay */
`}
/>
)
const TabListUnderlay = ({
isVisible,
...props
}: React.HTMLAttributes<HTMLDivElement> & { isVisible?: boolean }) => (
<div
{...props}
className={css`
background-color: rgba(200, 200, 200, 0.8);
display: ${isVisible ? 'block' : 'none'};
height: 100%;
top: 0;
position: fixed;
width: 100%;
z-index: 2;
`}
/>
)
const TabButton = (props: React.HTMLAttributes<HTMLSpanElement>) => (
<span
{...props}
className={css`
margin-left: 0.8em;
&:hover {
cursor: pointer;
}
.material-icons {
color: #aaa;
font-size: 24px;
}
`}
/>
)
const Tab = React.forwardRef(
(
{
active,
href,
...props
}: PropsWithChildren<{
active: boolean
href: string
[key: string]: unknown
}>,
ref: Ref<HTMLAnchorElement>
) => (
<a
ref={ref}
href={href}
{...props}
className={css`
display: inline-block;
margin-bottom: 0.2em;
padding: 0.2em 1em;
border-radius: 0.2em;
text-decoration: none;
color: ${active ? 'white' : '#777'};
background: ${active ? '#333' : 'transparent'};
&:hover {
background: #333;
}
`}
/>
)
)
const Wrapper = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
{...props}
className={cx(
className,
css`
max-width: 42em;
margin: 20px auto;
padding: 20px;
`
)}
/>
)
const ExampleHeader = (props: React.HTMLAttributes<HTMLDivElement>) => (
<div
{...props}
className={css`
align-items: center;
background-color: #555;
color: #ddd;
display: flex;
height: 42px;
position: relative;
z-index: 3; /* To appear above the underlay */
`}
/>
)
const ExampleTitle = (props: React.HTMLAttributes<HTMLSpanElement>) => (
<span
{...props}
className={css`
margin-left: 1em;
`}
/>
)
const ExampleContent = (props: React.HTMLAttributes<HTMLDivElement>) => (
<Wrapper
{...props}
className={css`
background: #fff;
`}
/>
)
const Warning = (props: React.HTMLAttributes<HTMLDivElement>) => (
<Wrapper
{...props}
className={css`
background: #fffae0;
& > pre {
background: #fbf1bd;
white-space: pre;
overflow-x: scroll;
margin-bottom: 0;
}
`}
/>
)
const ExamplePage = ({ example }: { example: string }) => {
const [error, setError] = useState<Error | undefined>(undefined)
const [stacktrace, setStacktrace] = useState<ErrorInfo | undefined>(undefined)
const [showTabs, setShowTabs] = useState<boolean>(false)
const EXAMPLE = EXAMPLES.find(e => e[2] === example)
const [name, Component, path] = EXAMPLE!
return (
<ErrorBoundary
onError={(error, stacktrace) => {
setError(error)
setStacktrace(stacktrace)
}}
fallbackRender={({ error, resetErrorBoundary }) => (
<Warning>
<p>An error was thrown by one of the example's React components!</p>
<pre>
<code>
{error.stack}
{'\n'}
{stacktrace}
</code>
</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</Warning>
)}
>
<div>
<Head>
<title>Slate Examples</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/index.css" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:400,400i,700,700i&subset=latin-ext"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/icon?family=Material+Icons"
/>
</Head>
<Header>
<Title>Slate Examples</Title>
<LinkList>
<A href="https://github.com/ianstormtaylor/slate">GitHub</A>
<A href="https://docs.slatejs.org/">Docs</A>
</LinkList>
</Header>
<ExampleHeader>
<TabButton
onClick={e => {
e.stopPropagation()
setShowTabs(!showTabs)
}}
>
<Icon>menu</Icon>
</TabButton>
<ExampleTitle>
{name}
<A
href={`https://github.com/ianstormtaylor/slate/blob/main/site/examples/js/${path}.jsx`}
>
<Pill>JS Code</Pill>
</A>
<A
href={`https://github.com/ianstormtaylor/slate/blob/main/site/examples/ts/${path}.tsx`}
>
<Pill>TS Code</Pill>
</A>
</ExampleTitle>
</ExampleHeader>
<TabList isVisible={showTabs}>
{NON_HIDDEN_EXAMPLES.map(([n, , p]) => (
<Link
key={p as string}
href="/examples/[example]"
as={`/examples/${p}`}
legacyBehavior
passHref
>
<Tab onClick={() => setShowTabs(false)}>{n}</Tab>
</Link>
))}
</TabList>
{error ? (
<Warning>
<p>An error was thrown by one of the example's React components!</p>
<pre>
<code>
<>
{error.stack}
{'\n'}
{stacktrace}
</>
</code>
</pre>
</Warning>
) : (
<ExampleContent>
<Component />
</ExampleContent>
)}
<TabListUnderlay
isVisible={showTabs}
onClick={() => setShowTabs(false)}
/>
</div>
</ErrorBoundary>
)
}
// Disable SSR because it results in a double rendering which makes debugging
// examples more challenging. No idea how any of this works.
const NoSsrExamplePage = dynamic(() => Promise.resolve(ExamplePage), {
ssr: false,
})
export async function getStaticPaths() {
const paths = getAllExamples()
return {
paths: paths.map(path => ({
params: {
example: path,
},
})),
fallback: false,
}
}
export async function getStaticProps({
params,
}: {
params: { example: string }
}) {
return {
props: {
example: params.example,
},
}
}
export default NoSsrExamplePage