Skip to content

Commit f348748

Browse files
authored
Merge pull request #481 from reactjs/sync-6bfde58c
Sync with react.dev @ 6bfde58
2 parents d9ac4f2 + 3351aa3 commit f348748

35 files changed

+630
-178
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
@@ -1507,7 +1507,7 @@ function ConferenceLayout({conf, children}) {
15071507
navigate(e.target.value);
15081508
});
15091509
}}
1510-
className="appearance-none pe-8 bg-transparent text-primary-dark text-2xl font-bold mb-0.5"
1510+
className="appearance-none pe-8 ps-2 bg-transparent text-primary-dark text-2xl font-bold mb-0.5"
15111511
style={{
15121512
backgroundSize: '4px 4px, 4px 4px',
15131513
backgroundRepeat: 'no-repeat',
@@ -1516,8 +1516,16 @@ function ConferenceLayout({conf, children}) {
15161516
backgroundImage:
15171517
'linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%)',
15181518
}}>
1519-
<option value="react-conf-2021">React Conf 2021</option>
1520-
<option value="react-conf-2019">React Conf 2019</option>
1519+
<option
1520+
className="bg-wash dark:bg-wash-dark text-primary dark:text-primary-dark"
1521+
value="react-conf-2021">
1522+
React Conf 2021
1523+
</option>
1524+
<option
1525+
className="bg-wash dark:bg-wash-dark text-primary dark:text-primary-dark"
1526+
value="react-conf-2019">
1527+
React Conf 2019
1528+
</option>
15211529
</select>
15221530
</Cover>
15231531
<div className="px-4 pb-4" key={conf.id}>
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/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/Seo.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const deployedTranslations = [
2424
'es',
2525
'fr',
2626
'ja',
27+
'tr',
2728
// We'll add more languages when they have enough content.
2829
// Please DO NOT edit this list without a discussion in the reactjs/react.dev repo.
2930
// It must be the same between all translations.

src/content/community/conferences.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ Do you know of a local React.js conference? Add it here! (Please keep the list c
1010

1111
## Upcoming Conferences {/*upcoming-conferences*/}
1212

13+
### React Paris 2024 {/*react-paris-2024*/}
14+
March 22, 2024. In-person in Paris, France + Remote (hybrid)
15+
16+
[Website](https://react.paris/) - [Twitter](https://twitter.com/BeJS_) - [LinkedIn](https://www.linkedin.com/events/7150816372074192900/comments/)
17+
1318
### App.js Conf 2024 {/*appjs-conf-2024*/}
1419
May 22 - 24, 2024. In-person in Kraków, Poland + remote
1520

@@ -20,6 +25,11 @@ June 12 - June 14, 2024. Atlanta, GA, USA
2025

2126
[Website](https://renderatl.com) - [Discord](https://www.renderatl.com/discord) - [Twitter](https://twitter.com/renderATL) - [Instagram](https://www.instagram.com/renderatl/) - [Facebook](https://www.facebook.com/renderatl/) - [LinkedIn](https://www.linkedin.com/company/renderatl) - [Podcast](https://www.renderatl.com/culture-and-code#/)
2227

28+
### React Nexus 2024 {/*react-nexus-2024*/}
29+
July 04 & 05, 2024. Bangalore, India (In-person event)
30+
31+
[Website](https://reactnexus.com/) - [Twitter](https://twitter.com/ReactNexus) - [Linkedin](https://www.linkedin.com/company/react-nexus) - [YouTube](https://www.youtube.com/reactify_in)
32+
2333
### React India 2024 {/*react-india-2024*/}
2434
October 17 - 19, 2024. In-person in Goa, India (hybrid event) + Oct 15 2024 - remote day
2535

@@ -50,7 +60,7 @@ October 20 & 23, 2023. In-person in London, UK + remote first interactivity (hyb
5060
### React Brussels 2023 {/*react-brussels-2023*/}
5161
October 13th 2023. In-person in Brussels, Belgium + Remote (hybrid)
5262

53-
[Website](https://www.react.brussels/) - [Twitter](https://twitter.com/BrusselsReact)
63+
[Website](https://www.react.brussels/) - [Twitter](https://twitter.com/BrusselsReact) - [Videos](https://www.youtube.com/playlist?list=PL53Z0yyYnpWh85KeMomUoVz8_brrmh_aC)
5464

5565
### React India 2023 {/*react-india-2023*/}
5666
October 5 - 7, 2023. In-person in Goa, India (hybrid event) + Oct 3 2023 - remote day

0 commit comments

Comments
 (0)