Skip to content

Commit 7ad0cac

Browse files
merging all conflicts
2 parents c4da3fe + 6bfde58 commit 7ad0cac

File tree

110 files changed

+1701
-1111
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

110 files changed

+1701
-1111
lines changed

plugins/remark-smartypants.js

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/*!
2+
* Based on 'silvenon/remark-smartypants'
3+
* https://github.com/silvenon/remark-smartypants/pull/80
4+
*/
5+
16
const visit = require('unist-util-visit');
27
const retext = require('retext');
38
const smartypants = require('retext-smartypants');
@@ -9,12 +14,48 @@ function check(parent) {
914
}
1015

1116
module.exports = function (options) {
12-
const processor = retext().use(smartypants, options);
17+
const processor = retext().use(smartypants, {
18+
...options,
19+
// Do not replace ellipses, dashes, backticks because they change string
20+
// length, and we couldn't guarantee right splice of text in second visit of
21+
// tree
22+
ellipses: false,
23+
dashes: false,
24+
backticks: false,
25+
});
26+
27+
const processor2 = retext().use(smartypants, {
28+
...options,
29+
// Do not replace quotes because they are already replaced in the first
30+
// processor
31+
quotes: false,
32+
});
1333

1434
function transformer(tree) {
15-
visit(tree, 'text', (node, index, parent) => {
16-
if (check(parent)) node.value = String(processor.processSync(node.value));
35+
let allText = '';
36+
let startIndex = 0;
37+
const textOrInlineCodeNodes = [];
38+
39+
visit(tree, ['text', 'inlineCode'], (node, _, parent) => {
40+
if (check(parent)) {
41+
if (node.type === 'text') allText += node.value;
42+
// for the case when inlineCode contains just one part of quote: `foo'bar`
43+
else allText += 'A'.repeat(node.value.length);
44+
textOrInlineCodeNodes.push(node);
45+
}
1746
});
47+
48+
// Concat all text into one string, to properly replace quotes around non-"text" nodes
49+
allText = String(processor.processSync(allText));
50+
51+
for (const node of textOrInlineCodeNodes) {
52+
const endIndex = startIndex + node.value.length;
53+
if (node.type === 'text') {
54+
const processedText = allText.slice(startIndex, endIndex);
55+
node.value = String(processor2.processSync(processedText));
56+
}
57+
startIndex = endIndex;
58+
}
1859
}
1960

2061
return transformer;

src/components/DocsFooter.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const DocsPageFooter = memo<DocsPageFooterProps>(
2727
<>
2828
{prevRoute?.path || nextRoute?.path ? (
2929
<>
30-
<div className="max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-2 gap-4 py-4 md:py-12">
30+
<div className="grid grid-cols-1 gap-4 py-4 mx-auto max-w-7xl md:grid-cols-2 md:py-12">
3131
{prevRoute?.path ? (
3232
<FooterLink
3333
type="Previous"
@@ -69,21 +69,23 @@ function FooterLink({
6969
<NextLink
7070
href={href}
7171
className={cn(
72-
'flex gap-x-4 md:gap-x-6 items-center w-full md:w-80 px-4 md:px-5 py-6 border-2 border-transparent text-base leading-base text-link dark:text-link-dark rounded-lg group focus:text-link dark:focus:text-link-dark focus:bg-highlight focus:border-link dark:focus:bg-highlight-dark dark:focus:border-link-dark focus:border-opacity-100 focus:border-2 focus:ring-1 focus:ring-offset-4 focus:ring-blue-40 active:ring-0 active:ring-offset-0 hover:bg-gray-5 dark:hover:bg-gray-80',
72+
'flex gap-x-4 md:gap-x-6 items-center w-full md:min-w-80 md:w-fit md:max-w-md px-4 md:px-5 py-6 border-2 border-transparent text-base leading-base text-link dark:text-link-dark rounded-lg group focus:text-link dark:focus:text-link-dark focus:bg-highlight focus:border-link dark:focus:bg-highlight-dark dark:focus:border-link-dark focus:border-opacity-100 focus:border-2 focus:ring-1 focus:ring-offset-4 focus:ring-blue-40 active:ring-0 active:ring-offset-0 hover:bg-gray-5 dark:hover:bg-gray-80',
7373
{
7474
'flex-row-reverse justify-self-end text-end': type === 'Next',
7575
}
7676
)}>
7777
<IconNavArrow
78-
className="text-tertiary dark:text-tertiary-dark inline group-focus:text-link dark:group-focus:text-link-dark"
78+
className="inline text-tertiary dark:text-tertiary-dark group-focus:text-link dark:group-focus:text-link-dark"
7979
displayDirection={type === 'Previous' ? 'start' : 'end'}
8080
/>
81-
<span>
82-
<span className="block no-underline text-sm tracking-wide text-secondary dark:text-secondary-dark uppercase font-bold group-focus:text-link dark:group-focus:text-link-dark group-focus:text-opacity-100">
81+
<div className="flex flex-col overflow-hidden">
82+
<span className="text-sm font-bold tracking-wide no-underline uppercase text-secondary dark:text-secondary-dark group-focus:text-link dark:group-focus:text-link-dark group-focus:text-opacity-100">
8383
{type}
8484
</span>
85-
<span className="block text-lg group-hover:underline">{title}</span>
86-
</span>
85+
<span className="text-lg break-words group-hover:underline">
86+
{title}
87+
</span>
88+
</div>
8789
</NextLink>
8890
);
8991
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Error Decoder requires reading pregenerated error message from getStaticProps,
2+
// but MDX component doesn't support props. So we use React Context to populate
3+
// the value without prop-drilling.
4+
// TODO: Replace with React.cache + React.use when migrating to Next.js App Router
5+
6+
import {createContext, useContext} from 'react';
7+
8+
const notInErrorDecoderContext = Symbol('not in error decoder context');
9+
10+
export const ErrorDecoderContext = createContext<
11+
| {errorMessage: string | null; errorCode: string | null}
12+
| typeof notInErrorDecoderContext
13+
>(notInErrorDecoderContext);
14+
15+
export const useErrorDecoderParams = () => {
16+
const params = useContext(ErrorDecoderContext);
17+
18+
if (params === notInErrorDecoderContext) {
19+
throw new Error('useErrorDecoder must be used in error decoder pages only');
20+
}
21+
22+
return params;
23+
};

src/components/Layout/Feedback.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import {useState} from 'react';
66
import {useRouter} from 'next/router';
7+
import cn from 'classnames';
78

89
export function Feedback({onSubmit = () => {}}: {onSubmit?: () => void}) {
910
const {asPath} = useRouter();
@@ -60,7 +61,11 @@ function sendGAEvent(isPositive: boolean) {
6061
function SendFeedback({onSubmit}: {onSubmit: () => void}) {
6162
const [isSubmitted, setIsSubmitted] = useState(false);
6263
return (
63-
<div className="max-w-xs w-80 lg:w-auto py-3 shadow-lg rounded-lg m-4 bg-wash dark:bg-gray-95 px-4 flex">
64+
<div
65+
className={cn(
66+
'max-w-xs w-80 lg:w-auto py-3 shadow-lg rounded-lg m-4 bg-wash dark:bg-gray-95 px-4 flex',
67+
{exit: isSubmitted}
68+
)}>
6469
<p className="w-full font-bold text-primary dark:text-primary-dark text-lg me-4">
6570
{isSubmitted ? 'Thank you for your feedback!' : 'Is this page useful?'}
6671
</p>

src/components/Layout/HomeContent.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,7 +1499,7 @@ function ConferenceLayout({conf, children}) {
14991499
navigate(e.target.value);
15001500
});
15011501
}}
1502-
className="appearance-none pe-8 bg-transparent text-primary-dark text-2xl font-bold mb-0.5"
1502+
className="appearance-none pe-8 ps-2 bg-transparent text-primary-dark text-2xl font-bold mb-0.5"
15031503
style={{
15041504
backgroundSize: '4px 4px, 4px 4px',
15051505
backgroundRepeat: 'no-repeat',
@@ -1508,8 +1508,16 @@ function ConferenceLayout({conf, children}) {
15081508
backgroundImage:
15091509
'linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%)',
15101510
}}>
1511-
<option value="react-conf-2021">React Conf 2021</option>
1512-
<option value="react-conf-2019">React Conf 2019</option>
1511+
<option
1512+
className="bg-wash dark:bg-wash-dark text-primary dark:text-primary-dark"
1513+
value="react-conf-2021">
1514+
React Conf 2021
1515+
</option>
1516+
<option
1517+
className="bg-wash dark:bg-wash-dark text-primary dark:text-primary-dark"
1518+
value="react-conf-2019">
1519+
React Conf 2019
1520+
</option>
15131521
</select>
15141522
</Cover>
15151523
<div className="px-4 pb-4" key={conf.id}>

src/components/MDX/ErrorDecoder.tsx

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import {useEffect, useState} from 'react';
2+
import {useErrorDecoderParams} from '../ErrorDecoderContext';
3+
import cn from 'classnames';
4+
5+
function replaceArgs(
6+
msg: string,
7+
argList: Array<string | undefined>,
8+
replacer = '[missing argument]'
9+
): string {
10+
let argIdx = 0;
11+
return msg.replace(/%s/g, function () {
12+
const arg = argList[argIdx++];
13+
// arg can be an empty string: ?args[0]=&args[1]=count
14+
return arg === undefined || arg === '' ? replacer : arg;
15+
});
16+
}
17+
18+
/**
19+
* Sindre Sorhus <https://sindresorhus.com>
20+
* Released under MIT license
21+
* https://github.com/sindresorhus/linkify-urls/blob/edd75a64a9c36d7025f102f666ddbb6cf0afa7cd/index.js#L4C25-L4C137
22+
*
23+
* The regex is used to extract URL from the string for linkify.
24+
*/
25+
const urlRegex =
26+
/((?<!\+)https?:\/\/(?:www\.)?(?:[-\w.]+?[.@][a-zA-Z\d]{2,}|localhost)(?:[-\w.:%+~#*$!?&/=@]*?(?:,(?!\s))*?)*)/g;
27+
28+
// When the message contains a URL (like https://fb.me/react-refs-must-have-owner),
29+
// make it a clickable link.
30+
function urlify(str: string): React.ReactNode[] {
31+
const segments = str.split(urlRegex);
32+
33+
return segments.map((message, i) => {
34+
if (i % 2 === 1) {
35+
return (
36+
<a
37+
key={i}
38+
target="_blank"
39+
className="underline"
40+
rel="noopener noreferrer"
41+
href={message}>
42+
{message}
43+
</a>
44+
);
45+
}
46+
return message;
47+
});
48+
}
49+
50+
// `?args[]=foo&args[]=bar`
51+
// or `// ?args[0]=foo&args[1]=bar`
52+
function parseQueryString(search: string): Array<string | undefined> {
53+
const rawQueryString = search.substring(1);
54+
if (!rawQueryString) {
55+
return [];
56+
}
57+
58+
const args: Array<string | undefined> = [];
59+
60+
const queries = rawQueryString.split('&');
61+
for (let i = 0; i < queries.length; i++) {
62+
const query = decodeURIComponent(queries[i]);
63+
if (query.startsWith('args[')) {
64+
args.push(query.slice(query.indexOf(']=') + 2));
65+
}
66+
}
67+
68+
return args;
69+
}
70+
71+
export default function ErrorDecoder() {
72+
const {errorMessage} = useErrorDecoderParams();
73+
/** error messages that contain %s require reading location.search */
74+
const hasParams = errorMessage?.includes('%s');
75+
const [message, setMessage] = useState<React.ReactNode | null>(() =>
76+
errorMessage ? urlify(errorMessage) : null
77+
);
78+
79+
const [isReady, setIsReady] = useState(errorMessage == null || !hasParams);
80+
81+
useEffect(() => {
82+
if (errorMessage == null || !hasParams) {
83+
return;
84+
}
85+
86+
setMessage(
87+
urlify(
88+
replaceArgs(
89+
errorMessage,
90+
parseQueryString(window.location.search),
91+
'[missing argument]'
92+
)
93+
)
94+
);
95+
setIsReady(true);
96+
}, [hasParams, errorMessage]);
97+
98+
return (
99+
<code
100+
className={cn(
101+
'block bg-red-100 text-red-600 py-4 px-6 mt-5 rounded-lg',
102+
isReady ? 'opacity-100' : 'opacity-0'
103+
)}>
104+
<b>{message}</b>
105+
</code>
106+
);
107+
}

src/components/MDX/MDXComponents.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import {TocContext} from './TocContext';
3131
import type {Toc, TocItem} from './TocContext';
3232
import {TeamMember} from './TeamMember';
3333

34+
import ErrorDecoder from './ErrorDecoder';
35+
3436
function CodeStep({children, step}: {children: any; step: number}) {
3537
return (
3638
<span
@@ -441,6 +443,7 @@ export const MDXComponents = {
441443
Solution,
442444
CodeStep,
443445
YouTubeIframe,
446+
ErrorDecoder,
444447
};
445448

446449
for (let key in MDXComponents) {

src/components/MDX/Sandpack/DownloadButton.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import {useSyncExternalStore} from 'react';
66
import {useSandpack} from '@codesandbox/sandpack-react/unstyled';
77
import {IconDownload} from '../../Icon/IconDownload';
8+
import {AppJSPath, StylesCSSPath, SUPPORTED_FILES} from './createFileMap';
89
export interface DownloadButtonProps {}
910

1011
let supportsImportMap = false;
@@ -32,8 +33,6 @@ function useSupportsImportMap() {
3233
return useSyncExternalStore(subscribe, getCurrentValue, getServerSnapshot);
3334
}
3435

35-
const SUPPORTED_FILES = ['/App.js', '/styles.css'];
36-
3736
export function DownloadButton({
3837
providedFiles,
3938
}: {
@@ -49,8 +48,8 @@ export function DownloadButton({
4948
}
5049

5150
const downloadHTML = () => {
52-
const css = sandpack.files['/styles.css']?.code ?? '';
53-
const code = sandpack.files['/App.js']?.code ?? '';
51+
const css = sandpack.files[StylesCSSPath]?.code ?? '';
52+
const code = sandpack.files[AppJSPath]?.code ?? '';
5453
const blob = new Blob([
5554
`<!DOCTYPE html>
5655
<html>

src/components/MDX/Sandpack/Preview.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export function Preview({
5454

5555
// When throwing a new Error in Sandpack - we want to disable the dev error dialog
5656
// to show the Error Boundary fallback
57-
if (rawError && rawError.message.includes(`throw Error('Example error')`)) {
57+
if (rawError && rawError.message.includes('Example Error:')) {
5858
rawError = null;
5959
}
6060

src/components/MDX/Sandpack/SandpackRoot.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {SandpackLogLevel} from '@codesandbox/sandpack-client';
99
import {CustomPreset} from './CustomPreset';
1010
import {createFileMap} from './createFileMap';
1111
import {CustomTheme} from './Themes';
12+
import {template} from './template';
1213

1314
type SandpackProps = {
1415
children: React.ReactNode;
@@ -70,17 +71,19 @@ function SandpackRoot(props: SandpackProps) {
7071
const codeSnippets = Children.toArray(children) as React.ReactElement[];
7172
const files = createFileMap(codeSnippets);
7273

73-
files['/styles.css'] = {
74-
code: [sandboxStyle, files['/styles.css']?.code ?? ''].join('\n\n'),
75-
hidden: !files['/styles.css']?.visible,
74+
files['/src/styles.css'] = {
75+
code: [sandboxStyle, files['/src/styles.css']?.code ?? ''].join('\n\n'),
76+
hidden: !files['/src/styles.css']?.visible,
7677
};
7778

7879
return (
7980
<div className="sandpack sandpack--playground w-full my-8" dir="ltr">
8081
<SandpackProvider
81-
template="react"
82-
files={files}
82+
files={{...template, ...files}}
8383
theme={CustomTheme}
84+
customSetup={{
85+
environment: 'create-react-app',
86+
}}
8487
options={{
8588
autorun,
8689
initMode: 'user-visible',

0 commit comments

Comments
 (0)