Skip to content

Commit

Permalink
Merge pull request #903 from reactjs/sync-6bfde58c
Browse files Browse the repository at this point in the history
Sync with react.dev @ 6bfde58
  • Loading branch information
carburo authored Jan 15, 2024
2 parents 3c78ee5 + 0708749 commit b812888
Show file tree
Hide file tree
Showing 104 changed files with 1,622 additions and 1,104 deletions.
47 changes: 44 additions & 3 deletions plugins/remark-smartypants.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*!
* Based on 'silvenon/remark-smartypants'
* https://github.com/silvenon/remark-smartypants/pull/80
*/

const visit = require('unist-util-visit');
const retext = require('retext');
const smartypants = require('retext-smartypants');
Expand All @@ -9,12 +14,48 @@ function check(parent) {
}

module.exports = function (options) {
const processor = retext().use(smartypants, options);
const processor = retext().use(smartypants, {
...options,
// Do not replace ellipses, dashes, backticks because they change string
// length, and we couldn't guarantee right splice of text in second visit of
// tree
ellipses: false,
dashes: false,
backticks: false,
});

const processor2 = retext().use(smartypants, {
...options,
// Do not replace quotes because they are already replaced in the first
// processor
quotes: false,
});

function transformer(tree) {
visit(tree, 'text', (node, index, parent) => {
if (check(parent)) node.value = String(processor.processSync(node.value));
let allText = '';
let startIndex = 0;
const textOrInlineCodeNodes = [];

visit(tree, ['text', 'inlineCode'], (node, _, parent) => {
if (check(parent)) {
if (node.type === 'text') allText += node.value;
// for the case when inlineCode contains just one part of quote: `foo'bar`
else allText += 'A'.repeat(node.value.length);
textOrInlineCodeNodes.push(node);
}
});

// Concat all text into one string, to properly replace quotes around non-"text" nodes
allText = String(processor.processSync(allText));

for (const node of textOrInlineCodeNodes) {
const endIndex = startIndex + node.value.length;
if (node.type === 'text') {
const processedText = allText.slice(startIndex, endIndex);
node.value = String(processor2.processSync(processedText));
}
startIndex = endIndex;
}
}

return transformer;
Expand Down
29 changes: 16 additions & 13 deletions src/components/DocsFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ export const DocsPageFooter = memo<DocsPageFooterProps>(
<>
{prevRoute?.path || nextRoute?.path ? (
<>
<div className="max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-2 gap-4 py-4 md:py-12">
<div className="grid grid-cols-1 gap-4 py-4 mx-auto max-w-7xl md:grid-cols-2 md:py-12">
{prevRoute?.path ? (
<FooterLink
type="Anterior"
type="Previous"
title={prevRoute.title}
href={prevRoute.path}
/>
Expand All @@ -40,7 +40,7 @@ export const DocsPageFooter = memo<DocsPageFooterProps>(

{nextRoute?.path ? (
<FooterLink
type="Siguiente"
type="Next"
title={nextRoute.title}
href={nextRoute.path}
/>
Expand All @@ -63,27 +63,30 @@ function FooterLink({
}: {
href: string;
title: string;
type: 'Anterior' | 'Siguiente';
type: 'Previous' | 'Next';
}) {
const typeTranslation = type === 'Previous' ? 'Anterior' : 'Siguiente';
return (
<NextLink
href={href}
className={cn(
'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',
'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',
{
'flex-row-reverse justify-self-end text-end': type === 'Siguiente',
'flex-row-reverse justify-self-end text-end': type === 'Next',
}
)}>
<IconNavArrow
className="text-tertiary dark:text-tertiary-dark inline group-focus:text-link dark:group-focus:text-link-dark"
displayDirection={type === 'Anterior' ? 'start' : 'end'}
className="inline text-tertiary dark:text-tertiary-dark group-focus:text-link dark:group-focus:text-link-dark"
displayDirection={type === 'Previous' ? 'start' : 'end'}
/>
<span>
<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">
{type}
<div className="flex flex-col overflow-hidden">
<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">
{typeTranslation}
</span>
<span className="block text-lg group-hover:underline">{title}</span>
</span>
<span className="text-lg break-words group-hover:underline">
{title}
</span>
</div>
</NextLink>
);
}
23 changes: 23 additions & 0 deletions src/components/ErrorDecoderContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Error Decoder requires reading pregenerated error message from getStaticProps,
// but MDX component doesn't support props. So we use React Context to populate
// the value without prop-drilling.
// TODO: Replace with React.cache + React.use when migrating to Next.js App Router

import {createContext, useContext} from 'react';

const notInErrorDecoderContext = Symbol('not in error decoder context');

export const ErrorDecoderContext = createContext<
| {errorMessage: string | null; errorCode: string | null}
| typeof notInErrorDecoderContext
>(notInErrorDecoderContext);

export const useErrorDecoderParams = () => {
const params = useContext(ErrorDecoderContext);

if (params === notInErrorDecoderContext) {
throw new Error('useErrorDecoder must be used in error decoder pages only');
}

return params;
};
7 changes: 6 additions & 1 deletion src/components/Layout/Feedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import {useState} from 'react';
import {useRouter} from 'next/router';
import cn from 'classnames';

export function Feedback({onSubmit = () => {}}: {onSubmit?: () => void}) {
const {asPath} = useRouter();
Expand Down Expand Up @@ -60,7 +61,11 @@ function sendGAEvent(isPositive: boolean) {
function SendFeedback({onSubmit}: {onSubmit: () => void}) {
const [isSubmitted, setIsSubmitted] = useState(false);
return (
<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">
<div
className={cn(
'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',
{exit: isSubmitted}
)}>
<p className="w-full font-bold text-primary dark:text-primary-dark text-lg me-4">
{isSubmitted
? '¡Gracias por tus comentarios!'
Expand Down
14 changes: 11 additions & 3 deletions src/components/Layout/HomeContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -1511,7 +1511,7 @@ function ConferenceLayout({conf, children}) {
navigate(e.target.value);
});
}}
className="appearance-none pe-8 bg-transparent text-primary-dark text-2xl font-bold mb-0.5"
className="appearance-none pe-8 ps-2 bg-transparent text-primary-dark text-2xl font-bold mb-0.5"
style={{
backgroundSize: '4px 4px, 4px 4px',
backgroundRepeat: 'no-repeat',
Expand All @@ -1520,8 +1520,16 @@ function ConferenceLayout({conf, children}) {
backgroundImage:
'linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%)',
}}>
<option value="react-conf-2021">React Conf 2021</option>
<option value="react-conf-2019">React Conf 2019</option>
<option
className="bg-wash dark:bg-wash-dark text-primary dark:text-primary-dark"
value="react-conf-2021">
React Conf 2021
</option>
<option
className="bg-wash dark:bg-wash-dark text-primary dark:text-primary-dark"
value="react-conf-2019">
React Conf 2019
</option>
</select>
</Cover>
<div className="px-4 pb-4" key={conf.id}>
Expand Down
107 changes: 107 additions & 0 deletions src/components/MDX/ErrorDecoder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {useEffect, useState} from 'react';
import {useErrorDecoderParams} from '../ErrorDecoderContext';
import cn from 'classnames';

function replaceArgs(
msg: string,
argList: Array<string | undefined>,
replacer = '[missing argument]'
): string {
let argIdx = 0;
return msg.replace(/%s/g, function () {
const arg = argList[argIdx++];
// arg can be an empty string: ?args[0]=&args[1]=count
return arg === undefined || arg === '' ? replacer : arg;
});
}

/**
* Sindre Sorhus <https://sindresorhus.com>
* Released under MIT license
* https://github.com/sindresorhus/linkify-urls/blob/edd75a64a9c36d7025f102f666ddbb6cf0afa7cd/index.js#L4C25-L4C137
*
* The regex is used to extract URL from the string for linkify.
*/
const urlRegex =
/((?<!\+)https?:\/\/(?:www\.)?(?:[-\w.]+?[.@][a-zA-Z\d]{2,}|localhost)(?:[-\w.:%+~#*$!?&/=@]*?(?:,(?!\s))*?)*)/g;

// When the message contains a URL (like https://fb.me/react-refs-must-have-owner),
// make it a clickable link.
function urlify(str: string): React.ReactNode[] {
const segments = str.split(urlRegex);

return segments.map((message, i) => {
if (i % 2 === 1) {
return (
<a
key={i}
target="_blank"
className="underline"
rel="noopener noreferrer"
href={message}>
{message}
</a>
);
}
return message;
});
}

// `?args[]=foo&args[]=bar`
// or `// ?args[0]=foo&args[1]=bar`
function parseQueryString(search: string): Array<string | undefined> {
const rawQueryString = search.substring(1);
if (!rawQueryString) {
return [];
}

const args: Array<string | undefined> = [];

const queries = rawQueryString.split('&');
for (let i = 0; i < queries.length; i++) {
const query = decodeURIComponent(queries[i]);
if (query.startsWith('args[')) {
args.push(query.slice(query.indexOf(']=') + 2));
}
}

return args;
}

export default function ErrorDecoder() {
const {errorMessage} = useErrorDecoderParams();
/** error messages that contain %s require reading location.search */
const hasParams = errorMessage?.includes('%s');
const [message, setMessage] = useState<React.ReactNode | null>(() =>
errorMessage ? urlify(errorMessage) : null
);

const [isReady, setIsReady] = useState(errorMessage == null || !hasParams);

useEffect(() => {
if (errorMessage == null || !hasParams) {
return;
}

setMessage(
urlify(
replaceArgs(
errorMessage,
parseQueryString(window.location.search),
'[missing argument]'
)
)
);
setIsReady(true);
}, [hasParams, errorMessage]);

return (
<code
className={cn(
'block bg-red-100 text-red-600 py-4 px-6 mt-5 rounded-lg',
isReady ? 'opacity-100' : 'opacity-0'
)}>
<b>{message}</b>
</code>
);
}
3 changes: 3 additions & 0 deletions src/components/MDX/MDXComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import {TocContext} from './TocContext';
import type {Toc, TocItem} from './TocContext';
import {TeamMember} from './TeamMember';

import ErrorDecoder from './ErrorDecoder';

function CodeStep({children, step}: {children: any; step: number}) {
return (
<span
Expand Down Expand Up @@ -441,6 +443,7 @@ export const MDXComponents = {
Solution,
CodeStep,
YouTubeIframe,
ErrorDecoder,
};

for (let key in MDXComponents) {
Expand Down
7 changes: 3 additions & 4 deletions src/components/MDX/Sandpack/DownloadButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import {useSyncExternalStore} from 'react';
import {useSandpack} from '@codesandbox/sandpack-react/unstyled';
import {IconDownload} from '../../Icon/IconDownload';
import {AppJSPath, StylesCSSPath, SUPPORTED_FILES} from './createFileMap';
export interface DownloadButtonProps {}

let supportsImportMap = false;
Expand Down Expand Up @@ -32,8 +33,6 @@ function useSupportsImportMap() {
return useSyncExternalStore(subscribe, getCurrentValue, getServerSnapshot);
}

const SUPPORTED_FILES = ['/App.js', '/styles.css'];

export function DownloadButton({
providedFiles,
}: {
Expand All @@ -49,8 +48,8 @@ export function DownloadButton({
}

const downloadHTML = () => {
const css = sandpack.files['/styles.css']?.code ?? '';
const code = sandpack.files['/App.js']?.code ?? '';
const css = sandpack.files[StylesCSSPath]?.code ?? '';
const code = sandpack.files[AppJSPath]?.code ?? '';
const blob = new Blob([
`<!DOCTYPE html>
<html>
Expand Down
2 changes: 1 addition & 1 deletion src/components/MDX/Sandpack/Preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function Preview({

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

Expand Down
13 changes: 8 additions & 5 deletions src/components/MDX/Sandpack/SandpackRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {SandpackLogLevel} from '@codesandbox/sandpack-client';
import {CustomPreset} from './CustomPreset';
import {createFileMap} from './createFileMap';
import {CustomTheme} from './Themes';
import {template} from './template';

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

files['/styles.css'] = {
code: [sandboxStyle, files['/styles.css']?.code ?? ''].join('\n\n'),
hidden: !files['/styles.css']?.visible,
files['/src/styles.css'] = {
code: [sandboxStyle, files['/src/styles.css']?.code ?? ''].join('\n\n'),
hidden: !files['/src/styles.css']?.visible,
};

return (
<div className="sandpack sandpack--playground w-full my-8" dir="ltr">
<SandpackProvider
template="react"
files={files}
files={{...template, ...files}}
theme={CustomTheme}
customSetup={{
environment: 'create-react-app',
}}
options={{
autorun,
initMode: 'user-visible',
Expand Down
Loading

0 comments on commit b812888

Please sign in to comment.